Coverage Report

Created: 2025-08-09 06:31

/src/util-linux/lib/timeutils.c
Line
Count
Source (jump to first uncovered line)
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
 */
15
#include <assert.h>
16
#include <ctype.h>
17
#include <stdlib.h>
18
#include <string.h>
19
#include <time.h>
20
#include <sys/time.h>
21
#include <inttypes.h>
22
23
#include "c.h"
24
#include "nls.h"
25
#include "strutils.h"
26
#include "timeutils.h"
27
28
0
#define WHITESPACE " \t\n\r"
29
30
0
#define streq(a,b) (strcmp((a),(b)) == 0)
31
32
static int parse_sec(const char *t, usec_t *usec)
33
0
{
34
0
  static const struct {
35
0
    const char *suffix;
36
0
    usec_t usec;
37
0
  } table[] = {
38
0
    { "seconds",  USEC_PER_SEC },
39
0
    { "second", USEC_PER_SEC },
40
0
    { "sec",  USEC_PER_SEC },
41
0
    { "s",    USEC_PER_SEC },
42
0
    { "minutes",  USEC_PER_MINUTE },
43
0
    { "minute", USEC_PER_MINUTE },
44
0
    { "min",  USEC_PER_MINUTE },
45
0
    { "months", USEC_PER_MONTH },
46
0
    { "month",  USEC_PER_MONTH },
47
0
    { "msec", USEC_PER_MSEC },
48
0
    { "ms",   USEC_PER_MSEC },
49
0
    { "m",    USEC_PER_MINUTE },
50
0
    { "hours",  USEC_PER_HOUR },
51
0
    { "hour", USEC_PER_HOUR },
52
0
    { "hr",   USEC_PER_HOUR },
53
0
    { "h",    USEC_PER_HOUR },
54
0
    { "days", USEC_PER_DAY },
55
0
    { "day",  USEC_PER_DAY },
56
0
    { "d",    USEC_PER_DAY },
57
0
    { "weeks",  USEC_PER_WEEK },
58
0
    { "week", USEC_PER_WEEK },
59
0
    { "w",    USEC_PER_WEEK },
60
0
    { "years",  USEC_PER_YEAR },
61
0
    { "year", USEC_PER_YEAR },
62
0
    { "y",    USEC_PER_YEAR },
63
0
    { "usec", 1ULL },
64
0
    { "us",   1ULL },
65
0
    { "",   USEC_PER_SEC }, /* default is sec */
66
0
  };
67
68
0
  const char *p;
69
0
  usec_t r = 0;
70
0
  int something = FALSE;
71
72
0
  assert(t);
73
0
  assert(usec);
74
75
0
  p = t;
76
0
  for (;;) {
77
0
    long long l, z = 0;
78
0
    char *e;
79
0
    unsigned i, n = 0;
80
81
0
    p += strspn(p, WHITESPACE);
82
83
0
    if (*p == 0) {
84
0
      if (!something)
85
0
        return -EINVAL;
86
87
0
      break;
88
0
    }
89
90
0
    errno = 0;
91
0
    l = strtoll(p, &e, 10);
92
93
0
    if (errno > 0)
94
0
      return -errno;
95
96
0
    if (l < 0)
97
0
      return -ERANGE;
98
99
0
    if (*e == '.') {
100
0
      char *b = e + 1;
101
102
0
      errno = 0;
103
0
      z = strtoll(b, &e, 10);
104
0
      if (errno > 0)
105
0
        return -errno;
106
107
0
      if (z < 0)
108
0
        return -ERANGE;
109
110
0
      if (e == b)
111
0
        return -EINVAL;
112
113
0
      n = e - b;
114
115
0
    } else if (e == p)
116
0
      return -EINVAL;
117
118
0
    e += strspn(e, WHITESPACE);
119
120
0
    for (i = 0; i < ARRAY_SIZE(table); i++)
121
0
      if (ul_startswith(e, table[i].suffix)) {
122
0
        usec_t k = (usec_t) z * table[i].usec;
123
124
0
        for (; n > 0; n--)
125
0
          k /= 10;
126
127
0
        r += (usec_t) l *table[i].usec + k;
128
0
        p = e + strlen(table[i].suffix);
129
130
0
        something = TRUE;
131
0
        break;
132
0
      }
133
134
0
    if (i >= ARRAY_SIZE(table))
135
0
      return -EINVAL;
136
137
0
  }
138
139
0
  *usec = r;
140
141
0
  return 0;
142
0
}
143
144
static int parse_subseconds(const char *t, usec_t *usec)
145
0
{
146
0
  usec_t ret = 0;
147
0
  int factor = USEC_PER_SEC / 10;
148
149
0
  if (*t != '.' && *t != ',')
150
0
    return -1;
151
152
0
  while (*(++t)) {
153
0
    if (!isdigit(*t) || factor < 1)
154
0
      return -1;
155
156
0
    ret += ((usec_t) *t - '0') * factor;
157
0
    factor /= 10;
158
0
  }
159
160
0
  *usec = ret;
161
0
  return 0;
162
0
}
163
164
static const char *parse_epoch_seconds(const char *t, struct tm *tm)
165
0
{
166
0
  int64_t s;
167
0
  time_t st;
168
0
  int f, c;
169
170
0
  f = sscanf(t, "%"SCNd64"%n", &s, &c);
171
0
  if (f < 1)
172
0
    return NULL;
173
0
  st = s;
174
0
  if ((int64_t) st < s)
175
0
    return NULL;
176
0
  if (!localtime_r(&st, tm))
177
0
    return NULL;
178
0
  return t + c;
179
0
}
180
181
static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec)
182
0
{
183
0
  static const struct {
184
0
    const char *name;
185
0
    const int nr;
186
0
  } day_nr[] = {
187
0
    { "Sunday", 0 },
188
0
    { "Sun",  0 },
189
0
    { "Monday", 1 },
190
0
    { "Mon",  1 },
191
0
    { "Tuesday",  2 },
192
0
    { "Tue",  2 },
193
0
    { "Wednesday",  3 },
194
0
    { "Wed",  3 },
195
0
    { "Thursday", 4 },
196
0
    { "Thu",  4 },
197
0
    { "Friday", 5 },
198
0
    { "Fri",  5 },
199
0
    { "Saturday", 6 },
200
0
    { "Sat",  6 },
201
0
  };
202
203
0
  const char *k;
204
0
  struct tm tm, copy;
205
0
  usec_t plus = 0, minus = 0, ret = 0;
206
0
  int r, weekday = -1;
207
0
  unsigned i;
208
209
  /*
210
   * Allowed syntaxes:
211
   *
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)
220
   *   now
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)
224
   *   +5min
225
   *   -5days
226
   *
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
   *
234
   */
235
236
0
  assert(t);
237
0
  assert(usec);
238
239
0
  localtime_r(&x, &tm);
240
0
  tm.tm_isdst = -1;
241
242
0
  if (streq(t, "now"))
243
0
    goto finish;
244
245
0
  else if (streq(t, "today")) {
246
0
    tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
247
0
    goto finish;
248
249
0
  } else if (streq(t, "yesterday")) {
250
0
    tm.tm_mday--;
251
0
    tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
252
0
    goto finish;
253
254
0
  } else if (streq(t, "tomorrow")) {
255
0
    tm.tm_mday++;
256
0
    tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
257
0
    goto finish;
258
259
0
  } else if (t[0] == '+') {
260
261
0
    r = parse_sec(t + 1, &plus);
262
0
    if (r < 0)
263
0
      return r;
264
265
0
    goto finish;
266
0
  } else if (t[0] == '-') {
267
268
0
    r = parse_sec(t + 1, &minus);
269
0
    if (r < 0)
270
0
      return r;
271
272
0
    goto finish;
273
0
  } else if (t[0] == '@') {
274
0
    k = parse_epoch_seconds(t + 1, &tm);
275
0
    if (k && *k == 0)
276
0
      goto finish;
277
0
    else if (k && parse_subseconds(k, &ret) == 0)
278
0
      goto finish;
279
280
0
    return -EINVAL;
281
0
  } else if (ul_endswith(t, " ago")) {
282
0
    char *z;
283
284
0
    z = strndup(t, strlen(t) - 4);
285
0
    if (!z)
286
0
      return -ENOMEM;
287
288
0
    r = parse_sec(z, &minus);
289
0
    free(z);
290
0
    if (r < 0)
291
0
      return r;
292
293
0
    goto finish;
294
0
  }
295
296
0
  for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
297
0
    size_t skip;
298
299
0
    if (!startswith_no_case(t, day_nr[i].name))
300
0
      continue;
301
302
0
    skip = strlen(day_nr[i].name);
303
0
    if (t[skip] != ' ')
304
0
      continue;
305
306
0
    weekday = day_nr[i].nr;
307
0
    t += skip + 1;
308
0
    break;
309
0
  }
