]> git.ipfire.org Git - thirdparty/util-linux.git/blame - lib/timeutils.c
Merge branch 'master' of https://github.com/u2386/util-linux
[thirdparty/util-linux.git] / lib / timeutils.c
CommitLineData
79feaa60
KZ
1/*
2 * SPDX-License-Identifier: LGPL-2.1-or-later
3 *
4 * First set of functions in this file are part of systemd, and were
5 * copied to util-linux at August 2013.
6 *
7 * Copyright 2010 Lennart Poettering
8 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
9 *
10 * This is free software; you can redistribute it and/or modify it under the
11 * terms of the GNU Lesser General Public License as published by the Free
12 * Software Foundation; either version 2.1 of the License, or (at your option)
13 * any later version.
14 */
2659a49e
SK
15#include <assert.h>
16#include <ctype.h>
b72a75e9 17#include <stdlib.h>
2659a49e
SK
18#include <string.h>
19#include <time.h>
33c7ffa3 20#include <sys/time.h>
ce3355cc 21#include <inttypes.h>
2659a49e
SK
22
23#include "c.h"
929f939e 24#include "nls.h"
60f25dea
KZ
25#include "strutils.h"
26#include "timeutils.h"
2659a49e
SK
27
28#define WHITESPACE " \t\n\r"
29
30#define streq(a,b) (strcmp((a),(b)) == 0)
31
2659a49e
SK
32static int parse_sec(const char *t, usec_t *usec)
33{
9d826076 34 static const struct {
2659a49e
SK
35 const char *suffix;
36 usec_t usec;
9d826076 37 } table[] = {
2659a49e
SK
38 { "seconds", USEC_PER_SEC },
39 { "second", USEC_PER_SEC },
40 { "sec", USEC_PER_SEC },
41 { "s", USEC_PER_SEC },
42 { "minutes", USEC_PER_MINUTE },
43 { "minute", USEC_PER_MINUTE },
44 { "min", USEC_PER_MINUTE },
45 { "months", USEC_PER_MONTH },
46 { "month", USEC_PER_MONTH },
47 { "msec", USEC_PER_MSEC },
48 { "ms", USEC_PER_MSEC },
49 { "m", USEC_PER_MINUTE },
50 { "hours", USEC_PER_HOUR },
51 { "hour", USEC_PER_HOUR },
52 { "hr", USEC_PER_HOUR },
53 { "h", USEC_PER_HOUR },
54 { "days", USEC_PER_DAY },
55 { "day", USEC_PER_DAY },
56 { "d", USEC_PER_DAY },
57 { "weeks", USEC_PER_WEEK },
58 { "week", USEC_PER_WEEK },
59 { "w", USEC_PER_WEEK },
60 { "years", USEC_PER_YEAR },
61 { "year", USEC_PER_YEAR },
62 { "y", USEC_PER_YEAR },
63 { "usec", 1ULL },
64 { "us", 1ULL },
65 { "", USEC_PER_SEC }, /* default is sec */
9d826076 66 };
2659a49e
SK
67
68 const char *p;
69 usec_t r = 0;
70 int something = FALSE;
71
72 assert(t);
73 assert(usec);
74
75 p = t;
76 for (;;) {
77 long long l, z = 0;
78 char *e;
79 unsigned i, n = 0;
80
81 p += strspn(p, WHITESPACE);
82
83 if (*p == 0) {
84 if (!something)
85 return -EINVAL;
86
87 break;
88 }
89
90 errno = 0;
91 l = strtoll(p, &e, 10);
92
93 if (errno > 0)
94 return -errno;
95
96 if (l < 0)
97 return -ERANGE;
98
99 if (*e == '.') {
100 char *b = e + 1;
101
102 errno = 0;
103 z = strtoll(b, &e, 10);
104 if (errno > 0)
105 return -errno;
106
107 if (z < 0)
108 return -ERANGE;
109
110 if (e == b)
111 return -EINVAL;
112
113 n = e - b;
114
115 } else if (e == p)
116 return -EINVAL;
117
118 e += strspn(e, WHITESPACE);
119
120 for (i = 0; i < ARRAY_SIZE(table); i++)
121 if (startswith(e, table[i].suffix)) {
122 usec_t k = (usec_t) z * table[i].usec;
123
124 for (; n > 0; n--)
125 k /= 10;
126
127 r += (usec_t) l *table[i].usec + k;
128 p = e + strlen(table[i].suffix);
129
130 something = TRUE;
131 break;
132 }
133
134 if (i >= ARRAY_SIZE(table))
135 return -EINVAL;
136
137 }
138
139 *usec = r;
140
141 return 0;
142}
143
c061d1a7
TW
144static int parse_subseconds(const char *t, usec_t *usec)
145{
146 usec_t ret = 0;
147 int factor = USEC_PER_SEC / 10;
148
149 if (*t != '.' && *t != ',')
150 return -1;
151
152 while (*(++t)) {
153 if (!isdigit(*t) || factor < 1)
154 return -1;
155
156 ret += ((usec_t) *t - '0') * factor;
157 factor /= 10;
158 }
159
160 *usec = ret;
161 return 0;
162}
163
15a1f536 164static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec)
2659a49e 165{
9d826076 166 static const struct {
2659a49e
SK
167 const char *name;
168 const int nr;
9d826076 169 } day_nr[] = {
2659a49e
SK
170 { "Sunday", 0 },
171 { "Sun", 0 },
172 { "Monday", 1 },
173 { "Mon", 1 },
174 { "Tuesday", 2 },
175 { "Tue", 2 },
176 { "Wednesday", 3 },
177 { "Wed", 3 },
178 { "Thursday", 4 },
179 { "Thu", 4 },
180 { "Friday", 5 },
181 { "Fri", 5 },
182 { "Saturday", 6 },
183 { "Sat", 6 },
9d826076 184 };
2659a49e
SK
185
186 const char *k;
187 struct tm tm, copy;
c061d1a7 188 usec_t plus = 0, minus = 0, ret = 0;
2659a49e
SK
189 int r, weekday = -1;
190 unsigned i;
191
192 /*
193 * Allowed syntaxes:
194 *
c061d1a7
TW
195 * 2012-09-22 16:34:22 !
196 * 2012-09-22T16:34:22 !
197 * 20120922163422 !
198 * @1348331662 ! (seconds since the Epoch (1970-01-01 00:00 UTC))
199 * 2012-09-22 16:34 (seconds will be set to 0)
200 * 2012-09-22 (time will be set to 00:00:00)
201 * 16:34:22 ! (date will be set to today)
202 * 16:34 (date will be set to today, seconds to 0)
2659a49e 203 * now
c061d1a7
TW
204 * yesterday (time is set to 00:00:00)
205 * today (time is set to 00:00:00)
206 * tomorrow (time is set to 00:00:00)
2659a49e
SK
207 * +5min
208 * -5days
209 *
c061d1a7
TW
210 * Syntaxes marked with '!' also optionally allow up to six digits of
211 * subsecond granularity, separated by '.' or ',':
212 *
213 * 2012-09-22 16:34:22.12
214 * 2012-09-22 16:34:22.123456
215 *
216 *
2659a49e
SK
217 */
218
219 assert(t);
220 assert(usec);
221
2659a49e
SK
222 localtime_r(&x, &tm);
223 tm.tm_isdst = -1;
224
225 if (streq(t, "now"))
226 goto finish;
227
228 else if (streq(t, "today")) {
229 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
230 goto finish;
231
232 } else if (streq(t, "yesterday")) {
233 tm.tm_mday--;
234 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
235 goto finish;
236
237 } else if (streq(t, "tomorrow")) {
238 tm.tm_mday++;
239 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
240 goto finish;
241
242 } else if (t[0] == '+') {
243
244 r = parse_sec(t + 1, &plus);
245 if (r < 0)
246 return r;
247
248 goto finish;
249 } else if (t[0] == '-') {
250
251 r = parse_sec(t + 1, &minus);
252 if (r < 0)
253 return r;
254
255 goto finish;
fef9b04d
PU
256 } else if (t[0] == '@') {
257 k = strptime(t + 1, "%s", &tm);
258 if (k && *k == 0)
259 goto finish;
c061d1a7
TW
260 else if (k && parse_subseconds(k, &ret) == 0)
261 goto finish;
2659a49e 262
fef9b04d 263 return -EINVAL;
2659a49e
SK
264 } else if (endswith(t, " ago")) {
265 char *z;
266
267 z = strndup(t, strlen(t) - 4);
268 if (!z)
269 return -ENOMEM;
270
271 r = parse_sec(z, &minus);
06264d63 272 free(z);
2659a49e
SK
273 if (r < 0)
274 return r;
275
276 goto finish;
277 }
278
279 for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
280 size_t skip;
281
282 if (!startswith_no_case(t, day_nr[i].name))
283 continue;
284
285 skip = strlen(day_nr[i].name);
286 if (t[skip] != ' ')
287 continue;
288
289 weekday = day_nr[i].nr;
290 t += skip + 1;
291 break;
292 }
293
294 copy = tm;
295 k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
296 if (k && *k == 0)
297 goto finish;
c061d1a7
TW
298 else if (k && parse_subseconds(k, &ret) == 0)
299 goto finish;
2659a49e
SK
300
301 tm = copy;
302 k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
303 if (k && *k == 0)
304 goto finish;
c061d1a7
TW
305 else if (k && parse_subseconds(k, &ret) == 0)
306 goto finish;
2659a49e 307
79995631
KZ
308 tm = copy;
309 k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
310 if (k && *k == 0)
311 goto finish;
c061d1a7
TW
312 else if (k && parse_subseconds(k, &ret) == 0)
313 goto finish;
79995631 314
2659a49e
SK
315 tm = copy;
316 k = strptime(t, "%y-%m-%d %H:%M", &tm);
317 if (k && *k == 0) {
318 tm.tm_sec = 0;
319 goto finish;
320 }
321
322 tm = copy;
323 k = strptime(t, "%Y-%m-%d %H:%M", &tm);
324 if (k && *k == 0) {
325 tm.tm_sec = 0;
326 goto finish;
327 }
328
329 tm = copy;
330 k = strptime(t, "%y-%m-%d", &tm);
331 if (k && *k == 0) {
332 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
333 goto finish;
334 }
335
336 tm = copy;
337 k = strptime(t, "%Y-%m-%d", &tm);
338 if (k && *k == 0) {
339 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
340 goto finish;
341 }
342
343 tm = copy;
344 k = strptime(t, "%H:%M:%S", &tm);
345 if (k && *k == 0)
346 goto finish;
c061d1a7
TW
347 else if (k && parse_subseconds(k, &ret) == 0)
348 goto finish;
2659a49e
SK
349
350 tm = copy;
351 k = strptime(t, "%H:%M", &tm);
352 if (k && *k == 0) {
353 tm.tm_sec = 0;
354 goto finish;
355 }
356
75ab9bf1
SK
357 tm = copy;
358 k = strptime(t, "%Y%m%d%H%M%S", &tm);
2abb8394 359 if (k && *k == 0)
75ab9bf1 360 goto finish;
c061d1a7
TW
361 else if (k && parse_subseconds(k, &ret) == 0)
362 goto finish;
75ab9bf1 363
2659a49e
SK
364 return -EINVAL;
365
366 finish:
367 x = mktime(&tm);
368 if (x == (time_t)-1)
369 return -EINVAL;
370
371 if (weekday >= 0 && tm.tm_wday != weekday)
372 return -EINVAL;
373
c061d1a7 374 ret += (usec_t) x * USEC_PER_SEC;
2659a49e
SK
375
376 ret += plus;
377 if (ret > minus)
378 ret -= minus;
379 else
380 ret = 0;
381
382 *usec = ret;
383
384 return 0;
385}
3c201431 386
15a1f536
TW
387int parse_timestamp(const char *t, usec_t *usec)
388{
389 return parse_timestamp_reference(time(NULL), t, usec);
390}
391
9fd0a7a9
WP
392/* Returns the difference in seconds between its argument and GMT. If if TP is
393 * invalid or no DST information is available default to UTC, that is, zero.
394 * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected.
395 * Derived from glibc/time/strftime_l.c
396 */
397int get_gmtoff(const struct tm *tp)
398{
399 if (tp->tm_isdst < 0)
ad524980 400 return 0;
9fd0a7a9
WP
401
402#if HAVE_TM_GMTOFF
403 return tp->tm_gmtoff;
404#else
405 struct tm tm;
406 struct tm gtm;
407 struct tm ltm = *tp;
408 time_t lt;
409
410 tzset();
411 lt = mktime(&ltm);
412 /* Check if mktime returning -1 is an error or a valid time_t */
413 if (lt == (time_t) -1) {
414 if (! localtime_r(&lt, &tm)
415 || ((ltm.tm_sec ^ tm.tm_sec)
416 | (ltm.tm_min ^ tm.tm_min)
417 | (ltm.tm_hour ^ tm.tm_hour)
418 | (ltm.tm_mday ^ tm.tm_mday)
419 | (ltm.tm_mon ^ tm.tm_mon)
420 | (ltm.tm_year ^ tm.tm_year)))
421 return 0;
422 }
423
424 if (! gmtime_r(&lt, &gtm))
425 return 0;
426
427 /* Calculate the GMT offset, that is, the difference between the
428 * TP argument (ltm) and GMT (gtm).
429 *
430 * Compute intervening leap days correctly even if year is negative.
431 * Take care to avoid int overflow in leap day calculations, but it's OK
432 * to assume that A and B are close to each other.
433 */
434 int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3);
435 int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3);
436 int a100 = a4 / 25 - (a4 % 25 < 0);
437 int b100 = b4 / 25 - (b4 % 25 < 0);
438 int a400 = a100 >> 2;
439 int b400 = b100 >> 2;
440 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
441
442 int years = ltm.tm_year - gtm.tm_year;
443 int days = (365 * years + intervening_leap_days
444 + (ltm.tm_yday - gtm.tm_yday));
445
446 return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour))
447 + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec));
448#endif
449}
450
136f4874 451static int format_iso_time(const struct tm *tm, uint32_t nsec, int flags, char *buf, size_t bufsz)
3c201431 452{
136f4874 453 uint32_t usec = nsec / NSEC_PER_USEC;
33c7ffa3
KZ
454 char *p = buf;
455 int len;
3c201431 456
4111bb3a 457 if (flags & ISO_DATE) {
2d9a0b0a
WP
458 len = snprintf(p, bufsz, "%4ld-%.2d-%.2d",
459 tm->tm_year + (long) 1900,
460 tm->tm_mon + 1, tm->tm_mday);
33c7ffa3 461 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 462 goto err;
33c7ffa3
KZ
463 bufsz -= len;
464 p += len;
465 }
466
4111bb3a 467 if ((flags & ISO_DATE) && (flags & ISO_TIME)) {
33c7ffa3 468 if (bufsz < 1)
6cdc7b9c 469 goto err;
4111bb3a 470 *p++ = (flags & ISO_T) ? 'T' : ' ';
33c7ffa3
KZ
471 bufsz--;
472 }
3c201431 473
4111bb3a 474 if (flags & ISO_TIME) {
33c7ffa3 475 len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour,
2d9a0b0a 476 tm->tm_min, tm->tm_sec);
33c7ffa3 477 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 478 goto err;
33c7ffa3
KZ
479 bufsz -= len;
480 p += len;
481 }
3c201431 482
136f4874
TW
483 if (flags & ISO_DOTNSEC) {
484 len = snprintf(p, bufsz, ".%09"PRIu32, nsec);
485 if (len < 0 || (size_t) len > bufsz)
486 goto err;
487 bufsz -= len;
488 p += len;
489
490 } else if (flags & ISO_COMMANSEC) {
491 len = snprintf(p, bufsz, ",%09"PRIu32, nsec);
492 if (len < 0 || (size_t) len > bufsz)
493 goto err;
494 bufsz -= len;
495 p += len;
496 } else if (flags & ISO_DOTUSEC) {
497 len = snprintf(p, bufsz, ".%06"PRIu32, usec);
33c7ffa3 498 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 499 goto err;
33c7ffa3
KZ
500 bufsz -= len;
501 p += len;
502
4111bb3a 503 } else if (flags & ISO_COMMAUSEC) {
136f4874 504 len = snprintf(p, bufsz, ",%06"PRIu32, usec);
33c7ffa3 505 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 506 goto err;
33c7ffa3
KZ
507 bufsz -= len;
508 p += len;
509 }
3c201431 510
4111bb3a 511 if (flags & ISO_TIMEZONE) {
9fd0a7a9
WP
512 int tmin = get_gmtoff(tm) / 60;
513 int zhour = tmin / 60;
514 int zmin = abs(tmin % 60);
515 len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
516 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 517 goto err;
9fd0a7a9 518 }
33c7ffa3 519 return 0;
6cdc7b9c
WP
520 err:
521 warnx(_("format_iso_time: buffer overflow."));
522 return -1;
3c201431
KZ
523}
524
b9abaae3
TW
525/* timespec to ISO 8601 */
526int strtimespec_iso(const struct timespec *ts, int flags, char *buf, size_t bufsz)
3c201431 527{
c1616946 528 struct tm tm;
ee475ab2 529 struct tm *rc;
c1616946 530
4111bb3a 531 if (flags & ISO_GMTIME)
b9abaae3 532 rc = gmtime_r(&ts->tv_sec, &tm);
c1616946 533 else
b9abaae3 534 rc = localtime_r(&ts->tv_sec, &tm);
ee475ab2
WP
535
536 if (rc)
b9abaae3 537 return format_iso_time(&tm, ts->tv_nsec, flags, buf, bufsz);
ee475ab2 538
b9abaae3 539 warnx(_("time %"PRId64" is out of range."), (int64_t)(ts->tv_sec));
ee475ab2 540 return -1;
3c201431
KZ
541}
542
b9abaae3
TW
543/* timeval to ISO 8601 */
544int strtimeval_iso(const struct timeval *tv, int flags, char *buf, size_t bufsz)
545{
546 struct timespec ts = {
547 .tv_sec = tv->tv_sec,
548 .tv_nsec = tv->tv_usec * NSEC_PER_USEC,
549 };
550
551 return strtimespec_iso(&ts, flags, buf, bufsz);
552}
553
33c7ffa3 554/* struct tm to ISO 8601 */
971244fd 555int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz)
3c201431 556{
33c7ffa3 557 return format_iso_time(tm, 0, flags, buf, bufsz);
3c201431
KZ
558}
559
33c7ffa3
KZ
560/* time_t to ISO 8601 */
561int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz)
3c201431 562{
c1616946 563 struct tm tm;
ee475ab2 564 struct tm *rc;
c1616946 565
4111bb3a 566 if (flags & ISO_GMTIME)
ee475ab2 567 rc = gmtime_r(t, &tm);
c1616946 568 else
ee475ab2
WP
569 rc = localtime_r(t, &tm);
570
571 if (rc)
572 return format_iso_time(&tm, 0, flags, buf, bufsz);
573
ce3355cc 574 warnx(_("time %"PRId64" is out of range."), (int64_t)*t);
ee475ab2 575 return -1;
3c201431
KZ
576}
577
eee665bb 578/* relative time functions */
d393c00c
SK
579static inline int time_is_thisyear(struct tm const *const tm,
580 struct tm const *const tmnow)
eee665bb 581{
d393c00c 582 return tm->tm_year == tmnow->tm_year;
eee665bb
KZ
583}
584
d393c00c
SK
585static inline int time_is_today(struct tm const *const tm,
586 struct tm const *const tmnow)
eee665bb 587{
d393c00c
SK
588 return (tm->tm_yday == tmnow->tm_yday &&
589 time_is_thisyear(tm, tmnow));
eee665bb
KZ
590}
591
592int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz)
593{
d393c00c 594 struct tm tm, tmnow;
eee665bb
KZ
595 int rc = 0;
596
d393c00c
SK
597 if (now->tv_sec == 0)
598 gettimeofday(now, NULL);
599
600 localtime_r(t, &tm);
601 localtime_r(&now->tv_sec, &tmnow);
eee665bb 602
d393c00c 603 if (time_is_today(&tm, &tmnow)) {
eee665bb
KZ
604 rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min);
605 if (rc < 0 || (size_t) rc > bufsz)
606 return -1;
607 rc = 1;
608
d393c00c 609 } else if (time_is_thisyear(&tm, &tmnow)) {
eee665bb
KZ
610 if (flags & UL_SHORTTIME_THISYEAR_HHMM)
611 rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm);
612 else
613 rc = strftime(buf, bufsz, "%b%d", &tm);
614 } else
615 rc = strftime(buf, bufsz, "%Y-%b%d", &tm);
616
617 return rc <= 0 ? -1 : 0;
618}
3c201431 619
00ddcaf1
TW
620int strtimespec_relative(const struct timespec *ts, char *buf, size_t bufsz)
621{
622 time_t secs = ts->tv_sec;
623 size_t i, parts = 0;
624 int rc;
625
626 if (bufsz)
627 buf[0] = '\0';
628
629 static const struct {
630 const char * const suffix;
631 int width;
632 int64_t secs;
633 } table[] = {
634 { "y", 4, NSEC_PER_YEAR / NSEC_PER_SEC },
635 { "d", 3, NSEC_PER_DAY / NSEC_PER_SEC },
636 { "h", 2, NSEC_PER_HOUR / NSEC_PER_SEC },
637 { "m", 2, NSEC_PER_MINUTE / NSEC_PER_SEC },
638 { "s", 2, NSEC_PER_SEC / NSEC_PER_SEC },
639 };
640
641 for (i = 0; i < ARRAY_SIZE(table); i++) {
642 if (secs >= table[i].secs) {
643 rc = snprintf(buf, bufsz,
644 "%*"PRId64"%s%s",
645 parts ? table[i].width : 0,
646 secs / table[i].secs, table[i].suffix,
647 secs % table[i].secs ? " " : "");
648 if (rc < 0 || (size_t) rc > bufsz)
649 goto err;
650 parts++;
651 buf += rc;
652 bufsz -= rc;
653 secs %= table[i].secs;
654 }
655 }
656
657 if (ts->tv_nsec) {
658 if (ts->tv_nsec % NSEC_PER_MSEC) {
659 rc = snprintf(buf, bufsz, "%*luns",
660 parts ? 10 : 0, ts->tv_nsec);
661 if (rc < 0 || (size_t) rc > bufsz)
662 goto err;
663 } else {
664 rc = snprintf(buf, bufsz, "%*llums",
665 parts ? 4 : 0, ts->tv_nsec / NSEC_PER_MSEC);
666 if (rc < 0 || (size_t) rc > bufsz)
667 goto err;
668 }
669 }
670
671 return 0;
672 err:
673 warnx(_("format_reltime: buffer overflow."));
674 return -1;
675}
676
b72a75e9
SK
677#ifndef HAVE_TIMEGM
678time_t timegm(struct tm *tm)
679{
680 const char *zone = getenv("TZ");
681 time_t ret;
682
683 setenv("TZ", "", 1);
684 tzset();
685 ret = mktime(tm);
686 if (zone)
687 setenv("TZ", zone, 1);
688 else
689 unsetenv("TZ");
690 tzset();
691 return ret;
692}
693#endif /* HAVE_TIMEGM */
694
3c201431
KZ
695#ifdef TEST_PROGRAM_TIMEUTILS
696
d9cc65cb
TW
697static int run_unittest_timestamp(void)
698{
699 int rc = EXIT_SUCCESS;
700 time_t reference = 1674180427;
701 static const struct testcase {
702 const char * const input;
703 usec_t expected;
704 } testcases[] = {
c061d1a7
TW
705 { "2012-09-22 16:34:22" , 1348331662000000 },
706 { "2012-09-22 16:34:22,012", 1348331662012000 },
707 { "2012-09-22 16:34:22.012", 1348331662012000 },
708 { "@1348331662" , 1348331662000000 },
709 { "@1348331662.234567" , 1348331662234567 },
710 { "2012-09-22 16:34" , 1348331640000000 },
711 { "2012-09-22" , 1348272000000000 },
712 { "16:34:22" , 1674232462000000 },
713 { "16:34:22,123456" , 1674232462123456 },
714 { "16:34:22.123456" , 1674232462123456 },
715 { "16:34" , 1674232440000000 },
716 { "now" , 1674180427000000 },
717 { "yesterday" , 1674086400000000 },
718 { "today" , 1674172800000000 },
719 { "tomorrow" , 1674259200000000 },
720 { "+5min" , 1674180727000000 },
721 { "-5days" , 1673748427000000 },
722 { "20120922163422" , 1348331662000000 },
d9cc65cb
TW
723 };
724
73fb8efe 725 setenv("TZ", "GMT", 1);
d9cc65cb
TW
726 tzset();
727
728 for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
729 struct testcase t = testcases[i];
730 usec_t result;
731 int r = parse_timestamp_reference(reference, t.input, &result);
732 if (r) {
733 fprintf(stderr, "Could not parse '%s'\n", t.input);
734 rc = EXIT_FAILURE;
735 }
736
737 if (result != t.expected) {
738 fprintf(stderr, "#%02zu %-25s: %"PRId64" != %"PRId64"\n",
739 i, t.input, result, t.expected);
740 rc = EXIT_FAILURE;
741 }
742 }
743
744 return rc;
745}
746
5c20665a
TW
747static int run_unittest_format(void)
748{
749 int rc = EXIT_SUCCESS;
750 const struct timespec ts = {
751 .tv_sec = 1674180427,
752 .tv_nsec = 12345,
753 };
754 char buf[FORMAT_TIMESTAMP_MAX];
755 static const struct testcase {
756 int flags;
757 const char * const expected;
758 } testcases[] = {
759 { ISO_DATE, "2023-01-20" },
760 { ISO_TIME, "02:07:07" },
761 { ISO_TIMEZONE, "+00:00" },
762 { ISO_TIMESTAMP_T, "2023-01-20T02:07:07+00:00" },
763 { ISO_TIMESTAMP_COMMA_G, "2023-01-20 02:07:07,000012+00:00" },
764 { ISO_TIME | ISO_DOTNSEC, "02:07:07.000012345" },
765 };
766
767 setenv("TZ", "GMT", 1);
768 tzset();
769
770 for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
771 struct testcase t = testcases[i];
772 int r = strtimespec_iso(&ts, t.flags, buf, sizeof(buf));
773 if (r) {
774 fprintf(stderr, "Could not format '%s'\n", t.expected);
775 rc = EXIT_FAILURE;
776 }
777
778 if (strcmp(buf, t.expected)) {
779 fprintf(stderr, "#%02zu %-20s != %-20s\n", i, buf, t.expected);
780 rc = EXIT_FAILURE;
781 }
782 }
783
784 return rc;
785}
786
00ddcaf1
TW
787static int run_unittest_format_relative(void)
788{
789 int rc = EXIT_SUCCESS;
790 char buf[FORMAT_TIMESTAMP_MAX];
791 static const struct testcase {
792 struct timespec ts;
793 const char * const expected;
794 } testcases[] = {
795 {{}, "" },
796 {{ 1 }, "1s" },
797 {{ 10 }, "10s" },
798 {{ 100 }, "1m 40s" },
799 {{ 1000 }, "16m 40s" },
800 {{ 10000 }, "2h 46m 40s" },
801 {{ 100000 }, "1d 3h 46m 40s" },
802 {{ 1000000 }, "11d 13h 46m 40s" },
803 {{ 10000000 }, "115d 17h 46m 40s" },
804 {{ 100000000 }, "3y 61d 15h 46m 40s" },
805 {{ 60 }, "1m" },
806 {{ 3600 }, "1h" },
807
808 {{ 1, 1 }, "1s 1ns" },
809 {{ 0, 1 }, "1ns" },
810 {{ 0, 1000000 }, "1ms" },
811 {{ 0, 1000001 }, "1000001ns" },
812 };
813
814 setenv("TZ", "GMT", 1);
815 tzset();
816
817 for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
818 struct testcase t = testcases[i];
819 int r = strtimespec_relative(&t.ts, buf, sizeof(buf));
820 if (r) {
821 fprintf(stderr, "Could not format '%s'\n", t.expected);
822 rc = EXIT_FAILURE;
823 }
824
825 if (strcmp(buf, t.expected)) {
826 fprintf(stderr, "#%02zu '%-20s' != '%-20s'\n", i, buf, t.expected);
827 rc = EXIT_FAILURE;
828 }
829 }
830
831 return rc;
832}
833
3c201431
KZ
834int main(int argc, char *argv[])
835{
6ebca9f1 836 struct timespec ts = { 0 };
4111bb3a 837 char buf[ISO_BUFSIZ];
3c201431
KZ
838
839 if (argc < 2) {
d9cc65cb 840 fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>] | [--unittest-timestamp]\n", argv[0]);
3c201431
KZ
841 exit(EXIT_FAILURE);
842 }
843
5c20665a 844 if (strcmp(argv[1], "--unittest-timestamp") == 0)
d9cc65cb 845 return run_unittest_timestamp();
5c20665a
TW
846 else if (strcmp(argv[1], "--unittest-format") == 0)
847 return run_unittest_format();
00ddcaf1
TW
848 else if (strcmp(argv[1], "--unittest-format-relative") == 0)
849 return run_unittest_format_relative();
d9cc65cb 850
79995631 851 if (strcmp(argv[1], "--timestamp") == 0) {
7ed57952 852 usec_t usec = 0;
79995631
KZ
853
854 parse_timestamp(argv[2], &usec);
6ebca9f1
TW
855 ts.tv_sec = (time_t) (usec / USEC_PER_SEC);
856 ts.tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC;
79995631 857 } else {
6ebca9f1 858 ts.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
79995631 859 if (argc == 3)
6ebca9f1
TW
860 ts.tv_nsec = strtos64_or_err(argv[2], "failed to parse <usec>")
861 * NSEC_PER_USEC;
79995631 862 }
3c201431 863
6ebca9f1 864 strtimespec_iso(&ts, ISO_DATE, buf, sizeof(buf));
33c7ffa3 865 printf("Date: '%s'\n", buf);
3c201431 866
6ebca9f1 867 strtimespec_iso(&ts, ISO_TIME, buf, sizeof(buf));
33c7ffa3 868 printf("Time: '%s'\n", buf);
3c201431 869
6ebca9f1 870 strtimespec_iso(&ts, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
4111bb3a 871 buf, sizeof(buf));
33c7ffa3 872 printf("Full: '%s'\n", buf);
3c201431 873
6ebca9f1 874 strtimespec_iso(&ts, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
33c7ffa3 875 printf("Zone: '%s'\n", buf);
3c201431 876
00ddcaf1
TW
877 strtimespec_relative(&ts, buf, sizeof(buf));
878 printf("Rel: '%s'\n", buf);
879
3c201431
KZ
880 return EXIT_SUCCESS;
881}
882
e8f7acb0 883#endif /* TEST_PROGRAM_TIMEUTILS */