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