310
311
0
  copy = tm;
312
0
  k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
313
0
  if (k && *k == 0)
314
0
    goto finish;
315
0
  else if (k && parse_subseconds(k, &ret) == 0)
316
0
    goto finish;
317
318
0
  tm = copy;
319
0
  k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
320
0
  if (k && *k == 0)
321
0
    goto finish;
322
0
  else if (k && parse_subseconds(k, &ret) == 0)
323
0
    goto finish;
324
325
0
  tm = copy;
326
0
  k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
327
0
  if (k && *k == 0)
328
0
    goto finish;
329
0
  else if (k && parse_subseconds(k, &ret) == 0)
330
0
    goto finish;
331
332
0
  tm = copy;
333
0
  k = strptime(t, "%y-%m-%d %H:%M", &tm);
334
0
  if (k && *k == 0) {
335
0
    tm.tm_sec = 0;
336
0
    goto finish;
337
0
  }
338
339
0
  tm = copy;
340
0
  k = strptime(t, "%Y-%m-%d %H:%M", &tm);
341
0
  if (k && *k == 0) {
342
0
    tm.tm_sec = 0;
343
0
    goto finish;
344
0
  }
345
346
0
  tm = copy;
347
0
  k = strptime(t, "%y-%m-%d", &tm);
348
0
  if (k && *k == 0) {
349
0
    tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
350
0
    goto finish;
351
0
  }
