/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(<m); |
431 | | /* Check if mktime returning -1 is an error or a valid time_t */ |
432 | | if (lt == (time_t) -1) { |
433 | | if (! localtime_r(<, &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(<, >m)) |
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 */ |