1 /* Copyright (c) 2001, Matej Pfajfar.
2 * Copyright (c) 2001-2004, Roger Dingledine.
3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4 * Copyright (c) 2007-2021, The Tor Project, Inc. */
5 /* See LICENSE for licensing information */
10 * \brief Encode and decode time in various formats.
12 * This module is higher-level than the conversion functions in "wallclock",
13 * and handles a larger variety of types. It converts between different time
14 * formats, and encodes and decodes them from strings.
16 #define TIME_FMT_PRIVATE
18 #include "lib/encoding/time_fmt.h"
19 #include "lib/log/log.h"
20 #include "lib/log/escape.h"
21 #include "lib/log/util_bug.h"
22 #include "lib/malloc/malloc.h"
23 #include "lib/string/printf.h"
24 #include "lib/string/scanf.h"
25 #include "lib/wallclock/time_to_tm.h"
31 #ifdef HAVE_SYS_TIME_H
36 /* For struct timeval */
40 /** As localtime_r, but defined for platforms that don't have it:
42 * Convert *<b>timep</b> to a struct tm in local time, and store the value in
43 * *<b>result</b>. Return the result on success, or NULL on failure.
45 * Treat malformatted inputs localtime outputs as a BUG.
48 tor_localtime_r(const time_t *timep
, struct tm
*result
)
51 struct tm
*r
= tor_localtime_r_msg(timep
, result
, &err
);
53 log_warn(LD_BUG
, "%s", err
);
59 /** As gmtime_r, but defined for platforms that don't have it:
61 * Convert *<b>timep</b> to a struct tm in UTC, and store the value in
62 * *<b>result</b>. Return the result on success, or NULL on failure.
64 * Treat malformatted inputs or gmtime outputs as a BUG.
67 tor_gmtime_r(const time_t *timep
, struct tm
*result
)
70 struct tm
*r
= tor_gmtime_r_msg(timep
, result
, &err
);
72 log_warn(LD_BUG
, "%s", err
);
78 /** Yield true iff <b>y</b> is a leap-year. */
79 #define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400)))
80 /** Helper: Return the number of leap-days between Jan 1, y1 and Jan 1, y2. */
82 n_leapdays(int year1
, int year2
)
86 return (year2
/4 - year1
/4) - (year2
/100 - year1
/100)
87 + (year2
/400 - year1
/400);
89 /** Number of days per month in non-leap year; used by tor_timegm and
90 * parse_rfc1123_time. */
91 static const int days_per_month
[] =
92 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
94 /** Compute a time_t given a struct tm. The result is given in UTC, and
95 * does not account for leap seconds. Return 0 on success, -1 on failure.
97 ATTR_UNUSED STATIC
int
98 tor_timegm_impl(const struct tm
*tm
, time_t *time_out
)
100 /* This is a pretty ironclad timegm implementation, snarfed from Python2.2.
101 * It's way more brute-force than fiddling with tzset().
103 * We use int64_t rather than time_t to avoid overflow on multiplication on
104 * platforms with 32-bit time_t. Since year is clipped to INT32_MAX, and
105 * since 365 * 24 * 60 * 60 is approximately 31 million, it's not possible
106 * for INT32_MAX years to overflow int64_t when converted to seconds. */
107 int64_t year
, days
, hours
, minutes
, seconds
;
108 int i
, invalid_year
, dpm
;
110 /* Initialize time_out to 0 for now, to avoid bad usage in case this function
111 fails and the caller ignores the return value. */
112 tor_assert(time_out
);
115 /* avoid int overflow on addition */
116 if (tm
->tm_year
< INT32_MAX
-1900) {
117 year
= tm
->tm_year
+ 1900;
122 invalid_year
= (year
< 1970 || tm
->tm_year
>= INT32_MAX
-1900);
124 if (tm
->tm_mon
>= 0 && tm
->tm_mon
<= 11) {
125 dpm
= days_per_month
[tm
->tm_mon
];
126 if (tm
->tm_mon
== 1 && !invalid_year
&& IS_LEAPYEAR(tm
->tm_year
)) {
130 /* invalid month - default to 0 days per month */
135 tm
->tm_mon
< 0 || tm
->tm_mon
> 11 ||
136 tm
->tm_mday
< 1 || tm
->tm_mday
> dpm
||
137 tm
->tm_hour
< 0 || tm
->tm_hour
> 23 ||
138 tm
->tm_min
< 0 || tm
->tm_min
> 59 ||
139 tm
->tm_sec
< 0 || tm
->tm_sec
> 60) {
140 log_warn(LD_BUG
, "Out-of-range argument to tor_timegm");
143 days
= 365 * (year
-1970) + n_leapdays(1970,(int)year
);
144 for (i
= 0; i
< tm
->tm_mon
; ++i
)
145 days
+= days_per_month
[i
];
146 if (tm
->tm_mon
> 1 && IS_LEAPYEAR(year
))
148 days
+= tm
->tm_mday
- 1;
149 hours
= days
*24 + tm
->tm_hour
;
151 minutes
= hours
*60 + tm
->tm_min
;
152 seconds
= minutes
*60 + tm
->tm_sec
;
153 /* Check that "seconds" will fit in a time_t. On platforms where time_t is
154 * 32-bit, this check will fail for dates in and after 2038.
156 * We already know that "seconds" can't be negative because "year" >= 1970 */
157 #if SIZEOF_TIME_T < 8
158 if (seconds
< TIME_MIN
|| seconds
> TIME_MAX
) {
159 log_warn(LD_BUG
, "Result does not fit in tor_timegm");
162 #endif /* SIZEOF_TIME_T < 8 */
163 *time_out
= (time_t)seconds
;
167 /** Compute a time_t given a struct tm. The result here should be an inverse
168 * of the system's gmtime() function. Return 0 on success, -1 on failure.
171 tor_timegm(const struct tm
*tm
, time_t *time_out
)
174 /* If the system gives us a timegm(), use it: if the system's time_t
175 * includes leap seconds, then we can hope that its timegm() knows too.
177 * https://k5wiki.kerberos.org/wiki/Leap_second_handling says the in
178 * general we can rely on any system with leap seconds also having a
179 * timegm implementation. Let's hope it's right!
181 time_t result
= timegm((struct tm
*) tm
);
183 log_warn(LD_BUG
, "timegm() could not convert time: %s", strerror(errno
));
191 /* The system doesn't have timegm; we'll have to use our own. */
192 return tor_timegm_impl(tm
, time_out
);
196 /* strftime is locale-specific, so we need to replace those parts */
198 /** A c-locale array of 3-letter names of weekdays, starting with Sun. */
199 static const char *WEEKDAY_NAMES
[] =
200 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
201 /** A c-locale array of 3-letter names of months, starting with Jan. */
202 static const char *MONTH_NAMES
[] =
203 { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
204 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
206 /** Set <b>buf</b> to the RFC1123 encoding of the UTC value of <b>t</b>.
207 * The buffer must be at least RFC1123_TIME_LEN+1 bytes long.
209 * (RFC1123 format is "Fri, 29 Sep 2006 15:54:20 GMT". Note the "GMT"
210 * rather than "UTC".)
213 format_rfc1123_time(char *buf
, time_t t
)
217 tor_gmtime_r(&t
, &tm
);
219 strftime(buf
, RFC1123_TIME_LEN
+1, "___, %d ___ %Y %H:%M:%S GMT", &tm
);
220 tor_assert(tm
.tm_wday
>= 0);
221 tor_assert(tm
.tm_wday
<= 6);
222 memcpy(buf
, WEEKDAY_NAMES
[tm
.tm_wday
], 3);
223 tor_assert(tm
.tm_mon
>= 0);
224 tor_assert(tm
.tm_mon
<= 11);
225 memcpy(buf
+8, MONTH_NAMES
[tm
.tm_mon
], 3);
228 /** Parse the (a subset of) the RFC1123 encoding of some time (in UTC) from
229 * <b>buf</b>, and store the result in *<b>t</b>.
231 * Note that we only accept the subset generated by format_rfc1123_time above,
232 * not the full range of formats suggested by RFC 1123.
234 * Return 0 on success, -1 on failure.
237 parse_rfc1123_time(const char *buf
, time_t *t
)
242 int i
, m
, invalid_year
;
243 unsigned tm_mday
, tm_year
, tm_hour
, tm_min
, tm_sec
;
246 if (strlen(buf
) != RFC1123_TIME_LEN
)
248 memset(&tm
, 0, sizeof(tm
));
249 if (tor_sscanf(buf
, "%3s, %2u %3s %u %2u:%2u:%2u GMT", weekday
,
250 &tm_mday
, month
, &tm_year
, &tm_hour
,
251 &tm_min
, &tm_sec
) < 7) {
252 char *esc
= esc_for_log(buf
);
253 log_warn(LD_GENERAL
, "Got invalid RFC1123 time %s", esc
);
259 for (i
= 0; i
< 12; ++i
) {
260 if (!strcmp(month
, MONTH_NAMES
[i
])) {
266 char *esc
= esc_for_log(buf
);
267 log_warn(LD_GENERAL
, "Got invalid RFC1123 time %s: No such month", esc
);
273 invalid_year
= (tm_year
>= INT32_MAX
|| tm_year
< 1970);
274 tor_assert(m
>= 0 && m
<= 11);
275 dpm
= days_per_month
[m
];
276 if (m
== 1 && !invalid_year
&& IS_LEAPYEAR(tm_year
)) {
280 if (invalid_year
|| tm_mday
< 1 || tm_mday
> dpm
||
281 tm_hour
> 23 || tm_min
> 59 || tm_sec
> 60) {
282 char *esc
= esc_for_log(buf
);
283 log_warn(LD_GENERAL
, "Got invalid RFC1123 time %s", esc
);
287 tm
.tm_mday
= (int)tm_mday
;
288 tm
.tm_year
= (int)tm_year
;
289 tm
.tm_hour
= (int)tm_hour
;
290 tm
.tm_min
= (int)tm_min
;
291 tm
.tm_sec
= (int)tm_sec
;
293 if (tm
.tm_year
< 1970) {
295 * XXXX I think this is dead code; we already checked for
296 * invalid_year above. */
297 tor_assert_nonfatal_unreached();
298 char *esc
= esc_for_log(buf
);
300 "Got invalid RFC1123 time %s. (Before 1970)", esc
);
307 return tor_timegm(&tm
, t
);
310 /** Set <b>buf</b> to the ISO8601 encoding of the local value of <b>t</b>.
311 * The buffer must be at least ISO_TIME_LEN+1 bytes long.
313 * (ISO8601 format is 2006-10-29 10:57:20)
316 format_local_iso_time(char *buf
, time_t t
)
319 strftime(buf
, ISO_TIME_LEN
+1, "%Y-%m-%d %H:%M:%S", tor_localtime_r(&t
, &tm
));
322 /** Set <b>buf</b> to the ISO8601 encoding of the GMT value of <b>t</b>.
323 * The buffer must be at least ISO_TIME_LEN+1 bytes long.
326 format_iso_time(char *buf
, time_t t
)
329 strftime(buf
, ISO_TIME_LEN
+1, "%Y-%m-%d %H:%M:%S", tor_gmtime_r(&t
, &tm
));
332 /** As format_local_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
333 * embedding an internal space. */
335 format_local_iso_time_nospace(char *buf
, time_t t
)
337 format_local_iso_time(buf
, t
);
341 /** As format_iso_time, but use the yyyy-mm-ddThh:mm:ss format to avoid
342 * embedding an internal space. */
344 format_iso_time_nospace(char *buf
, time_t t
)
346 format_iso_time(buf
, t
);
350 /** As format_iso_time_nospace, but include microseconds in decimal
351 * fixed-point format. Requires that buf be at least ISO_TIME_USEC_LEN+1
354 format_iso_time_nospace_usec(char *buf
, const struct timeval
*tv
)
357 format_iso_time_nospace(buf
, (time_t)tv
->tv_sec
);
358 tor_snprintf(buf
+ISO_TIME_LEN
, 8, ".%06d", (int)tv
->tv_usec
);
361 /** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
362 * parse it and store its value in *<b>t</b>. Return 0 on success, -1 on
363 * failure. Ignore extraneous stuff in <b>cp</b> after the end of the time
364 * string, unless <b>strict</b> is set. If <b>nospace</b> is set,
365 * expect the YYYY-MM-DDTHH:MM:SS format. */
367 parse_iso_time_(const char *cp
, time_t *t
, int strict
, int nospace
)
370 unsigned int year
=0, month
=0, day
=0, hour
=0, minute
=0, second
=0;
372 char extra_char
, separator_char
;
373 n_fields
= tor_sscanf(cp
, "%u-%2u-%2u%c%2u:%2u:%2u%c",
376 &hour
, &minute
, &second
, &extra_char
);
377 if (strict
? (n_fields
!= 7) : (n_fields
< 7)) {
378 char *esc
= esc_for_log(cp
);
379 log_warn(LD_GENERAL
, "ISO time %s was unparseable", esc
);
383 if (separator_char
!= (nospace
? 'T' : ' ')) {
384 char *esc
= esc_for_log(cp
);
385 log_warn(LD_GENERAL
, "ISO time %s was unparseable", esc
);
389 if (year
< 1970 || month
< 1 || month
> 12 || day
< 1 || day
> 31 ||
390 hour
> 23 || minute
> 59 || second
> 60 || year
>= INT32_MAX
) {
391 char *esc
= esc_for_log(cp
);
392 log_warn(LD_GENERAL
, "ISO time %s was nonsensical", esc
);
396 st_tm
.tm_year
= (int)year
-1900;
397 st_tm
.tm_mon
= month
-1;
399 st_tm
.tm_hour
= hour
;
400 st_tm
.tm_min
= minute
;
401 st_tm
.tm_sec
= second
;
402 st_tm
.tm_wday
= 0; /* Should be ignored. */
404 if (st_tm
.tm_year
< 70) {
406 * XXXX I think this is dead code; we already checked for
407 * year < 1970 above. */
408 tor_assert_nonfatal_unreached();
409 char *esc
= esc_for_log(cp
);
410 log_warn(LD_GENERAL
, "Got invalid ISO time %s. (Before 1970)", esc
);
415 return tor_timegm(&st_tm
, t
);
418 /** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
419 * parse it and store its value in *<b>t</b>. Return 0 on success, -1 on
420 * failure. Reject the string if any characters are present after the time.
423 parse_iso_time(const char *cp
, time_t *t
)
425 return parse_iso_time_(cp
, t
, 1, 0);
429 * As parse_iso_time, but parses a time encoded by format_iso_time_nospace().
432 parse_iso_time_nospace(const char *cp
, time_t *t
)
434 return parse_iso_time_(cp
, t
, 1, 1);
437 /** Given a <b>date</b> in one of the three formats allowed by HTTP (ugh),
438 * parse it into <b>tm</b>. Return 0 on success, negative on failure. */
440 parse_http_time(const char *date
, struct tm
*tm
)
446 unsigned tm_mday
, tm_year
, tm_hour
, tm_min
, tm_sec
;
449 memset(tm
, 0, sizeof(*tm
));
451 /* First, try RFC1123 or RFC850 format: skip the weekday. */
452 if ((cp
= strchr(date
, ','))) {
457 if (tor_sscanf(cp
, "%2u %3s %4u %2u:%2u:%2u GMT",
458 &tm_mday
, month
, &tm_year
,
459 &tm_hour
, &tm_min
, &tm_sec
) == 6) {
462 } else if (tor_sscanf(cp
, "%2u-%3s-%2u %2u:%2u:%2u GMT",
463 &tm_mday
, month
, &tm_year
,
464 &tm_hour
, &tm_min
, &tm_sec
) == 6) {
470 /* No comma; possibly asctime() format. */
471 if (tor_sscanf(date
, "%3s %3s %2u %2u:%2u:%2u %4u",
472 wkday
, month
, &tm_mday
,
473 &tm_hour
, &tm_min
, &tm_sec
, &tm_year
) == 7) {
479 tm
->tm_mday
= (int)tm_mday
;
480 tm
->tm_year
= (int)tm_year
;
481 tm
->tm_hour
= (int)tm_hour
;
482 tm
->tm_min
= (int)tm_min
;
483 tm
->tm_sec
= (int)tm_sec
;
484 tm
->tm_wday
= 0; /* Leave this unset. */
487 /* Okay, now decode the month. */
488 /* set tm->tm_mon to dummy value so the check below fails. */
490 for (i
= 0; i
< 12; ++i
) {
491 if (!strcasecmp(MONTH_NAMES
[i
], month
)) {
496 if (tm
->tm_year
< 0 ||
497 tm
->tm_mon
< 0 || tm
->tm_mon
> 11 ||
498 tm
->tm_mday
< 1 || tm
->tm_mday
> 31 ||
499 tm
->tm_hour
< 0 || tm
->tm_hour
> 23 ||
500 tm
->tm_min
< 0 || tm
->tm_min
> 59 ||
501 tm
->tm_sec
< 0 || tm
->tm_sec
> 60)
502 return -1; /* Out of range, or bad month. */
507 /** Given an <b>interval</b> in seconds, try to write it to the
508 * <b>out_len</b>-byte buffer in <b>out</b> in a human-readable form.
509 * Returns a non-negative integer on success, -1 on failure.
512 format_time_interval(char *out
, size_t out_len
, long interval
)
514 /* We only report seconds if there's no hours. */
515 long sec
= 0, min
= 0, hour
= 0, day
= 0;
517 /* -LONG_MIN is LONG_MAX + 1, which causes signed overflow */
518 if (interval
< -LONG_MAX
)
520 else if (interval
< 0)
521 interval
= -interval
;
523 if (interval
>= 86400) {
524 day
= interval
/ 86400;
527 if (interval
>= 3600) {
528 hour
= interval
/ 3600;
531 if (interval
>= 60) {
538 return tor_snprintf(out
, out_len
, "%ld days, %ld hours, %ld minutes",
541 return tor_snprintf(out
, out_len
, "%ld hours, %ld minutes", hour
, min
);
543 return tor_snprintf(out
, out_len
, "%ld minutes, %ld seconds", min
, sec
);
545 return tor_snprintf(out
, out_len
, "%ld seconds", sec
);