]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/time-util.c
hexdecoct: make unbase64mem and unhexmem always use SIZE_MAX
[thirdparty/systemd.git] / src / basic / time-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
9a98c7a1 2
279f52a1 3#include <ctype.h>
11c3a366
TA
4#include <errno.h>
5#include <limits.h>
6#include <stdlib.h>
48d26c01 7#include <sys/mman.h>
11c3a366 8#include <sys/time.h>
77ff2de9 9#include <sys/timerfd.h>
11c3a366
TA
10#include <sys/types.h>
11#include <unistd.h>
9a98c7a1 12
b5efdb8a 13#include "alloc-util.h"
3ffd4af2 14#include "fd-util.h"
0d39fa9c 15#include "fileio.h"
f4f15635 16#include "fs-util.h"
a2932d51 17#include "io-util.h"
11c3a366
TA
18#include "log.h"
19#include "macro.h"
5545f336 20#include "missing_threads.h"
3c94e504 21#include "missing_timerfd.h"
93cc7779
TA
22#include "parse-util.h"
23#include "path-util.h"
dccca82b 24#include "process-util.h"
a2932d51 25#include "stat-util.h"
7b3eb5c9 26#include "string-table.h"
07630cea 27#include "string-util.h"
75683450 28#include "strv.h"
07630cea 29#include "time-util.h"
9a98c7a1 30
32c1f5a5
LP
31static clockid_t map_clock_id(clockid_t c) {
32
8b105ec8
ZJS
33 /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus,
34 * clock_gettime() will fail for them. Since they are essentially the same as their non-ALARM
35 * pendants (their only difference is when timers are set on them), let's just map them
36 * accordingly. This way, we can get the correct time even on those archs. */
32c1f5a5
LP
37
38 switch (c) {
39
40 case CLOCK_BOOTTIME_ALARM:
bdf19f8f 41 return CLOCK_BOOTTIME;
32c1f5a5
LP
42
43 case CLOCK_REALTIME_ALARM:
44 return CLOCK_REALTIME;
45
46 default:
47 return c;
48 }
49}
50
9a98c7a1
LP
51usec_t now(clockid_t clock_id) {
52 struct timespec ts;
53
32c1f5a5 54 assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
9a98c7a1
LP
55
56 return timespec_load(&ts);
57}
58
45d7a8bb
LP
59nsec_t now_nsec(clockid_t clock_id) {
60 struct timespec ts;
61
32c1f5a5 62 assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
45d7a8bb
LP
63
64 return timespec_load_nsec(&ts);
65}
66
fa5a0251 67dual_timestamp* dual_timestamp_now(dual_timestamp *ts) {
9a98c7a1
LP
68 assert(ts);
69
70 ts->realtime = now(CLOCK_REALTIME);
71 ts->monotonic = now(CLOCK_MONOTONIC);
72
73 return ts;
74}
75
fa5a0251 76triple_timestamp* triple_timestamp_now(triple_timestamp *ts) {
fe624c4c
LP
77 assert(ts);
78
79 ts->realtime = now(CLOCK_REALTIME);
80 ts->monotonic = now(CLOCK_MONOTONIC);
ba4e0427 81 ts->boottime = now(CLOCK_BOOTTIME);
fe624c4c
LP
82
83 return ts;
84}
85
7c0eb30e
LP
86static usec_t map_clock_usec_internal(usec_t from, usec_t from_base, usec_t to_base) {
87
88 /* Maps the time 'from' between two clocks, based on a common reference point where the first clock
89 * is at 'from_base' and the second clock at 'to_base'. Basically calculates:
90 *
91 * from - from_base + to_base
92 *
93 * But takes care of overflows/underflows and avoids signed operations. */
94
95 if (from >= from_base) { /* In the future */
96 usec_t delta = from - from_base;
97
98 if (to_base >= USEC_INFINITY - delta) /* overflow? */
99 return USEC_INFINITY;
100
101 return to_base + delta;
102
103 } else { /* In the past */
104 usec_t delta = from_base - from;
105
106 if (to_base <= delta) /* underflow? */
107 return 0;
108
109 return to_base - delta;
110 }
111}
112
113usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock) {
114
115 /* Try to avoid any inaccuracy needlessly added in case we convert from effectively the same clock
116 * onto itself */
117 if (map_clock_id(from_clock) == map_clock_id(to_clock))
118 return from;
119
120 /* Keep infinity as is */
121 if (from == USEC_INFINITY)
122 return from;
123
124 return map_clock_usec_internal(from, now(from_clock), now(to_clock));
125}
126
9a98c7a1 127dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
9a98c7a1
LP
128 assert(ts);
129
0da36375 130 if (!timestamp_is_set(u)) {
75a5f1d8 131 ts->realtime = ts->monotonic = u;
cae0c5e0
LP
132 return ts;
133 }
134
9a98c7a1 135 ts->realtime = u;
7c0eb30e 136 ts->monotonic = map_clock_usec(u, CLOCK_REALTIME, CLOCK_MONOTONIC);
9a98c7a1
LP
137 return ts;
138}
139
fe624c4c 140triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
7c0eb30e 141 usec_t nowr;
fe624c4c
LP
142
143 assert(ts);
144
0da36375 145 if (!timestamp_is_set(u)) {
fe624c4c
LP
146 ts->realtime = ts->monotonic = ts->boottime = u;
147 return ts;
148 }
149
7c0eb30e
LP
150 nowr = now(CLOCK_REALTIME);
151
fe624c4c 152 ts->realtime = u;
7c0eb30e 153 ts->monotonic = map_clock_usec_internal(u, nowr, now(CLOCK_MONOTONIC));
ba4e0427 154 ts->boottime = map_clock_usec_internal(u, nowr, now(CLOCK_BOOTTIME));
fe624c4c
LP
155
156 return ts;
157}
158
7cd07551
YW
159triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u) {
160 usec_t nowb;
161
162 assert(ts);
163
164 if (u == USEC_INFINITY) {
165 ts->realtime = ts->monotonic = ts->boottime = u;
166 return ts;
167 }
168
169 nowb = now(CLOCK_BOOTTIME);
170
171 ts->boottime = u;
172 ts->monotonic = map_clock_usec_internal(u, nowb, now(CLOCK_MONOTONIC));
173 ts->realtime = map_clock_usec_internal(u, nowb, now(CLOCK_REALTIME));
174
175 return ts;
176}
177
cae0c5e0 178dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
cae0c5e0
LP
179 assert(ts);
180
3a43da28
KS
181 if (u == USEC_INFINITY) {
182 ts->realtime = ts->monotonic = USEC_INFINITY;
cae0c5e0
LP
183 return ts;
184 }
185
186 ts->monotonic = u;
7c0eb30e 187 ts->realtime = map_clock_usec(u, CLOCK_MONOTONIC, CLOCK_REALTIME);
cae0c5e0
LP
188 return ts;
189}
190
ba4e0427 191dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
7c0eb30e 192 usec_t nowm;
fbe55073 193
dff3bddc
YW
194 assert(ts);
195
fbe55073
LP
196 if (u == USEC_INFINITY) {
197 ts->realtime = ts->monotonic = USEC_INFINITY;
198 return ts;
199 }
fbe55073 200
ba4e0427
LP
201 nowm = now(CLOCK_BOOTTIME);
202 ts->monotonic = map_clock_usec_internal(u, nowm, now(CLOCK_MONOTONIC));
7c0eb30e 203 ts->realtime = map_clock_usec_internal(u, nowm, now(CLOCK_REALTIME));
fbe55073
LP
204 return ts;
205}
206
fe624c4c 207usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
dff3bddc 208 assert(ts);
fe624c4c
LP
209
210 switch (clock) {
211
212 case CLOCK_REALTIME:
213 case CLOCK_REALTIME_ALARM:
214 return ts->realtime;
215
216 case CLOCK_MONOTONIC:
217 return ts->monotonic;
218
219 case CLOCK_BOOTTIME:
220 case CLOCK_BOOTTIME_ALARM:
221 return ts->boottime;
222
223 default:
224 return USEC_INFINITY;
225 }
226}
227
9a98c7a1
LP
228usec_t timespec_load(const struct timespec *ts) {
229 assert(ts);
230
c477ff14 231 if (ts->tv_sec < 0 || ts->tv_nsec < 0)
3a43da28 232 return USEC_INFINITY;
9a98c7a1
LP
233
234 if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
3a43da28 235 return USEC_INFINITY;
9a98c7a1
LP
236
237 return
238 (usec_t) ts->tv_sec * USEC_PER_SEC +
239 (usec_t) ts->tv_nsec / NSEC_PER_USEC;
240}
241
3a730176 242nsec_t timespec_load_nsec(const struct timespec *ts) {
45d7a8bb
LP
243 assert(ts);
244
c477ff14 245 if (ts->tv_sec < 0 || ts->tv_nsec < 0)
45d7a8bb
LP
246 return NSEC_INFINITY;
247
a2daa2f0
ZJS
248 if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
249 return NSEC_INFINITY;
250
251 return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
45d7a8bb
LP
252}
253
7464953f 254struct timespec *timespec_store(struct timespec *ts, usec_t u) {
9a98c7a1
LP
255 assert(ts);
256
f977849c 257 if (u == USEC_INFINITY ||
d201d908 258 u / USEC_PER_SEC >= TIME_T_MAX) {
9a98c7a1 259 ts->tv_sec = (time_t) -1;
cd1361e2 260 ts->tv_nsec = -1L;
9a98c7a1
LP
261 return ts;
262 }
263
264 ts->tv_sec = (time_t) (u / USEC_PER_SEC);
cd1361e2
LP
265 ts->tv_nsec = (long) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
266
267 return ts;
268}
269
7464953f 270struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) {
cd1361e2
LP
271 assert(ts);
272
273 if (n == NSEC_INFINITY ||
274 n / NSEC_PER_SEC >= TIME_T_MAX) {
275 ts->tv_sec = (time_t) -1;
276 ts->tv_nsec = -1L;
277 return ts;
278 }
279
280 ts->tv_sec = (time_t) (n / NSEC_PER_SEC);
281 ts->tv_nsec = (long) (n % NSEC_PER_SEC);
9a98c7a1
LP
282
283 return ts;
284}
285
286usec_t timeval_load(const struct timeval *tv) {
287 assert(tv);
288
c477ff14 289 if (tv->tv_sec < 0 || tv->tv_usec < 0)
3a43da28 290 return USEC_INFINITY;
9a98c7a1
LP
291
292 if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
3a43da28 293 return USEC_INFINITY;
9a98c7a1
LP
294
295 return
296 (usec_t) tv->tv_sec * USEC_PER_SEC +
297 (usec_t) tv->tv_usec;
298}
299
300struct timeval *timeval_store(struct timeval *tv, usec_t u) {
301 assert(tv);
302
68bdd2d2 303 if (u == USEC_INFINITY ||
f977849c 304 u / USEC_PER_SEC > TIME_T_MAX) {
9a98c7a1
LP
305 tv->tv_sec = (time_t) -1;
306 tv->tv_usec = (suseconds_t) -1;
4d89874a
ZJS
307 } else {
308 tv->tv_sec = (time_t) (u / USEC_PER_SEC);
309 tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
9a98c7a1
LP
310 }
311
9a98c7a1
LP
312 return tv;
313}
314
7b3eb5c9 315char *format_timestamp_style(
21b3a0fc
LP
316 char *buf,
317 size_t l,
318 usec_t t,
7b3eb5c9 319 TimestampStyle style) {
21b3a0fc 320
8b105ec8
ZJS
321 /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that
322 * our generated timestamps may be parsed with parse_timestamp(), and always read the same. */
21b3a0fc
LP
323 static const char * const weekdays[] = {
324 [0] = "Sun",
325 [1] = "Mon",
326 [2] = "Tue",
327 [3] = "Wed",
328 [4] = "Thu",
329 [5] = "Fri",
330 [6] = "Sat",
331 };
332
9a98c7a1 333 struct tm tm;
060c9c02 334 bool utc, us;
9a98c7a1 335 time_t sec;
21b3a0fc 336 size_t n;
9a98c7a1
LP
337
338 assert(buf);
060c9c02
LP
339 assert(style >= 0);
340 assert(style < _TIMESTAMP_STYLE_MAX);
9a98c7a1 341
0da36375 342 if (!timestamp_is_set(t))
21b3a0fc
LP
343 return NULL; /* Timestamp is unset */
344
ed4a5b43 345 if (style == TIMESTAMP_UNIX) {
61d03c54
LP
346 if (l < (size_t) (1 + 1 + 1))
347 return NULL; /* not enough space for even the shortest of forms */
ed4a5b43 348
e503019b 349 return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down μs → s */
ed4a5b43
FS
350 }
351
64f3419e 352 utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE);
3703e5d0
LP
353 us = IN_SET(style, TIMESTAMP_US, TIMESTAMP_US_UTC);
354
64f3419e
LP
355 if (l < (size_t) (3 + /* week day */
356 1 + 10 + /* space and date */
357 style == TIMESTAMP_DATE ? 0 :
358 (1 + 8 + /* space and time */
359 (us ? 1 + 6 : 0) + /* "." and microsecond part */
360 1 + (utc ? 3 : 1)) + /* space and shortest possible zone */
3703e5d0
LP
361 1))
362 return NULL; /* Not enough space even for the shortest form. */
363
1bb4b028 364 /* Let's not format times with years > 9999 */
d3d28024 365 if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
4361678c
LP
366 static const char* const xxx[_TIMESTAMP_STYLE_MAX] = {
367 [TIMESTAMP_PRETTY] = "--- XXXX-XX-XX XX:XX:XX",
368 [TIMESTAMP_US] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX",
369 [TIMESTAMP_UTC] = "--- XXXX-XX-XX XX:XX:XX UTC",
370 [TIMESTAMP_US_UTC] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC",
64f3419e 371 [TIMESTAMP_DATE] = "--- XXXX-XX-XX",
4361678c
LP
372 };
373
374 assert(l >= strlen(xxx[style]) + 1);
375 return strcpy(buf, xxx[style]);
d3d28024 376 }
1bb4b028 377
21b3a0fc 378 sec = (time_t) (t / USEC_PER_SEC); /* Round down */
21b3a0fc
LP
379
380 if (!localtime_or_gmtime_r(&sec, &tm, utc))
9a98c7a1
LP
381 return NULL;
382
21b3a0fc
LP
383 /* Start with the week day */
384 assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
385 memcpy(buf, weekdays[tm.tm_wday], 4);
9a98c7a1 386
64f3419e
LP
387 if (style == TIMESTAMP_DATE) {
388 /* Special format string if only date should be shown. */
389 if (strftime(buf + 3, l - 3, " %Y-%m-%d", &tm) <= 0)
390 return NULL; /* Doesn't fit */
391
392 return buf;
393 }
394
21b3a0fc
LP
395 /* Add the main components */
396 if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
397 return NULL; /* Doesn't fit */
0056086a 398
21b3a0fc 399 /* Append the microseconds part, if that's requested */
0056086a 400 if (us) {
21b3a0fc
LP
401 n = strlen(buf);
402 if (n + 8 > l)
403 return NULL; /* Microseconds part doesn't fit. */
404
70887c5f 405 sprintf(buf + n, ".%06"PRI_USEC, t % USEC_PER_SEC);
21b3a0fc
LP
406 }
407
408 /* Append the timezone */
409 n = strlen(buf);
410 if (utc) {
8b105ec8
ZJS
411 /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r()
412 * normally uses the obsolete "GMT" instead. */
21b3a0fc
LP
413 if (n + 5 > l)
414 return NULL; /* "UTC" doesn't fit. */
415
416 strcpy(buf + n, " UTC");
417
418 } else if (!isempty(tm.tm_zone)) {
419 size_t tn;
420
421 /* An explicit timezone is specified, let's use it, if it fits */
422 tn = strlen(tm.tm_zone);
423 if (n + 1 + tn + 1 > l) {
424 /* The full time zone does not fit in. Yuck. */
425
426 if (n + 1 + _POSIX_TZNAME_MAX + 1 > l)
8b105ec8
ZJS
427 return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that
428 * case, complain that it doesn't fit. */
429
430 /* So the time zone doesn't fit in fully, but the caller passed enough space for the
431 * POSIX minimum time zone length. In this case suppress the timezone entirely, in
432 * order not to dump an overly long, hard to read string on the user. This should be
433 * safe, because the user will assume the local timezone anyway if none is shown. And
434 * so does parse_timestamp(). */
21b3a0fc
LP
435 } else {
436 buf[n++] = ' ';
437 strcpy(buf + n, tm.tm_zone);
438 }
0056086a 439 }
9a98c7a1
LP
440
441 return buf;
442}
443
d65c289f 444char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) {
1fcf71f5 445 const char *s;
9a98c7a1
LP
446 usec_t n, d;
447
dff3bddc
YW
448 assert(buf);
449
0da36375 450 if (!timestamp_is_set(t))
9a98c7a1
LP
451 return NULL;
452
d65c289f 453 n = now(clock);
1fcf71f5
LP
454 if (n > t) {
455 d = n - t;
d5e6f36c 456 s = " ago";
1fcf71f5
LP
457 } else {
458 d = t - n;
d5e6f36c 459 s = implicit_left ? "" : " left";
1fcf71f5 460 }
9a98c7a1 461
45eb4d22
AW
462 if (d >= USEC_PER_YEAR) {
463 usec_t years = d / USEC_PER_YEAR;
464 usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH;
f7581717 465
d5e6f36c 466 (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
121ed16c
LB
467 years,
468 years == 1 ? "year" : "years",
469 months,
470 months == 1 ? "month" : "months",
471 s);
f7581717 472 } else if (d >= USEC_PER_MONTH) {
45eb4d22
AW
473 usec_t months = d / USEC_PER_MONTH;
474 usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY;
f7581717 475
d5e6f36c 476 (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
121ed16c
LB
477 months,
478 months == 1 ? "month" : "months",
479 days,
480 days == 1 ? "day" : "days",
481 s);
f7581717 482 } else if (d >= USEC_PER_WEEK) {
45eb4d22
AW
483 usec_t weeks = d / USEC_PER_WEEK;
484 usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY;
f7581717 485
d5e6f36c 486 (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
121ed16c
LB
487 weeks,
488 weeks == 1 ? "week" : "weeks",
489 days,
490 days == 1 ? "day" : "days",
491 s);
f7581717 492 } else if (d >= 2*USEC_PER_DAY)
d5e6f36c 493 (void) snprintf(buf, l, USEC_FMT " days%s", d / USEC_PER_DAY,s);
9a98c7a1 494 else if (d >= 25*USEC_PER_HOUR)
d5e6f36c 495 (void) snprintf(buf, l, "1 day " USEC_FMT "h%s",
121ed16c 496 (d - USEC_PER_DAY) / USEC_PER_HOUR, s);
9a98c7a1 497 else if (d >= 6*USEC_PER_HOUR)
d5e6f36c 498 (void) snprintf(buf, l, USEC_FMT "h%s",
121ed16c 499 d / USEC_PER_HOUR, s);
9a98c7a1 500 else if (d >= USEC_PER_HOUR)
d5e6f36c 501 (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min%s",
121ed16c
LB
502 d / USEC_PER_HOUR,
503 (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
9a98c7a1 504 else if (d >= 5*USEC_PER_MINUTE)
d5e6f36c 505 (void) snprintf(buf, l, USEC_FMT "min%s",
121ed16c 506 d / USEC_PER_MINUTE, s);
9a98c7a1 507 else if (d >= USEC_PER_MINUTE)
d5e6f36c 508 (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s%s",
121ed16c
LB
509 d / USEC_PER_MINUTE,
510 (d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
9a98c7a1 511 else if (d >= USEC_PER_SEC)
d5e6f36c 512 (void) snprintf(buf, l, USEC_FMT "s%s",
121ed16c 513 d / USEC_PER_SEC, s);
9a98c7a1 514 else if (d >= USEC_PER_MSEC)
d5e6f36c 515 (void) snprintf(buf, l, USEC_FMT "ms%s",
121ed16c 516 d / USEC_PER_MSEC, s);
9a98c7a1 517 else if (d > 0)
d5e6f36c 518 (void) snprintf(buf, l, USEC_FMT"us%s",
121ed16c 519 d, s);
9a98c7a1 520 else
121ed16c 521 (void) snprintf(buf, l, "now");
9a98c7a1
LP
522
523 buf[l-1] = 0;
524 return buf;
525}
526
d5e6f36c 527char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
9a98c7a1
LP
528 static const struct {
529 const char *suffix;
530 usec_t usec;
531 } table[] = {
eb55ec9f
LP
532 { "y", USEC_PER_YEAR },
533 { "month", USEC_PER_MONTH },
534 { "w", USEC_PER_WEEK },
535 { "d", USEC_PER_DAY },
536 { "h", USEC_PER_HOUR },
537 { "min", USEC_PER_MINUTE },
538 { "s", USEC_PER_SEC },
539 { "ms", USEC_PER_MSEC },
540 { "us", 1 },
9a98c7a1
LP
541 };
542
99534007 543 char *p = ASSERT_PTR(buf);
2fa4092c 544 bool something = false;
9a98c7a1 545
9a98c7a1
LP
546 assert(l > 0);
547
bb1fada8
LP
548 if (t == USEC_INFINITY) {
549 strncpy(p, "infinity", l-1);
550 p[l-1] = 0;
551 return p;
552 }
553
554 if (t <= 0) {
555 strncpy(p, "0", l-1);
7c537b2e
LP
556 p[l-1] = 0;
557 return p;
558 }
559
7f602784 560 /* The result of this function can be parsed with parse_sec */
9a98c7a1 561
fe96c0f8 562 for (size_t i = 0; i < ELEMENTSOF(table); i++) {
7759ecb2 563 int k = 0;
9a98c7a1 564 size_t n;
2fa4092c
LP
565 bool done = false;
566 usec_t a, b;
567
7c537b2e
LP
568 if (t <= 0)
569 break;
2fa4092c 570
7c537b2e 571 if (t < accuracy && something)
2fa4092c 572 break;
9a98c7a1
LP
573
574 if (t < table[i].usec)
575 continue;
576
577 if (l <= 1)
578 break;
579
2fa4092c
LP
580 a = t / table[i].usec;
581 b = t % table[i].usec;
582
583 /* Let's see if we should shows this in dot notation */
584 if (t < USEC_PER_MINUTE && b > 0) {
c543f4d7 585 signed char j = 0;
2fa4092c 586
c543f4d7 587 for (usec_t cc = table[i].usec; cc > 1; cc /= 10)
2fa4092c
LP
588 j++;
589
c543f4d7 590 for (usec_t cc = accuracy; cc > 1; cc /= 10) {
2fa4092c
LP
591 b /= 10;
592 j--;
593 }
594
595 if (j > 0) {
596 k = snprintf(p, l,
70887c5f 597 "%s"USEC_FMT".%0*"PRI_USEC"%s",
2fa4092c 598 p > buf ? " " : "",
de0671ee 599 a,
2fa4092c 600 j,
70887c5f 601 b,
2fa4092c
LP
602 table[i].suffix);
603
604 t = 0;
605 done = true;
606 }
607 }
608
609 /* No? Then let's show it normally */
610 if (!done) {
611 k = snprintf(p, l,
de0671ee 612 "%s"USEC_FMT"%s",
2fa4092c 613 p > buf ? " " : "",
de0671ee 614 a,
2fa4092c
LP
615 table[i].suffix);
616
617 t = b;
618 }
619
9102c625 620 n = MIN((size_t) k, l-1);
9a98c7a1
LP
621
622 l -= n;
623 p += n;
624
2fa4092c 625 something = true;
9a98c7a1
LP
626 }
627
628 *p = 0;
629
630 return buf;
631}
632
7a9afae6
YW
633static int parse_timestamp_impl(
634 const char *t,
ef658a63 635 size_t max_len,
7a9afae6
YW
636 bool utc,
637 int isdst,
638 long gmtoff,
639 usec_t *ret) {
640
92134489
LP
641 static const struct {
642 const char *name;
643 const int nr;
644 } day_nr[] = {
645 { "Sunday", 0 },
646 { "Sun", 0 },
647 { "Monday", 1 },
648 { "Mon", 1 },
649 { "Tuesday", 2 },
650 { "Tue", 2 },
651 { "Wednesday", 3 },
652 { "Wed", 3 },
653 { "Thursday", 4 },
654 { "Thu", 4 },
655 { "Friday", 5 },
656 { "Fri", 5 },
657 { "Saturday", 6 },
658 { "Sat", 6 },
659 };
660
8beb47c8 661 _cleanup_free_ char *t_alloc = NULL;
1d2c42c5 662 usec_t usec, plus = 0, minus = 0;
8beb47c8 663 bool with_tz = false;
7a9afae6 664 int r, weekday = -1;
1d2c42c5 665 unsigned fractional = 0;
7a9afae6
YW
666 const char *k;
667 struct tm tm, copy;
a83c1baa 668 time_t sec;
9a98c7a1 669
947f9f01 670 /* Allowed syntaxes:
9a98c7a1 671 *
ef658a63 672 * 2012-09-22 16:34:22.1[2[3[4[5[6]]]]]
673 * 2012-09-22 16:34:22 (µsec will be set to 0)
9a98c7a1 674 * 2012-09-22 16:34 (seconds will be set to 0)
ef658a63 675 * 2012-09-22T16:34:22.1[2[3[4[5[6]]]]]
676 * 2012-09-22T16:34:22 (µsec will be set to 0)
677 * 2012-09-22T16:34 (seconds will be set to 0)
9a98c7a1
LP
678 * 2012-09-22 (time will be set to 00:00:00)
679 * 16:34:22 (date will be set to today)
680 * 16:34 (date will be set to today, seconds to 0)
681 * now
682 * yesterday (time is set to 00:00:00)
683 * today (time is set to 00:00:00)
684 * tomorrow (time is set to 00:00:00)
685 * +5min
686 * -5days
5ba6e094 687 * @2147483647 (seconds since epoch)
37c6a3dc
YW
688 *
689 * Note, on DST change, 00:00:00 may not exist and in that case the time part may be shifted.
690 * E.g. "Sun 2023-03-13 America/Havana" is parsed as "Sun 2023-03-13 01:00:00 CDT".
ef658a63 691 *
692 * A simplified strptime-spelled RFC3339 ABNF looks like
693 * "%Y-%m-%d" "T" "%H" ":" "%M" ":" "%S" [".%N"] ("Z" / (("+" / "-") "%H:%M"))
694 * We additionally allow no seconds and inherited timezone
695 * for symmetry with our other syntaxes and improved interactive usability:
696 * "%Y-%m-%d" "T" "%H" ":" "%M" ":" ["%S" [".%N"]] ["Z" / (("+" / "-") "%H:%M")]
697 * RFC3339 defines time-secfrac to as "." 1*DIGIT, but we limit to 6 digits,
698 * since we're limited to 1µs resolution.
699 * We also accept "Sat 2012-09-22T16:34:22", RFC3339 warns against it.
9a98c7a1
LP
700 */
701
702 assert(t);
9a98c7a1 703
ef658a63 704 if (max_len != SIZE_MAX) {
8beb47c8
YW
705 /* If the input string contains timezone, then cut it here. */
706
ef658a63 707 if (max_len == 0) /* Can't be the only field */
8beb47c8
YW
708 return -EINVAL;
709
ef658a63 710 t_alloc = strndup(t, max_len);
8beb47c8
YW
711 if (!t_alloc)
712 return -ENOMEM;
713
714 t = t_alloc;
715 with_tz = true;
716 }
717
9f819781
YW
718 if (utc) {
719 /* glibc accepts gmtoff more than 24 hours, but we refuse it. */
720 if ((usec_t) labs(gmtoff) * USEC_PER_SEC > USEC_PER_DAY)
721 return -EINVAL;
722 } else {
723 if (gmtoff != 0)
724 return -EINVAL;
725 }
726
48d26c01 727 if (t[0] == '@' && !with_tz)
cf98b66d 728 return parse_sec(t + 1, ret);
9a98c7a1 729
cf98b66d 730 usec = now(CLOCK_REALTIME);
9a98c7a1 731
48d26c01
IK
732 if (!with_tz) {
733 if (streq(t, "now"))
734 goto finish;
9a98c7a1 735
17d1ebfc 736 if (t[0] == '+') {
48d26c01
IK
737 r = parse_sec(t+1, &plus);
738 if (r < 0)
739 return r;
9a98c7a1 740
48d26c01 741 goto finish;
17d1ebfc 742 }
9a98c7a1 743
17d1ebfc 744 if (t[0] == '-') {
48d26c01
IK
745 r = parse_sec(t+1, &minus);
746 if (r < 0)
747 return r;
9a98c7a1 748
48d26c01 749 goto finish;
17d1ebfc 750 }
decad910 751
17d1ebfc 752 if ((k = endswith(t, " ago"))) {
804537bd 753 _cleanup_free_ char *buf = NULL;
decad910 754
804537bd
YW
755 buf = strndup(t, k - t);
756 if (!buf)
757 return -ENOMEM;
758
759 r = parse_sec(buf, &minus);
48d26c01
IK
760 if (r < 0)
761 return r;
decad910 762
48d26c01 763 goto finish;
17d1ebfc 764 }
1fcf71f5 765
17d1ebfc 766 if ((k = endswith(t, " left"))) {
804537bd
YW
767 _cleanup_free_ char *buf = NULL;
768
769 buf = strndup(t, k - t);
770 if (!buf)
771 return -ENOMEM;
1fcf71f5 772
804537bd 773 r = parse_sec(buf, &plus);
48d26c01
IK
774 if (r < 0)
775 return r;
1fcf71f5 776
48d26c01
IK
777 goto finish;
778 }
21b3a0fc
LP
779 }
780
a83c1baa 781 sec = (time_t) (usec / USEC_PER_SEC);
e4eaf99a 782
a83c1baa 783 if (!localtime_or_gmtime_r(&sec, &tm, utc))
21b3a0fc
LP
784 return -EINVAL;
785
a83c1baa 786 tm.tm_isdst = isdst;
e4eaf99a
HV
787
788 if (streq(t, "today")) {
789 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
790 goto from_tm;
791
792 } else if (streq(t, "yesterday")) {
313cefa1 793 tm.tm_mday--;
e4eaf99a
HV
794 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
795 goto from_tm;
796
797 } else if (streq(t, "tomorrow")) {
313cefa1 798 tm.tm_mday++;
e4eaf99a
HV
799 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
800 goto from_tm;
801 }
802
f2ecfd8b
YW
803 for (size_t i = 0; i < ELEMENTSOF(day_nr); i++) {
804 k = startswith_no_case(t, day_nr[i].name);
805 if (!k || *k != ' ')
92134489
LP
806 continue;
807
808 weekday = day_nr[i].nr;
f2ecfd8b 809 t = k + 1;
92134489
LP
810 break;
811 }
812
9a98c7a1
LP
813 copy = tm;
814 k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
e4eaf99a
HV
815 if (k) {
816 if (*k == '.')
817 goto parse_usec;
818 else if (*k == 0)
819 goto from_tm;
820 }
9a98c7a1 821
ef658a63 822 /* Our "canonical" RFC3339 syntax variant */
9a98c7a1
LP
823 tm = copy;
824 k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
e4eaf99a
HV
825 if (k) {
826 if (*k == '.')
827 goto parse_usec;
828 else if (*k == 0)
829 goto from_tm;
830 }
9a98c7a1 831
ef658a63 832 /* RFC3339 syntax */
833 tm = copy;
834 k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
835 if (k) {
836 if (*k == '.')
837 goto parse_usec;
838 else if (*k == 0)
839 goto from_tm;
840 }
841
34c4dff4
FS
842 /* Support OUTPUT_SHORT and OUTPUT_SHORT_PRECISE formats */
843 tm = copy;
844 k = strptime(t, "%b %d %H:%M:%S", &tm);
845 if (k) {
846 if (*k == '.')
847 goto parse_usec;
848 else if (*k == 0)
849 goto from_tm;
850 }
851
9a98c7a1
LP
852 tm = copy;
853 k = strptime(t, "%y-%m-%d %H:%M", &tm);
854 if (k && *k == 0) {
855 tm.tm_sec = 0;
e4eaf99a 856 goto from_tm;
9a98c7a1
LP
857 }
858
ef658a63 859 /* Our "canonical" RFC3339 syntax variant without seconds */
9a98c7a1
LP
860 tm = copy;
861 k = strptime(t, "%Y-%m-%d %H:%M", &tm);
862 if (k && *k == 0) {
863 tm.tm_sec = 0;
e4eaf99a 864 goto from_tm;
9a98c7a1
LP
865 }
866
ef658a63 867 /* RFC3339 syntax without seconds */
868 tm = copy;
869 k = strptime(t, "%Y-%m-%dT%H:%M", &tm);
870 if (k && *k == 0) {
871 tm.tm_sec = 0;
872 goto from_tm;
873 }
874
9a98c7a1
LP
875 tm = copy;
876 k = strptime(t, "%y-%m-%d", &tm);
877 if (k && *k == 0) {
878 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
e4eaf99a 879 goto from_tm;
9a98c7a1
LP
880 }
881
882 tm = copy;
883 k = strptime(t, "%Y-%m-%d", &tm);
884 if (k && *k == 0) {
885 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
e4eaf99a 886 goto from_tm;
9a98c7a1
LP
887 }
888
889 tm = copy;
890 k = strptime(t, "%H:%M:%S", &tm);
e4eaf99a
HV
891 if (k) {
892 if (*k == '.')
893 goto parse_usec;
894 else if (*k == 0)
895 goto from_tm;
896 }
9a98c7a1
LP
897
898 tm = copy;
899 k = strptime(t, "%H:%M", &tm);
900 if (k && *k == 0) {
901 tm.tm_sec = 0;
e4eaf99a 902 goto from_tm;
9a98c7a1
LP
903 }
904
905 return -EINVAL;
906
e4eaf99a 907parse_usec:
1d2c42c5
YW
908 k++;
909 r = parse_fractional_part_u(&k, 6, &fractional);
910 if (r < 0)
911 return -EINVAL;
912 if (*k != '\0')
913 return -EINVAL;
e4eaf99a
HV
914
915from_tm:
7a9afae6
YW
916 assert(plus == 0);
917 assert(minus == 0);
918
214cc95d 919 if (weekday >= 0 && tm.tm_wday != weekday)
68bdd2d2 920 return -EINVAL;
9a98c7a1 921
7a9afae6
YW
922 if (gmtoff < 0) {
923 plus = -gmtoff * USEC_PER_SEC;
924
ca9c9d8d 925 /* If gmtoff is negative, the string may be too old to be parsed as UTC.
7a9afae6
YW
926 * E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC
927 * We assumed that gmtoff is in the range of -24:00…+24:00, hence the only date we need to
928 * handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift
929 * later. */
930 if (tm.tm_year == 69 && tm.tm_mon == 11 && tm.tm_mday == 31) {
931 /* Thu 1970-01-01-00:00:00 */
932 tm.tm_year = 70;
933 tm.tm_mon = 0;
934 tm.tm_mday = 1;
935 tm.tm_wday = 4;
936 tm.tm_yday = 0;
937 minus = USEC_PER_DAY;
938 }
939 } else
940 minus = gmtoff * USEC_PER_SEC;
941
a83c1baa
YW
942 sec = mktime_or_timegm(&tm, utc);
943 if (sec < 0)
92134489
LP
944 return -EINVAL;
945
a83c1baa 946 usec = usec_add(sec * USEC_PER_SEC, fractional);
9a98c7a1 947
e4eaf99a 948finish:
db43717e
YW
949 usec = usec_add(usec, plus);
950
951 if (usec < minus)
1bb4b028
LP
952 return -EINVAL;
953
db43717e
YW
954 usec = usec_sub_unsigned(usec, minus);
955
956 if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
68bdd2d2 957 return -EINVAL;
9a98c7a1 958
cf98b66d
YW
959 if (ret)
960 *ret = usec;
9a98c7a1
LP
961 return 0;
962}
963
8beb47c8 964static int parse_timestamp_maybe_with_tz(const char *t, size_t tz_offset, bool valid_tz, usec_t *ret) {
7a9afae6 965 assert(t);
7a9afae6
YW
966
967 tzset();
968
969 for (int j = 0; j <= 1; j++) {
970 if (isempty(tzname[j]))
971 continue;
972
8beb47c8 973 if (!streq(t + tz_offset, tzname[j]))
7a9afae6
YW
974 continue;
975
976 /* The specified timezone matches tzname[] of the local timezone. */
ef658a63 977 return parse_timestamp_impl(t, tz_offset - 1, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret);
7a9afae6
YW
978 }
979
87e0fd57
YW
980 /* If we know that the last word is a valid timezone (e.g. Asia/Tokyo), then simply drop the timezone
981 * and parse the remaining string as a local time. If we know that the last word is not a timezone,
982 * then assume that it is a part of the time and try to parse the whole string as a local time. */
ef658a63 983 return parse_timestamp_impl(t, valid_tz ? tz_offset - 1 : SIZE_MAX,
87e0fd57 984 /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
7a9afae6
YW
985}
986
48d26c01
IK
987typedef struct ParseTimestampResult {
988 usec_t usec;
989 int return_value;
990} ParseTimestampResult;
991
cf98b66d 992int parse_timestamp(const char *t, usec_t *ret) {
48d26c01 993 ParseTimestampResult *shared, tmp;
8beb47c8 994 const char *k, *tz, *current_tz;
ef658a63 995 size_t max_len, t_len;
7a9afae6 996 struct tm tm;
4c253ed1 997 int r;
48d26c01 998
dff3bddc
YW
999 assert(t);
1000
ef658a63 1001 t_len = strlen(t);
1002 if (t_len > 2 && t[t_len - 1] == 'Z' && t[t_len - 2] != ' ') /* RFC3339-style welded UTC: "1985-04-12T23:20:50.52Z" */
1003 return parse_timestamp_impl(t, t_len - 1, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
1004
1005 if (t_len > 7 && IN_SET(t[t_len - 6], '+', '-') && t[t_len - 7] != ' ') { /* RFC3339-style welded offset: "1990-12-31T15:59:60-08:00" */
1006 k = strptime(&t[t_len - 6], "%z", &tm);
1007 if (k && *k == '\0')
1008 return parse_timestamp_impl(t, t_len - 6, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
1009 }
1010
8beb47c8
YW
1011 tz = strrchr(t, ' ');
1012 if (!tz)
ef658a63 1013 return parse_timestamp_impl(t, /* max_len = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
48d26c01 1014
ef658a63 1015 max_len = tz - t;
8beb47c8 1016 tz++;
7a9afae6
YW
1017
1018 /* Shortcut, parse the string as UTC. */
8beb47c8 1019 if (streq(tz, "UTC"))
ef658a63 1020 return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
7a9afae6
YW
1021
1022 /* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as
87e0fd57
YW
1023 * UTC and shift the result. Note, this must be earlier than the timezone check with tzname[], as
1024 * tzname[] may be in the same format. */
8beb47c8 1025 k = strptime(tz, "%z", &tm);
9f819781 1026 if (k && *k == '\0')
ef658a63 1027 return parse_timestamp_impl(t, max_len, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
7a9afae6
YW
1028
1029 /* If the last word is not a timezone file (e.g. Asia/Tokyo), then let's check if it matches
1030 * tzname[] of the local timezone, e.g. JST or CEST. */
8beb47c8 1031 if (!timezone_is_valid(tz, LOG_DEBUG))
ef658a63 1032 return parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ false, ret);
7a9afae6
YW
1033
1034 /* Shortcut. If the current $TZ is equivalent to the specified timezone, it is not necessary to fork
1035 * the process. */
8beb47c8
YW
1036 current_tz = getenv("TZ");
1037 if (current_tz && *current_tz == ':' && streq(current_tz + 1, tz))
ef658a63 1038 return parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ true, ret);
7a9afae6
YW
1039
1040 /* Otherwise, to avoid polluting the current environment variables, let's fork the process and set
1041 * the specified timezone in the child process. */
48d26c01 1042
48d26c01
IK
1043 shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
1044 if (shared == MAP_FAILED)
1045 return negative_errno();
1046
e9ccae31 1047 r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, NULL);
4c253ed1 1048 if (r < 0) {
48d26c01 1049 (void) munmap(shared, sizeof *shared);
4c253ed1 1050 return r;
48d26c01 1051 }
4c253ed1 1052 if (r == 0) {
7a9afae6 1053 const char *colon_tz;
3fd4929b 1054
437f48a4 1055 /* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
8beb47c8 1056 colon_tz = strjoina(":", tz);
437f48a4
LP
1057
1058 if (setenv("TZ", colon_tz, 1) != 0) {
48d26c01
IK
1059 shared->return_value = negative_errno();
1060 _exit(EXIT_FAILURE);
1061 }
1062
ef658a63 1063 shared->return_value = parse_timestamp_maybe_with_tz(t, tz - t, /* valid_tz = */ true, &shared->usec);
48d26c01
IK
1064
1065 _exit(EXIT_SUCCESS);
1066 }
1067
48d26c01
IK
1068 tmp = *shared;
1069 if (munmap(shared, sizeof *shared) != 0)
1070 return negative_errno();
1071
cf98b66d
YW
1072 if (tmp.return_value == 0 && ret)
1073 *ret = tmp.usec;
48d26c01
IK
1074
1075 return tmp.return_value;
1076}
1077
cf98b66d 1078static const char* extract_multiplier(const char *p, usec_t *ret) {
9a98c7a1
LP
1079 static const struct {
1080 const char *suffix;
1081 usec_t usec;
1082 } table[] = {
eb55ec9f
LP
1083 { "seconds", USEC_PER_SEC },
1084 { "second", USEC_PER_SEC },
1085 { "sec", USEC_PER_SEC },
1086 { "s", USEC_PER_SEC },
9a98c7a1 1087 { "minutes", USEC_PER_MINUTE },
eb55ec9f
LP
1088 { "minute", USEC_PER_MINUTE },
1089 { "min", USEC_PER_MINUTE },
1090 { "months", USEC_PER_MONTH },
1091 { "month", USEC_PER_MONTH },
1092 { "M", USEC_PER_MONTH },
1093 { "msec", USEC_PER_MSEC },
1094 { "ms", USEC_PER_MSEC },
1095 { "m", USEC_PER_MINUTE },
1096 { "hours", USEC_PER_HOUR },
1097 { "hour", USEC_PER_HOUR },
1098 { "hr", USEC_PER_HOUR },
1099 { "h", USEC_PER_HOUR },
1100 { "days", USEC_PER_DAY },
1101 { "day", USEC_PER_DAY },
1102 { "d", USEC_PER_DAY },
1103 { "weeks", USEC_PER_WEEK },
1104 { "week", USEC_PER_WEEK },
1105 { "w", USEC_PER_WEEK },
1106 { "years", USEC_PER_YEAR },
1107 { "year", USEC_PER_YEAR },
1108 { "y", USEC_PER_YEAR },
1109 { "usec", 1ULL },
1110 { "us", 1ULL },
d0a6d7c4
LP
1111 { "μs", 1ULL }, /* U+03bc (aka GREEK SMALL LETTER MU) */
1112 { "µs", 1ULL }, /* U+b5 (aka MICRO SIGN) */
9a98c7a1 1113 };
240a7ba9 1114
dff3bddc
YW
1115 assert(p);
1116 assert(ret);
1117
fe96c0f8 1118 for (size_t i = 0; i < ELEMENTSOF(table); i++) {
240a7ba9
ZJS
1119 char *e;
1120
1121 e = startswith(p, table[i].suffix);
1122 if (e) {
cf98b66d 1123 *ret = table[i].usec;
240a7ba9
ZJS
1124 return e;
1125 }
1126 }
9a98c7a1 1127
240a7ba9
ZJS
1128 return p;
1129}
1130
cf98b66d 1131int parse_time(const char *t, usec_t *ret, usec_t default_unit) {
b1d6dcf5 1132 const char *p, *s;
cf98b66d 1133 usec_t usec = 0;
cb0dac05 1134 bool something = false;
9a98c7a1
LP
1135
1136 assert(t);
519cffec 1137 assert(default_unit > 0);
9a98c7a1
LP
1138
1139 p = t;
b1d6dcf5
ZJS
1140
1141 p += strspn(p, WHITESPACE);
1142 s = startswith(p, "infinity");
1143 if (s) {
1144 s += strspn(s, WHITESPACE);
1145 if (*s != 0)
1146 return -EINVAL;
1147
cf98b66d
YW
1148 if (ret)
1149 *ret = USEC_INFINITY;
b1d6dcf5
ZJS
1150 return 0;
1151 }
1152
cb0dac05 1153 for (;;) {
5a9fb358 1154 usec_t multiplier = default_unit, k;
ed2e7967 1155 long long l;
5a9fb358 1156 char *e;
cb0dac05
LP
1157
1158 p += strspn(p, WHITESPACE);
1159
1160 if (*p == 0) {
1161 if (!something)
1162 return -EINVAL;
1163
1164 break;
1165 }
9a98c7a1 1166
5a9fb358
LP
1167 if (*p == '-') /* Don't allow "-0" */
1168 return -ERANGE;
1169
9a98c7a1
LP
1170 errno = 0;
1171 l = strtoll(p, &e, 10);
8333c77e 1172 if (errno > 0)
9a98c7a1 1173 return -errno;
9a98c7a1
LP
1174 if (l < 0)
1175 return -ERANGE;
1176
cb0dac05 1177 if (*e == '.') {
ed2e7967
YW
1178 p = e + 1;
1179 p += strspn(p, DIGITS);
cb0dac05 1180 } else if (e == p)
9a98c7a1 1181 return -EINVAL;
ed2e7967
YW
1182 else
1183 p = e;
9a98c7a1 1184
ed2e7967
YW
1185 s = extract_multiplier(p + strspn(p, WHITESPACE), &multiplier);
1186 if (s == p && *s != '\0')
7802194a 1187 /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56' */
ed2e7967 1188 return -EINVAL;
9a98c7a1 1189
ed2e7967 1190 p = s;
519cffec 1191
ed2e7967
YW
1192 if ((usec_t) l >= USEC_INFINITY / multiplier)
1193 return -ERANGE;
8079c903 1194
ed2e7967 1195 k = (usec_t) l * multiplier;
cf98b66d 1196 if (k >= USEC_INFINITY - usec)
8079c903
YW
1197 return -ERANGE;
1198
cf98b66d 1199 usec += k;
519cffec 1200
ed2e7967 1201 something = true;
519cffec 1202
ed2e7967
YW
1203 if (*e == '.') {
1204 usec_t m = multiplier / 10;
1205 const char *b;
8079c903 1206
ed2e7967
YW
1207 for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
1208 k = (usec_t) (*b - '0') * m;
cf98b66d 1209 if (k >= USEC_INFINITY - usec)
ed2e7967
YW
1210 return -ERANGE;
1211
cf98b66d 1212 usec += k;
ed2e7967
YW
1213 }
1214
7802194a 1215 /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
ed2e7967
YW
1216 if (b == e + 1)
1217 return -EINVAL;
1218 }
cb0dac05 1219 }
9a98c7a1 1220
cf98b66d
YW
1221 if (ret)
1222 *ret = usec;
9a98c7a1
LP
1223 return 0;
1224}
1225
cf98b66d
YW
1226int parse_sec(const char *t, usec_t *ret) {
1227 return parse_time(t, ret, USEC_PER_SEC);
519cffec
LP
1228}
1229
def34f63
LP
1230int parse_sec_fix_0(const char *t, usec_t *ret) {
1231 usec_t k;
1232 int r;
eae51da3 1233
def34f63
LP
1234 assert(t);
1235 assert(ret);
eae51da3 1236
def34f63
LP
1237 r = parse_sec(t, &k);
1238 if (r < 0)
1239 return r;
0004f698 1240
def34f63
LP
1241 *ret = k == 0 ? USEC_INFINITY : k;
1242 return r;
0004f698
ZJS
1243}
1244
7b61ce3c 1245int parse_sec_def_infinity(const char *t, usec_t *ret) {
dff3bddc
YW
1246 assert(t);
1247 assert(ret);
1248
7b61ce3c
FB
1249 t += strspn(t, WHITESPACE);
1250 if (isempty(t)) {
1251 *ret = USEC_INFINITY;
1252 return 0;
1253 }
1254 return parse_sec(t, ret);
1255}
1256
cf98b66d 1257static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
9a98c7a1
LP
1258 static const struct {
1259 const char *suffix;
1260 nsec_t nsec;
1261 } table[] = {
ed2e7967
YW
1262 { "seconds", NSEC_PER_SEC },
1263 { "second", NSEC_PER_SEC },
1264 { "sec", NSEC_PER_SEC },
1265 { "s", NSEC_PER_SEC },
9a98c7a1 1266 { "minutes", NSEC_PER_MINUTE },
ed2e7967
YW
1267 { "minute", NSEC_PER_MINUTE },
1268 { "min", NSEC_PER_MINUTE },
1269 { "months", NSEC_PER_MONTH },
1270 { "month", NSEC_PER_MONTH },
1271 { "M", NSEC_PER_MONTH },
1272 { "msec", NSEC_PER_MSEC },
1273 { "ms", NSEC_PER_MSEC },
1274 { "m", NSEC_PER_MINUTE },
1275 { "hours", NSEC_PER_HOUR },
1276 { "hour", NSEC_PER_HOUR },
1277 { "hr", NSEC_PER_HOUR },
1278 { "h", NSEC_PER_HOUR },
1279 { "days", NSEC_PER_DAY },
1280 { "day", NSEC_PER_DAY },
1281 { "d", NSEC_PER_DAY },
1282 { "weeks", NSEC_PER_WEEK },
1283 { "week", NSEC_PER_WEEK },
1284 { "w", NSEC_PER_WEEK },
1285 { "years", NSEC_PER_YEAR },
1286 { "year", NSEC_PER_YEAR },
1287 { "y", NSEC_PER_YEAR },
1288 { "usec", NSEC_PER_USEC },
1289 { "us", NSEC_PER_USEC },
d0a6d7c4
LP
1290 { "μs", NSEC_PER_USEC }, /* U+03bc (aka GREEK LETTER MU) */
1291 { "µs", NSEC_PER_USEC }, /* U+b5 (aka MICRO SIGN) */
ed2e7967
YW
1292 { "nsec", 1ULL },
1293 { "ns", 1ULL },
1294 { "", 1ULL }, /* default is nsec */
9a98c7a1 1295 };
ed2e7967
YW
1296 size_t i;
1297
dff3bddc
YW
1298 assert(p);
1299 assert(ret);
1300
ed2e7967
YW
1301 for (i = 0; i < ELEMENTSOF(table); i++) {
1302 char *e;
1303
1304 e = startswith(p, table[i].suffix);
1305 if (e) {
cf98b66d 1306 *ret = table[i].nsec;
ed2e7967
YW
1307 return e;
1308 }
1309 }
1310
1311 return p;
1312}
9a98c7a1 1313
cf98b66d 1314int parse_nsec(const char *t, nsec_t *ret) {
e73c78c2 1315 const char *p, *s;
cf98b66d 1316 nsec_t nsec = 0;
cb0dac05 1317 bool something = false;
9a98c7a1
LP
1318
1319 assert(t);
cf98b66d 1320 assert(ret);
9a98c7a1
LP
1321
1322 p = t;
e73c78c2
LP
1323
1324 p += strspn(p, WHITESPACE);
1325 s = startswith(p, "infinity");
1326 if (s) {
1327 s += strspn(s, WHITESPACE);
8e8933ca 1328 if (*s != 0)
e73c78c2
LP
1329 return -EINVAL;
1330
cf98b66d 1331 *ret = NSEC_INFINITY;
e73c78c2
LP
1332 return 0;
1333 }
1334
cb0dac05 1335 for (;;) {
ed2e7967
YW
1336 nsec_t multiplier = 1, k;
1337 long long l;
9a98c7a1 1338 char *e;
cb0dac05
LP
1339
1340 p += strspn(p, WHITESPACE);
1341
1342 if (*p == 0) {
1343 if (!something)
1344 return -EINVAL;
1345
1346 break;
1347 }
9a98c7a1 1348
ed2e7967 1349 if (*p == '-') /* Don't allow "-0" */
5a9fb358
LP
1350 return -ERANGE;
1351
9a98c7a1
LP
1352 errno = 0;
1353 l = strtoll(p, &e, 10);
8333c77e 1354 if (errno > 0)
9a98c7a1 1355 return -errno;
9a98c7a1
LP
1356 if (l < 0)
1357 return -ERANGE;
1358
cb0dac05 1359 if (*e == '.') {
ed2e7967
YW
1360 p = e + 1;
1361 p += strspn(p, DIGITS);
cb0dac05 1362 } else if (e == p)
9a98c7a1 1363 return -EINVAL;
ed2e7967
YW
1364 else
1365 p = e;
9a98c7a1 1366
ed2e7967
YW
1367 s = extract_nsec_multiplier(p + strspn(p, WHITESPACE), &multiplier);
1368 if (s == p && *s != '\0')
7802194a 1369 /* Don't allow '12.34.56', but accept '12.34 .56' or '12.34s.56' */
ed2e7967 1370 return -EINVAL;
9a98c7a1 1371
ed2e7967 1372 p = s;
f6a178e9 1373
ed2e7967
YW
1374 if ((nsec_t) l >= NSEC_INFINITY / multiplier)
1375 return -ERANGE;
1376
1377 k = (nsec_t) l * multiplier;
cf98b66d 1378 if (k >= NSEC_INFINITY - nsec)
ed2e7967 1379 return -ERANGE;
f6a178e9 1380
cf98b66d 1381 nsec += k;
ed2e7967
YW
1382
1383 something = true;
cb0dac05 1384
ed2e7967
YW
1385 if (*e == '.') {
1386 nsec_t m = multiplier / 10;
1387 const char *b;
cb0dac05 1388
ed2e7967
YW
1389 for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
1390 k = (nsec_t) (*b - '0') * m;
cf98b66d 1391 if (k >= NSEC_INFINITY - nsec)
f6a178e9
YW
1392 return -ERANGE;
1393
cf98b66d 1394 nsec += k;
9a98c7a1
LP
1395 }
1396
7802194a 1397 /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
ed2e7967
YW
1398 if (b == e + 1)
1399 return -EINVAL;
1400 }
cb0dac05 1401 }
9a98c7a1 1402
cf98b66d 1403 *ret = nsec;
9a98c7a1
LP
1404
1405 return 0;
1406}
03cc26dd 1407
09a54a86 1408static int get_timezones_from_zone1970_tab(char ***ret) {
75683450
LP
1409 _cleanup_fclose_ FILE *f = NULL;
1410 _cleanup_strv_free_ char **zones = NULL;
8d2b9d14 1411 int r;
75683450
LP
1412
1413 assert(ret);
1414
ba32084f 1415 f = fopen("/usr/share/zoneinfo/zone1970.tab", "re");
09a54a86
DS
1416 if (!f)
1417 return -errno;
75683450 1418
09a54a86
DS
1419 for (;;) {
1420 _cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL;
8d2b9d14 1421
09a54a86
DS
1422 r = read_line(f, LONG_LINE_MAX, &line);
1423 if (r < 0)
1424 return r;
1425 if (r == 0)
1426 break;
75683450 1427
09a54a86 1428 const char *p = line;
75683450 1429
09a54a86
DS
1430 /* Line format is:
1431 * 'country codes' 'coordinates' 'timezone' 'comments' */
1432 r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL);
1433 if (r < 0)
1434 continue;
75683450 1435
09a54a86
DS
1436 /* Lines that start with # are comments. */
1437 if (*cc == '#')
1438 continue;
1439
1440 r = strv_extend(&zones, tz);
1441 if (r < 0)
1442 return r;
1443 }
1444
1445 *ret = TAKE_PTR(zones);
1446 return 0;
1447}
1448
147bc363
DS
1449static int get_timezones_from_tzdata_zi(char ***ret) {
1450 _cleanup_fclose_ FILE *f = NULL;
1451 _cleanup_strv_free_ char **zones = NULL;
1452 int r;
1453
dff3bddc
YW
1454 assert(ret);
1455
147bc363
DS
1456 f = fopen("/usr/share/zoneinfo/tzdata.zi", "re");
1457 if (!f)
1458 return -errno;
1459
1460 for (;;) {
1461 _cleanup_free_ char *line = NULL, *type = NULL, *f1 = NULL, *f2 = NULL;
1462
1463 r = read_line(f, LONG_LINE_MAX, &line);
1464 if (r < 0)
1465 return r;
1466 if (r == 0)
1467 break;
1468
1469 const char *p = line;
1470
1471 /* The only lines we care about are Zone and Link lines.
1472 * Zone line format is:
1473 * 'Zone' 'timezone' ...
1474 * Link line format is:
1475 * 'Link' 'target' 'alias'
1476 * See 'man zic' for more detail. */
1477 r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL);
1478 if (r < 0)
1479 continue;
1480
1481 char *tz;
2f092762 1482 if (IN_SET(*type, 'Z', 'z'))
147bc363
DS
1483 /* Zone lines have timezone in field 1. */
1484 tz = f1;
2f092762 1485 else if (IN_SET(*type, 'L', 'l'))
147bc363
DS
1486 /* Link lines have timezone in field 2. */
1487 tz = f2;
1488 else
1489 /* Not a line we care about. */
1490 continue;
1491
1492 r = strv_extend(&zones, tz);
1493 if (r < 0)
1494 return r;
1495 }
1496
1497 *ret = TAKE_PTR(zones);
1498 return 0;
1499}
1500
09a54a86
DS
1501int get_timezones(char ***ret) {
1502 _cleanup_strv_free_ char **zones = NULL;
1503 int r;
1504
1505 assert(ret);
1506
147bc363
DS
1507 r = get_timezones_from_tzdata_zi(&zones);
1508 if (r == -ENOENT) {
1509 log_debug_errno(r, "Could not get timezone data from tzdata.zi, using zone1970.tab: %m");
1510 r = get_timezones_from_zone1970_tab(&zones);
1511 if (r == -ENOENT)
1512 log_debug_errno(r, "Could not get timezone data from zone1970.tab, using UTC: %m");
1513 }
09a54a86
DS
1514 if (r < 0 && r != -ENOENT)
1515 return r;
75683450 1516
31097e2b
DS
1517 /* Always include UTC */
1518 r = strv_extend(&zones, "UTC");
1519 if (r < 0)
1520 return -ENOMEM;
75683450 1521
31097e2b
DS
1522 strv_sort(zones);
1523 strv_uniq(zones);
1524
1525 *ret = TAKE_PTR(zones);
75683450
LP
1526 return 0;
1527}
1528
bdaeafea 1529int verify_timezone(const char *name, int log_level) {
75683450
LP
1530 bool slash = false;
1531 const char *p, *t;
254d1313 1532 _cleanup_close_ int fd = -EBADF;
a2932d51
MG
1533 char buf[4];
1534 int r;
75683450 1535
5c904ba5 1536 if (isempty(name))
bdaeafea 1537 return -EINVAL;
5c904ba5 1538
e8b9e9c4
LP
1539 /* Always accept "UTC" as valid timezone, since it's the fallback, even if user has no timezones installed. */
1540 if (streq(name, "UTC"))
bdaeafea 1541 return 0;
e8b9e9c4 1542
5c904ba5 1543 if (name[0] == '/')
bdaeafea 1544 return -EINVAL;
75683450
LP
1545
1546 for (p = name; *p; p++) {
ff25d338
LP
1547 if (!ascii_isdigit(*p) &&
1548 !ascii_isalpha(*p) &&
4c701096 1549 !IN_SET(*p, '-', '_', '+', '/'))
bdaeafea 1550 return -EINVAL;
75683450
LP
1551
1552 if (*p == '/') {
1553
1554 if (slash)
bdaeafea 1555 return -EINVAL;
75683450
LP
1556
1557 slash = true;
1558 } else
1559 slash = false;
1560 }
1561
1562 if (slash)
bdaeafea 1563 return -EINVAL;
75683450 1564
1c73b60b 1565 if (p - name >= PATH_MAX)
bdaeafea 1566 return -ENAMETOOLONG;
1c73b60b 1567
63c372cb 1568 t = strjoina("/usr/share/zoneinfo/", name);
a2932d51
MG
1569
1570 fd = open(t, O_RDONLY|O_CLOEXEC);
bdaeafea
ZJS
1571 if (fd < 0)
1572 return log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t);
a2932d51
MG
1573
1574 r = fd_verify_regular(fd);
bdaeafea
ZJS
1575 if (r < 0)
1576 return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t);
a2932d51
MG
1577
1578 r = loop_read_exact(fd, buf, 4, false);
bdaeafea
ZJS
1579 if (r < 0)
1580 return log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t);
75683450 1581
a2932d51 1582 /* Magic from tzfile(5) */
bdaeafea 1583 if (memcmp(buf, "TZif", 4) != 0)
8def4b34 1584 return log_full_errno(log_level, SYNTHETIC_ERRNO(EBADMSG),
bdaeafea 1585 "Timezone file '%s' has wrong magic bytes", t);
75683450 1586
bdaeafea 1587 return 0;
75683450 1588}
77ff2de9 1589
fe624c4c
LP
1590bool clock_supported(clockid_t clock) {
1591 struct timespec ts;
1592
1593 switch (clock) {
1594
1595 case CLOCK_MONOTONIC:
1596 case CLOCK_REALTIME:
fe624c4c 1597 case CLOCK_BOOTTIME:
ba4e0427
LP
1598 /* These three are always available in our baseline, and work in timerfd, as of kernel 3.15 */
1599 return true;
fe624c4c 1600
fe624c4c
LP
1601 default:
1602 /* For everything else, check properly */
1603 return clock_gettime(clock, &ts) >= 0;
1604 }
1605}
1606
bc9ecd48 1607int get_timezone(char **ret) {
5c904ba5
LP
1608 _cleanup_free_ char *t = NULL;
1609 const char *e;
1610 char *z;
1611 int r;
1612
dff3bddc
YW
1613 assert(ret);
1614
5c904ba5 1615 r = readlink_malloc("/etc/localtime", &t);
bc9ecd48 1616 if (r == -ENOENT) {
7802194a 1617 /* If the symlink does not exist, assume "UTC", like glibc does */
bc9ecd48
LP
1618 z = strdup("UTC");
1619 if (!z)
1620 return -ENOMEM;
1621
1622 *ret = z;
1623 return 0;
1624 }
5c904ba5
LP
1625 if (r < 0)
1626 return r; /* returns EINVAL if not a symlink */
1627
da9fc98d 1628 e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/");
5c904ba5
LP
1629 if (!e)
1630 return -EINVAL;
1631
089fb865 1632 if (!timezone_is_valid(e, LOG_DEBUG))
5c904ba5
LP
1633 return -EINVAL;
1634
1635 z = strdup(e);
1636 if (!z)
1637 return -ENOMEM;
1638
bc9ecd48 1639 *ret = z;
5c904ba5
LP
1640 return 0;
1641}
7c67c79c
HV
1642
1643time_t mktime_or_timegm(struct tm *tm, bool utc) {
dff3bddc
YW
1644 assert(tm);
1645
7c67c79c
HV
1646 return utc ? timegm(tm) : mktime(tm);
1647}
1648
1649struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
dff3bddc
YW
1650 assert(t);
1651 assert(tm);
1652
7c67c79c
HV
1653 return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
1654}
87b8ce69 1655
77372afb
YW
1656static uint32_t sysconf_clock_ticks_cached(void) {
1657 static thread_local uint32_t hz = 0;
87b8ce69
SS
1658 long r;
1659
1660 if (hz == 0) {
1661 r = sysconf(_SC_CLK_TCK);
1662
1663 assert(r > 0);
70887c5f 1664 hz = r;
87b8ce69
SS
1665 }
1666
77372afb
YW
1667 return hz;
1668}
1669
1670uint32_t usec_to_jiffies(usec_t u) {
1671 uint32_t hz = sysconf_clock_ticks_cached();
1672 return DIV_ROUND_UP(u, USEC_PER_SEC / hz);
1673}
1674
1675usec_t jiffies_to_usec(uint32_t j) {
1676 uint32_t hz = sysconf_clock_ticks_cached();
1677 return DIV_ROUND_UP(j * USEC_PER_SEC, hz);
87b8ce69 1678}
1007ec60
LP
1679
1680usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to) {
1681 usec_t a, b;
1682
1683 if (x == USEC_INFINITY)
1684 return USEC_INFINITY;
1685 if (map_clock_id(from) == map_clock_id(to))
1686 return x;
1687
1688 a = now(from);
1689 b = now(to);
1690
1691 if (x > a)
1692 /* x lies in the future */
1693 return usec_add(b, usec_sub_unsigned(x, a));
1694 else
1695 /* x lies in the past */
1696 return usec_sub_unsigned(b, usec_sub_unsigned(a, x));
1697}
9a9a4f10
LP
1698
1699bool in_utc_timezone(void) {
1700 tzset();
1701
1702 return timezone == 0 && daylight == 0;
1703}
4f811d27
LP
1704
1705int time_change_fd(void) {
1706
1707 /* We only care for the cancellation event, hence we set the timeout to the latest possible value. */
1708 static const struct itimerspec its = {
1709 .it_value.tv_sec = TIME_T_MAX,
1710 };
1711
254d1313 1712 _cleanup_close_ int fd = -EBADF;
4f811d27
LP
1713
1714 assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
1715
1716 /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever CLOCK_REALTIME makes a jump relative to
1717 * CLOCK_MONOTONIC. */
1718
1719 fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
1720 if (fd < 0)
1721 return -errno;
1722
601f91be
LP
1723 if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) >= 0)
1724 return TAKE_FD(fd);
1725
da890466
ZJS
1726 /* So apparently there are systems where time_t is 64-bit, but the kernel actually doesn't support
1727 * 64-bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a
601f91be
LP
1728 * similar error. If that's the case let's try with INT32_MAX instead, maybe that works. It's a bit
1729 * of a black magic thing though, but what can we do?
1730 *
da890466
ZJS
1731 * We don't want this code on x86-64, hence let's conditionalize this for systems with 64-bit time_t
1732 * but where "long" is shorter than 64-bit, i.e. 32-bit archs.
601f91be
LP
1733 *
1734 * See: https://github.com/systemd/systemd/issues/14362 */
1735
1736#if SIZEOF_TIME_T == 8 && ULONG_MAX < UINT64_MAX
9e7c8f64 1737 if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EOVERFLOW) {
601f91be
LP
1738 static const struct itimerspec its32 = {
1739 .it_value.tv_sec = INT32_MAX,
1740 };
1741
1742 if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its32, NULL) >= 0)
1743 return TAKE_FD(fd);
1744 }
1745#endif
4f811d27 1746
601f91be 1747 return -errno;
4f811d27 1748}
7b3eb5c9
LB
1749
1750static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = {
1751 [TIMESTAMP_PRETTY] = "pretty",
e01a8fdd
YW
1752 [TIMESTAMP_US] = "us",
1753 [TIMESTAMP_UTC] = "utc",
7b3eb5c9 1754 [TIMESTAMP_US_UTC] = "us+utc",
e01a8fdd 1755 [TIMESTAMP_UNIX] = "unix",
7b3eb5c9
LB
1756};
1757
1758/* Use the macro for enum → string to allow for aliases */
d227a42a 1759DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle);
7b3eb5c9
LB
1760
1761/* For the string → enum mapping we use the generic implementation, but also support two aliases */
1762TimestampStyle timestamp_style_from_string(const char *s) {
1763 TimestampStyle t;
1764
1765 t = (TimestampStyle) string_table_lookup(timestamp_style_table, ELEMENTSOF(timestamp_style_table), s);
1766 if (t >= 0)
1767 return t;
9a27ef09 1768 if (STRPTR_IN_SET(s, "µs", "μs")) /* accept both µ symbols in unicode, i.e. micro symbol + Greek small letter mu. */
7b3eb5c9 1769 return TIMESTAMP_US;
d0a6d7c4 1770 if (STRPTR_IN_SET(s, "µs+utc", "μs+utc"))
7b3eb5c9
LB
1771 return TIMESTAMP_US_UTC;
1772 return t;
1773}