SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / tod.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-only */
9
10 /* A function for returning the time of day in various formats */
11
12
13 #include "exim.h"
14
15 /* #define TESTING_LOG_DATESTAMP */
16
17
18 static uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss.ddd +zzzz")];
19
20
21 /*************************************************
22 *                Return timestamp                *
23 *************************************************/
24
25 /* The log timestamp format is dd-mmm-yy so as to be non-confusing on both
26 sides of the Atlantic. We calculate an explicit numerical offset from GMT for
27 the full datestamp and BSD inbox datestamp. Note that on some systems
28 localtime() and gmtime() re-use the same store, so we must save the local time
29 values before calling gmtime(). If timestamps_utc is set, don't use
30 localtime(); all times are then in UTC (with offset +0000).
31
32 There are also some contortions to get the day of the month without
33 a leading zero for the full stamp, since Ustrftime() doesn't provide this
34 option.
35
36 Argument:  type of timestamp required:
37              tod_bsdin                  BSD inbox format
38              tod_epoch                  Unix epoch format
39              tod_epochl                 Unix epoch/usec format
40              tod_full                   full date and time
41              tod_log                    log file data line format,
42                                           with zone if log_timezone is TRUE
43              tod_log_bare               always without zone
44              tod_log_datestamp_daily    for log file names when datestamped daily
45              tod_log_datestamp_monthly  for log file names when datestamped monthly
46              tod_log_zone               always with zone
47              tod_mbx                    MBX inbox format
48              tod_zone                   just the timezone offset
49              tod_zulu                   time in 8601 zulu format
50
51 Returns:   pointer to fixed buffer containing the timestamp
52 */
53
54 uschar *
55 tod_stamp(int type)
56 {
57 struct timeval now;
58 struct tm * t;
59
60 gettimeofday(&now, NULL);
61
62 /* Styles that don't need local time */
63
64 switch(type)
65   {
66   case tod_epoch:
67     (void) snprintf(CS timebuf, sizeof(timebuf), TIME_T_FMT, now.tv_sec);  /* Unix epoch format */
68     return timebuf;     /* NB the above will be wrong if time_t is FP */
69
70   case tod_epoch_l:
71     /* Unix epoch/usec format */
72     (void) snprintf(CS timebuf, sizeof(timebuf), TIME_T_FMT "%06ld", now.tv_sec, (long) now.tv_usec );
73     return timebuf;
74
75   case tod_zulu:
76     t = gmtime(&now.tv_sec);
77     (void) snprintf(CS timebuf, sizeof(timebuf), "%04u%02u%02u%02u%02u%02uZ",
78       1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday, (uint)t->tm_hour, (uint)t->tm_min,
79       (uint)t->tm_sec);
80     return timebuf;
81   }
82
83 /* Vary log type according to timezone requirement */
84
85 if (type == tod_log) type = log_timezone ? tod_log_zone : tod_log_bare;
86
87 /* Convert to local time or UTC */
88
89 t = f.timestamps_utc ? gmtime(&now.tv_sec) : localtime(&now.tv_sec);
90
91 switch(type)
92   {
93   case tod_log_bare:          /* Format used in logging without timezone */
94 #ifndef COMPILE_UTILITY
95     if (LOGGING(millisec))
96       snprintf(CS timebuf, sizeof(timebuf), "%04u-%02u-%02u %02u:%02u:%02u.%03u",
97         1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
98         (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec,
99         (uint)(now.tv_usec/1000));
100     else
101 #endif
102       snprintf(CS timebuf, sizeof(timebuf), "%04u-%02u-%02u %02u:%02u:%02u",
103         1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
104         (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec);
105
106     break;
107
108     /* Format used as suffix of log file name when 'log_datestamp' is active. For
109     testing purposes, it changes the file every second. */
110
111 #ifdef TESTING_LOG_DATESTAMP
112   case tod_log_datestamp_daily:
113   case tod_log_datestamp_monthly:
114     snprintf(CS timebuf, sizeof(timebuf), "%04u%02u%02u%02u%02u",
115       1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
116       (uint)t->tm_hour, (uint)t->tm_min);
117     break;
118
119 #else
120   case tod_log_datestamp_daily:
121     snprintf(CS timebuf, sizeof(timebuf), "%04u%02u%02u",
122       1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday);
123     break;
124
125   case tod_log_datestamp_monthly:
126 #ifndef COMPILE_UTILITY
127     snprintf(CS timebuf, sizeof(timebuf), "%04u%02u",
128       1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon);
129 #endif
130     break;
131 #endif
132
133     /* Format used in BSD inbox separator lines. Sort-of documented in RFC 976
134     ("UUCP Mail Interchange Format Standard") but only by example, not by
135     explicit definition. The examples show no timezone offsets, and some MUAs
136     appear to be sensitive to this, so Exim has been changed to remove the
137     timezone offsets that originally appeared. */
138
139   case tod_bsdin:
140       {
141       int len = Ustrftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S", t);
142       Ustrftime(timebuf + len, sizeof(timebuf) - len, " %Y", t);
143       }
144     break;
145
146     /* Other types require the GMT offset to be calculated, or just set up in the
147     case of UTC timestamping. We need to take a copy of the local time first. */
148
149   default:
150       {
151       int diff_hour, diff_min;
152       struct tm local;
153       struct tm * lp = &local;
154       memcpy(lp, t, sizeof(struct tm));
155
156       if (f.timestamps_utc)
157         diff_hour = diff_min = 0;
158       else
159         {
160         struct tm * gmt = gmtime(&now.tv_sec);
161
162         if (local.tm_sec == gmt->tm_sec)        /* usual case */
163           {
164           diff_min = 60 * (local.tm_hour - gmt->tm_hour)
165                     + local.tm_min - gmt->tm_min;
166           if (local.tm_year != gmt->tm_year)
167             diff_min += (local.tm_year > gmt->tm_year) ? 1440 : -1440;
168           else if (local.tm_yday != gmt->tm_yday)
169             diff_min += (local.tm_yday > gmt->tm_yday) ? 1440 : -1440;
170           diff_hour = diff_min/60;
171           diff_min  = abs(diff_min - diff_hour*60);
172           }
173         else                                    /* subminute offset, eg. TAI */
174           {
175           lp = gmt;                             /* pretend we're in UTC */
176           diff_min = diff_hour = 0;
177           }
178         }
179
180       switch(type)
181         {
182         case tod_log_zone:          /* Format used in logging with timezone */
183 #ifndef COMPILE_UTILITY
184           if (LOGGING(millisec))
185             (void) snprintf(CS timebuf, sizeof(timebuf),
186               "%04u-%02u-%02u %02u:%02u:%02u.%03u %+03d%02d",
187               1900 + (uint)lp->tm_year, 1 + (uint)lp->tm_mon, (uint)lp->tm_mday,
188               (uint)lp->tm_hour, (uint)lp->tm_min, (uint)lp->tm_sec, (uint)(now.tv_usec/1000),
189               diff_hour, diff_min);
190           else
191 #endif
192             (void) snprintf(CS timebuf, sizeof(timebuf),
193               "%04u-%02u-%02u %02u:%02u:%02u %+03d%02d",
194               1900 + (uint)lp->tm_year, 1 + (uint)lp->tm_mon, (uint)lp->tm_mday,
195               (uint)lp->tm_hour, (uint)lp->tm_min, (uint)lp->tm_sec,
196               diff_hour, diff_min);
197           break;
198
199         case tod_zone:              /* Just the timezone offset */
200           (void) snprintf(CS timebuf, sizeof(timebuf), "%+03d%02d", diff_hour, diff_min);
201           break;
202
203         /* tod_mbx: format used in MBX mailboxes - subtly different to tod_full */
204
205 #ifdef SUPPORT_MBX
206         case tod_mbx:
207           {
208           int len = snprintf(CS timebuf, sizeof(timebuf),
209             "%02u-", (uint)lp->tm_mday);
210           len += Ustrftime(timebuf + len, sizeof(timebuf) - len,
211             "%b-%Y %H:%M:%S", lp);
212           (void) snprintf(CS timebuf + len, sizeof(timebuf)-len,
213             " %+03d%02d", diff_hour, diff_min);
214           }
215           break;
216 #endif
217
218         /* tod_full: format used in Received: headers (use as default just in case
219         called with a junk type value) */
220
221         default:
222           {
223           int len = Ustrftime(timebuf, sizeof(timebuf), "%a, ", lp);
224           len += snprintf(CS timebuf + len, sizeof(timebuf)-len,
225             "%02u ", (uint)lp->tm_mday);
226           len += Ustrftime(timebuf + len, sizeof(timebuf) - len,
227             "%b %Y %H:%M:%S", lp);
228           (void) snprintf(CS timebuf + len, sizeof(timebuf)-len,
229             " %+03d%02d", diff_hour, diff_min);
230           }
231           break;
232         }
233       }
234     break;
235   }
236
237 return timebuf;
238 }
239
240 /* End of tod.c */