352
353
0
  tm = copy;
354
0
  k = strptime(t, "%Y-%m-%d", &tm);
355
0
  if (k && *k == 0) {
356
0
    tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
357
0
    goto finish;
358
0
  }
359
360
0
  tm = copy;
361
0
  k = strptime(t, "%H:%M:%S", &tm);
362
0
  if (k && *k == 0)
363
0
    goto finish;
364
0
  else if (k && parse_subseconds(k, &ret) == 0)
365
0
    goto finish;
366
367
0
  tm = copy;
368
0
  k = strptime(t, "%H:%M", &tm);
369
0
  if (k && *k == 0) {
370
0
    tm.tm_sec = 0;
371
0
    goto finish;
372
0
  }
373
374
0
  tm = copy;
375
0
  k = strptime(t, "%Y%m%d%H%M%S", &tm);
376
0
  if (k && *k == 0)
377
0
    goto finish;
378
0
  else if (k && parse_subseconds(k, &ret) == 0)
379
0
    goto finish;
380
381
0
  return -EINVAL;
382
383
0
 finish:
384
0
  x = mktime(&tm);
385
0
  if (x == (time_t)-1)
386
0
    return -EINVAL;
387
388
0
  if (weekday >= 0 && tm.tm_wday != weekday)
389
0
    return -EINVAL;
390
391
0
  ret += (usec_t) x * USEC_PER_SEC;
392
393
0
  if (minus > ret)
394
0
    return -ERANGE;
395
0
  if ((ret + plus) < ret)
396
0
    return -ERANGE;
397
398
0
  ret += plus;
399
0
  ret -= minus;
400
401
0
  *usec = ret;
402
403
0
  return 0;
404
0
}
405
406
int ul_parse_timestamp(const char *t, usec_t *usec)
407
0
{
408
0
  return parse_timestamp_reference(time(NULL), t, usec);
409
0
}
410
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
 */
416
int get_gmtoff(const struct tm *tp)
417
0
{
418
0
  if (tp->tm_isdst < 0)
419
0
    return 0;
420
421
0
#if HAVE_TM_GMTOFF
422
0
  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
0
}
469
470
static int format_iso_time(const struct tm *tm, uint32_t nsec, int flags, char *buf, size_t bufsz)
471
0
{
472
0
  uint32_t usec = nsec / NSEC_PER_USEC;
473
0
  char *p = buf;
474
0
  int len;
475
476
0
  if (flags & ISO_DATE) {
477
0
    len = snprintf(p, bufsz, "%4ld-%.2d-%.2d",
478
0
             tm->tm_year + (long) 1900,
479
0
             tm->tm_mon + 1, tm->tm_mday);
480
0
    if (len < 0 || (size_t) len > bufsz)
481
0
      goto err;
482
0
    bufsz -= len;
483
0
    p += len;
484
0
  }
485
486
0
  if ((flags & ISO_DATE) && (flags & ISO_TIME)) {
487
0
    if (bufsz < 1)
488
0
      goto err;
489
0
    *p++ = (flags & ISO_T) ? 'T' : ' ';
490
0
    bufsz--;
491
0
  }
492
493
0
  if (flags & ISO_TIME) {
494
0
    len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour,
495
0
             tm->tm_min, tm->tm_sec);
496
0
    if (len < 0 || (size_t) len > bufsz)
497
0
      goto err;
498
0
    bufsz -= len;
499
0
    p += len;
500
0
  }
501
502
0
  if (flags & ISO_DOTNSEC) {
503
0
    len = snprintf(p, bufsz, ".%09"PRIu32, nsec);
504
0
    if (len < 0 || (size_t) len > bufsz)
505
0
      goto err;
506
0
    bufsz -= len;
507
0
    p += len;
508
509
0
  } else if (flags & ISO_COMMANSEC) {
510
0
    len = snprintf(p, bufsz, ",%09"PRIu32, nsec);
511
0
    if (len < 0 || (size_t) len > bufsz)
512
0
      goto err;
513
0
    bufsz -= len;
514
0
    p += len;
515
0
  } else if (flags & ISO_DOTUSEC) {
516
0
    len = snprintf(p, bufsz, ".%06"PRIu32, usec);
517
0
    if (len < 0 || (size_t) len > bufsz)
518
0
      goto err;
519
0
    bufsz -= len;
520
0
    p += len;
521
522
0
  } else if (flags & ISO_COMMAUSEC) {
523
0
    len = snprintf(p, bufsz, ",%06"PRIu32, usec);
524
0
    if (len < 0 || (size_t) len > bufsz)
525
0
      goto err;
526
0
    bufsz -= len;
527
0
    p += len;
528
0
  }
529
530
0
  if (flags & ISO_TIMEZONE) {
531
0
    int tmin  = get_gmtoff(tm) / 60;
532
0
    int zhour = tmin / 60;
533
0
    int zmin  = abs(tmin % 60);
534
0
    len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
535
0
    if (len < 0 || (size_t) len > bufsz)
536
0
      goto err;
537
0
  }
538
0
  return 0;
539
0
 err:
540
0
  warnx(_("format_iso_time: buffer overflow."));
541
0
  return -1;
542
0
}
543
544
/* timespec to ISO 8601 */
545
int strtimespec_iso(const struct timespec *ts, int flags, char *buf, size_t bufsz)
546
0
{
547
0
  struct tm tm;
548
0
  struct tm *rc;
549
550
0
  if (flags & ISO_GMTIME)
551
0
    rc = gmtime_r(&ts->tv_sec, &tm);
552
0
  else
553
0
    rc = localtime_r(&ts->tv_sec, &tm);
554
555
0
  if (rc)
556
0
    return format_iso_time(&tm, ts->tv_nsec, flags, buf, bufsz);
557
558
0
  warnx(_("time %"PRId64" is out of range."), (int64_t)(ts->tv_sec));
559
0
  return -1;
560
0
}
561
562
/* timeval to ISO 8601 */
563
int strtimeval_iso(const struct timeval *tv, int flags, char *buf, size_t bufsz)
564
0
{
565
0
  struct timespec ts = {
566
0
    .tv_sec = tv->tv_sec,
567
0
    .tv_nsec = tv->tv_usec * NSEC_PER_USEC,
568
0
  };
569
570
0
  return strtimespec_iso(&ts, flags, buf, bufsz);
571
0
}
572
573
/* struct tm to ISO 8601 */
574
int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz)
575
0
{
576
0
  return format_iso_time(tm, 0, flags, buf, bufsz);
577
0
}
578
579
/* time_t to ISO 8601 */
580
int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz)
581
0
{
582
0
  struct tm tm;
583
0
  struct tm *rc;
584
585
0
  if (flags & ISO_GMTIME)
586
0
    rc = gmtime_r(t, &tm);
587
0
  else
588
0
    rc = localtime_r(t, &tm);
589
590
0
  if (rc)
591
0
    return format_iso_time(&tm, 0, flags, buf, bufsz);
592
593
0
  warnx(_("time %"PRId64" is out of range."), (int64_t)*t);
594
0
  return -1;
595
0
}
596
597
/* relative time functions */
598
static inline int time_is_thisyear(struct tm const *const tm,
599
           struct tm const *const tmnow)
600
0
{
601
0
  return tm->tm_year == tmnow->tm_year;
602
0
}
603
604
static inline int time_is_today(struct tm const *const tm,
605
        struct tm const *const tmnow)
606
0
{
607
0
  return (tm->tm_yday == tmnow->tm_yday &&
608
0
    time_is_thisyear(tm, tmnow));
609
0
}
610
611
int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz)
612
0
{
613
0
  struct tm tm, tmnow;
614
0
  int rc = 0;
615
616
0
  if (now->tv_sec == 0)
617
0
    gettimeofday(now, NULL);
618
619
0
  localtime_r(t, &tm);
620
0
  localtime_r(&now->tv_sec, &tmnow);
621
622
0
  if (time_is_today(&tm, &tmnow)) {
623
0
    rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min);
624
0
    if (rc < 0 || (size_t) rc > bufsz)
625
0
      return -1;
626
0
    rc = 1;
627
628
0
  } else if (time_is_thisyear(&tm, &tmnow)) {
629
0
    if (flags & UL_SHORTTIME_THISYEAR_HHMM)
630
0
      rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm);
631
0
    else
632
0
      rc = strftime(buf, bufsz, "%b%d", &tm);
633
0
  } else
634
0
    rc = strftime(buf, bufsz, "%Y-%b%d", &tm);
635
636
0
  return rc <= 0 ? -1 : 0;
637
0
}
638
639
int strtimespec_relative(const struct timespec *ts, char *buf, size_t bufsz)
640
0
{
641
0
  time_t secs = ts->tv_sec;
642
0
  size_t i, parts = 0;
643
0
  int rc;
644
645
0
  if (bufsz)
646
0
    buf[0] = '\0';
647
648
0
  static const struct {
649
0
    const char * const suffix;
650
0
    int width;
651
0
    int64_t secs;
652
0
  } table[] = {
653
0
    { "y", 4, NSEC_PER_YEAR   / NSEC_PER_SEC },
654
0
    { "d", 3, NSEC_PER_DAY    / NSEC_PER_SEC },
655
0
    { "h", 2, NSEC_PER_HOUR   / NSEC_PER_SEC },
656
0
    { "m", 2, NSEC_PER_MINUTE / NSEC_PER_SEC },
657
0
    { "s", 2, NSEC_PER_SEC    / NSEC_PER_SEC },
658
0
  };
659
660
0
  for (i = 0; i < ARRAY_SIZE(table); i++) {
661
0
    if (secs >= table[i].secs) {
662
0
      rc = snprintf(buf, bufsz,
663
0
              "%*"PRId64"%s%s",
664
0
              parts ? table[i].width : 0,
665
0
              secs / table[i].secs, table[i].suffix,
666
0
              secs % table[i].secs ? " " : "");
667
0
      if (rc < 0 || (size_t) rc > bufsz)
668
0
        goto err;
669
0
      parts++;
670
0
      buf += rc;
671
0
      bufsz -= rc;
672
0
      secs %= table[i].secs;
673
0
    }
674
0
  }
675
676
0
  if (ts->tv_nsec) {
677
0
    if (ts->tv_nsec % NSEC_PER_MSEC) {
678
0
      rc = snprintf(buf, bufsz, "%*luns",
679
0
              parts ? 10 : 0, ts->tv_nsec);
680
0
      if (rc < 0 || (size_t) rc > bufsz)
681
0
        goto err;
682
0
    } else {
683
0
      rc = snprintf(buf, bufsz, "%*llums",
684
0
              parts ? 4 : 0, ts->tv_nsec / NSEC_PER_MSEC);
685
0
      if (rc < 0 || (size_t) rc > bufsz)
686
0
        goto err;
687
0
    }
688
0
  }
689
690
0
  return 0;
691
0
 err:
692
0
  warnx(_("format_reltime: buffer overflow."));
693
0
  return -1;
694
0
}
695
696
#ifndef HAVE_TIMEGM
697
time_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
714
#ifdef TEST_PROGRAM_TIMEUTILS
715
716
static 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[] = {
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 },
729
    { "@0"                     ,                0 },
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 },
743
  };
744
745
  setenv("TZ", "GMT", 1);
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
767
static 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
807
static 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
854
int main(int argc, char *argv[])
855
{
856
  struct timespec ts = { 0 };
857
  char buf[ISO_BUFSIZ];
858
  int r;
859
860
  if (argc < 2) {
861
    fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>] | [--unittest-timestamp]\n", argv[0]);
862
    exit(EXIT_FAILURE);
863
  }
864
865
  if (strcmp(argv[1], "--unittest-timestamp") == 0)
866
    return run_unittest_timestamp();
867
  else if (strcmp(argv[1], "--unittest-format") == 0)
868
    return run_unittest_format();
869
  else if (strcmp(argv[1], "--unittest-format-relative") == 0)
870
    return run_unittest_format_relative();
871
872
  if (strcmp(argv[1], "--timestamp") == 0) {
873
    usec_t usec = 0;
874
875
    r = ul_parse_timestamp(argv[2], &usec);
876
    if (r)
877
      errx(EXIT_FAILURE, "Can not parse '%s': %s", argv[2], strerror(-r));
878
    ts.tv_sec = (time_t) (usec / USEC_PER_SEC);
879
    ts.tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC;
880
  } else {
881
    ts.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
882
    if (argc == 3)
883
      ts.tv_nsec = strtos64_or_err(argv[2], "failed to parse <usec>")
884
             * NSEC_PER_USEC;
885
  }
886
887
  strtimespec_iso(&ts, ISO_DATE, buf, sizeof(buf));
888
  printf("Date: '%s'\n", buf);
889
890
  strtimespec_iso(&ts, ISO_TIME, buf, sizeof(buf));
891
  printf("Time: '%s'\n", buf);
892
893
  strtimespec_iso(&ts, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
894
           buf, sizeof(buf));
895
  printf("Full: '%s'\n", buf);
896
897
  strtimespec_iso(&ts, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
898
  printf("Zone: '%s'\n", buf);
899
900
  strtimespec_relative(&ts, buf, sizeof(buf));
901
  printf("Rel:  '%s'\n", buf);
902
903
  return EXIT_SUCCESS;
904
}
905
906
#endif /* TEST_PROGRAM_TIMEUTILS */