]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/timeutils.c
lscpu: fix variable shadowing
[thirdparty/util-linux.git] / lib / timeutils.c
1 /***
2 First set of functions in this file are part of systemd, and were
3 copied to util-linux at August 2013.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with util-linux; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <assert.h>
22 #include <ctype.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <sys/time.h>
27
28 #include "c.h"
29 #include "nls.h"
30 #include "strutils.h"
31 #include "timeutils.h"
32
33 #define WHITESPACE " \t\n\r"
34
35 #define streq(a,b) (strcmp((a),(b)) == 0)
36
37 static int parse_sec(const char *t, usec_t *usec)
38 {
39 static const struct {
40 const char *suffix;
41 usec_t usec;
42 } table[] = {
43 { "seconds", USEC_PER_SEC },
44 { "second", USEC_PER_SEC },
45 { "sec", USEC_PER_SEC },
46 { "s", USEC_PER_SEC },
47 { "minutes", USEC_PER_MINUTE },
48 { "minute", USEC_PER_MINUTE },
49 { "min", USEC_PER_MINUTE },
50 { "months", USEC_PER_MONTH },
51 { "month", USEC_PER_MONTH },
52 { "msec", USEC_PER_MSEC },
53 { "ms", USEC_PER_MSEC },
54 { "m", USEC_PER_MINUTE },
55 { "hours", USEC_PER_HOUR },
56 { "hour", USEC_PER_HOUR },
57 { "hr", USEC_PER_HOUR },
58 { "h", USEC_PER_HOUR },
59 { "days", USEC_PER_DAY },
60 { "day", USEC_PER_DAY },
61 { "d", USEC_PER_DAY },
62 { "weeks", USEC_PER_WEEK },
63 { "week", USEC_PER_WEEK },
64 { "w", USEC_PER_WEEK },
65 { "years", USEC_PER_YEAR },
66 { "year", USEC_PER_YEAR },
67 { "y", USEC_PER_YEAR },
68 { "usec", 1ULL },
69 { "us", 1ULL },
70 { "", USEC_PER_SEC }, /* default is sec */
71 };
72
73 const char *p;
74 usec_t r = 0;
75 int something = FALSE;
76
77 assert(t);
78 assert(usec);
79
80 p = t;
81 for (;;) {
82 long long l, z = 0;
83 char *e;
84 unsigned i, n = 0;
85
86 p += strspn(p, WHITESPACE);
87
88 if (*p == 0) {
89 if (!something)
90 return -EINVAL;
91
92 break;
93 }
94
95 errno = 0;
96 l = strtoll(p, &e, 10);
97
98 if (errno > 0)
99 return -errno;
100
101 if (l < 0)
102 return -ERANGE;
103
104 if (*e == '.') {
105 char *b = e + 1;
106
107 errno = 0;
108 z = strtoll(b, &e, 10);
109 if (errno > 0)
110 return -errno;
111
112 if (z < 0)
113 return -ERANGE;
114
115 if (e == b)
116 return -EINVAL;
117
118 n = e - b;
119
120 } else if (e == p)
121 return -EINVAL;
122
123 e += strspn(e, WHITESPACE);
124
125 for (i = 0; i < ARRAY_SIZE(table); i++)
126 if (startswith(e, table[i].suffix)) {
127 usec_t k = (usec_t) z * table[i].usec;
128
129 for (; n > 0; n--)
130 k /= 10;
131
132 r += (usec_t) l *table[i].usec + k;
133 p = e + strlen(table[i].suffix);
134
135 something = TRUE;
136 break;
137 }
138
139 if (i >= ARRAY_SIZE(table))
140 return -EINVAL;
141
142 }
143
144 *usec = r;
145
146 return 0;
147 }
148
149 int parse_timestamp(const char *t, usec_t *usec)
150 {
151 static const struct {
152 const char *name;
153 const int nr;
154 } day_nr[] = {
155 { "Sunday", 0 },
156 { "Sun", 0 },
157 { "Monday", 1 },
158 { "Mon", 1 },
159 { "Tuesday", 2 },
160 { "Tue", 2 },
161 { "Wednesday", 3 },
162 { "Wed", 3 },
163 { "Thursday", 4 },
164 { "Thu", 4 },
165 { "Friday", 5 },
166 { "Fri", 5 },
167 { "Saturday", 6 },
168 { "Sat", 6 },
169 };
170
171 const char *k;
172 struct tm tm, copy;
173 time_t x;
174 usec_t plus = 0, minus = 0, ret;
175 int r, weekday = -1;
176 unsigned i;
177
178 /*
179 * Allowed syntaxes:
180 *
181 * 2012-09-22 16:34:22
182 * 2012-09-22T16:34:22
183 * 2012-09-22 16:34 (seconds will be set to 0)
184 * 2012-09-22 (time will be set to 00:00:00)
185 * 16:34:22 (date will be set to today)
186 * 16:34 (date will be set to today, seconds to 0)
187 * now
188 * yesterday (time is set to 00:00:00)
189 * today (time is set to 00:00:00)
190 * tomorrow (time is set to 00:00:00)
191 * +5min
192 * -5days
193 *
194 */
195
196 assert(t);
197 assert(usec);
198
199 x = time(NULL);
200 localtime_r(&x, &tm);
201 tm.tm_isdst = -1;
202
203 if (streq(t, "now"))
204 goto finish;
205
206 else if (streq(t, "today")) {
207 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
208 goto finish;
209
210 } else if (streq(t, "yesterday")) {
211 tm.tm_mday--;
212 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
213 goto finish;
214
215 } else if (streq(t, "tomorrow")) {
216 tm.tm_mday++;
217 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
218 goto finish;
219
220 } else if (t[0] == '+') {
221
222 r = parse_sec(t + 1, &plus);
223 if (r < 0)
224 return r;
225
226 goto finish;
227 } else if (t[0] == '-') {
228
229 r = parse_sec(t + 1, &minus);
230 if (r < 0)
231 return r;
232
233 goto finish;
234
235 } else if (endswith(t, " ago")) {
236 char *z;
237
238 z = strndup(t, strlen(t) - 4);
239 if (!z)
240 return -ENOMEM;
241
242 r = parse_sec(z, &minus);
243 free(z);
244 if (r < 0)
245 return r;
246
247 goto finish;
248 }
249
250 for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
251 size_t skip;
252
253 if (!startswith_no_case(t, day_nr[i].name))
254 continue;
255
256 skip = strlen(day_nr[i].name);
257 if (t[skip] != ' ')
258 continue;
259
260 weekday = day_nr[i].nr;
261 t += skip + 1;
262 break;
263 }
264
265 copy = tm;
266 k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
267 if (k && *k == 0)
268 goto finish;
269
270 tm = copy;
271 k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
272 if (k && *k == 0)
273 goto finish;
274
275 tm = copy;
276 k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
277 if (k && *k == 0)
278 goto finish;
279
280 tm = copy;
281 k = strptime(t, "%y-%m-%d %H:%M", &tm);
282 if (k && *k == 0) {
283 tm.tm_sec = 0;
284 goto finish;
285 }
286
287 tm = copy;
288 k = strptime(t, "%Y-%m-%d %H:%M", &tm);
289 if (k && *k == 0) {
290 tm.tm_sec = 0;
291 goto finish;
292 }
293
294 tm = copy;
295 k = strptime(t, "%y-%m-%d", &tm);
296 if (k && *k == 0) {
297 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
298 goto finish;
299 }
300
301 tm = copy;
302 k = strptime(t, "%Y-%m-%d", &tm);
303 if (k && *k == 0) {
304 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
305 goto finish;
306 }
307
308 tm = copy;
309 k = strptime(t, "%H:%M:%S", &tm);
310 if (k && *k == 0)
311 goto finish;
312
313 tm = copy;
314 k = strptime(t, "%H:%M", &tm);
315 if (k && *k == 0) {
316 tm.tm_sec = 0;
317 goto finish;
318 }
319
320 tm = copy;
321 k = strptime(t, "%Y%m%d%H%M%S", &tm);
322 if (k && *k == 0) {
323 tm.tm_sec = 0;
324 goto finish;
325 }
326
327 return -EINVAL;
328
329 finish:
330 x = mktime(&tm);
331 if (x == (time_t)-1)
332 return -EINVAL;
333
334 if (weekday >= 0 && tm.tm_wday != weekday)
335 return -EINVAL;
336
337 ret = (usec_t) x *USEC_PER_SEC;
338
339 ret += plus;
340 if (ret > minus)
341 ret -= minus;
342 else
343 ret = 0;
344
345 *usec = ret;
346
347 return 0;
348 }
349
350 /* Returns the difference in seconds between its argument and GMT. If if TP is
351 * invalid or no DST information is available default to UTC, that is, zero.
352 * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected.
353 * Derived from glibc/time/strftime_l.c
354 */
355 int get_gmtoff(const struct tm *tp)
356 {
357 if (tp->tm_isdst < 0)
358 return 0;
359
360 #if HAVE_TM_GMTOFF
361 return tp->tm_gmtoff;
362 #else
363 struct tm tm;
364 struct tm gtm;
365 struct tm ltm = *tp;
366 time_t lt;
367
368 tzset();
369 lt = mktime(&ltm);
370 /* Check if mktime returning -1 is an error or a valid time_t */
371 if (lt == (time_t) -1) {
372 if (! localtime_r(&lt, &tm)
373 || ((ltm.tm_sec ^ tm.tm_sec)
374 | (ltm.tm_min ^ tm.tm_min)
375 | (ltm.tm_hour ^ tm.tm_hour)
376 | (ltm.tm_mday ^ tm.tm_mday)
377 | (ltm.tm_mon ^ tm.tm_mon)
378 | (ltm.tm_year ^ tm.tm_year)))
379 return 0;
380 }
381
382 if (! gmtime_r(&lt, &gtm))
383 return 0;
384
385 /* Calculate the GMT offset, that is, the difference between the
386 * TP argument (ltm) and GMT (gtm).
387 *
388 * Compute intervening leap days correctly even if year is negative.
389 * Take care to avoid int overflow in leap day calculations, but it's OK
390 * to assume that A and B are close to each other.
391 */
392 int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3);
393 int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3);
394 int a100 = a4 / 25 - (a4 % 25 < 0);
395 int b100 = b4 / 25 - (b4 % 25 < 0);
396 int a400 = a100 >> 2;
397 int b400 = b100 >> 2;
398 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
399
400 int years = ltm.tm_year - gtm.tm_year;
401 int days = (365 * years + intervening_leap_days
402 + (ltm.tm_yday - gtm.tm_yday));
403
404 return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour))
405 + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec));
406 #endif
407 }
408
409 static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz)
410 {
411 char *p = buf;
412 int len;
413
414 if (flags & ISO_DATE) {
415 len = snprintf(p, bufsz, "%4ld-%.2d-%.2d",
416 tm->tm_year + (long) 1900,
417 tm->tm_mon + 1, tm->tm_mday);
418 if (len < 0 || (size_t) len > bufsz)
419 goto err;
420 bufsz -= len;
421 p += len;
422 }
423
424 if ((flags & ISO_DATE) && (flags & ISO_TIME)) {
425 if (bufsz < 1)
426 goto err;
427 *p++ = (flags & ISO_T) ? 'T' : ' ';
428 bufsz--;
429 }
430
431 if (flags & ISO_TIME) {
432 len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour,
433 tm->tm_min, tm->tm_sec);
434 if (len < 0 || (size_t) len > bufsz)
435 goto err;
436 bufsz -= len;
437 p += len;
438 }
439
440 if (flags & ISO_DOTUSEC) {
441 len = snprintf(p, bufsz, ".%06ld", (long) usec);
442 if (len < 0 || (size_t) len > bufsz)
443 goto err;
444 bufsz -= len;
445 p += len;
446
447 } else if (flags & ISO_COMMAUSEC) {
448 len = snprintf(p, bufsz, ",%06ld", (long) usec);
449 if (len < 0 || (size_t) len > bufsz)
450 goto err;
451 bufsz -= len;
452 p += len;
453 }
454
455 if (flags & ISO_TIMEZONE) {
456 int tmin = get_gmtoff(tm) / 60;
457 int zhour = tmin / 60;
458 int zmin = abs(tmin % 60);
459 len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
460 if (len < 0 || (size_t) len > bufsz)
461 goto err;
462 }
463 return 0;
464 err:
465 warnx(_("format_iso_time: buffer overflow."));
466 return -1;
467 }
468
469 /* timeval to ISO 8601 */
470 int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz)
471 {
472 struct tm tm;
473 struct tm *rc;
474
475 if (flags & ISO_GMTIME)
476 rc = gmtime_r(&tv->tv_sec, &tm);
477 else
478 rc = localtime_r(&tv->tv_sec, &tm);
479
480 if (rc)
481 return format_iso_time(&tm, tv->tv_usec, flags, buf, bufsz);
482
483 warnx(_("time %ld is out of range."), tv->tv_sec);
484 return -1;
485 }
486
487 /* struct tm to ISO 8601 */
488 int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz)
489 {
490 return format_iso_time(tm, 0, flags, buf, bufsz);
491 }
492
493 /* time_t to ISO 8601 */
494 int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz)
495 {
496 struct tm tm;
497 struct tm *rc;
498
499 if (flags & ISO_GMTIME)
500 rc = gmtime_r(t, &tm);
501 else
502 rc = localtime_r(t, &tm);
503
504 if (rc)
505 return format_iso_time(&tm, 0, flags, buf, bufsz);
506
507 warnx(_("time %ld is out of range."), (long)t);
508 return -1;
509 }
510
511 /* relative time functions */
512 static inline int time_is_thisyear(struct tm const *const tm,
513 struct tm const *const tmnow)
514 {
515 return tm->tm_year == tmnow->tm_year;
516 }
517
518 static inline int time_is_today(struct tm const *const tm,
519 struct tm const *const tmnow)
520 {
521 return (tm->tm_yday == tmnow->tm_yday &&
522 time_is_thisyear(tm, tmnow));
523 }
524
525 int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz)
526 {
527 struct tm tm, tmnow;
528 int rc = 0;
529
530 if (now->tv_sec == 0)
531 gettimeofday(now, NULL);
532
533 localtime_r(t, &tm);
534 localtime_r(&now->tv_sec, &tmnow);
535
536 if (time_is_today(&tm, &tmnow)) {
537 rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min);
538 if (rc < 0 || (size_t) rc > bufsz)
539 return -1;
540 rc = 1;
541
542 } else if (time_is_thisyear(&tm, &tmnow)) {
543 if (flags & UL_SHORTTIME_THISYEAR_HHMM)
544 rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm);
545 else
546 rc = strftime(buf, bufsz, "%b%d", &tm);
547 } else
548 rc = strftime(buf, bufsz, "%Y-%b%d", &tm);
549
550 return rc <= 0 ? -1 : 0;
551 }
552
553 #ifndef HAVE_TIMEGM
554 time_t timegm(struct tm *tm)
555 {
556 const char *zone = getenv("TZ");
557 time_t ret;
558
559 setenv("TZ", "", 1);
560 tzset();
561 ret = mktime(tm);
562 if (zone)
563 setenv("TZ", zone, 1);
564 else
565 unsetenv("TZ");
566 tzset();
567 return ret;
568 }
569 #endif /* HAVE_TIMEGM */
570
571 #ifdef TEST_PROGRAM_TIMEUTILS
572
573 int main(int argc, char *argv[])
574 {
575 struct timeval tv = { 0 };
576 char buf[ISO_BUFSIZ];
577
578 if (argc < 2) {
579 fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>]\n", argv[0]);
580 exit(EXIT_FAILURE);
581 }
582
583 if (strcmp(argv[1], "--timestamp") == 0) {
584 usec_t usec = 0;
585
586 parse_timestamp(argv[2], &usec);
587 tv.tv_sec = (time_t) (usec / 1000000);
588 tv.tv_usec = usec % 1000000;
589 } else {
590 tv.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
591 if (argc == 3)
592 tv.tv_usec = strtos64_or_err(argv[2], "failed to parse <usec>");
593 }
594
595 strtimeval_iso(&tv, ISO_DATE, buf, sizeof(buf));
596 printf("Date: '%s'\n", buf);
597
598 strtimeval_iso(&tv, ISO_TIME, buf, sizeof(buf));
599 printf("Time: '%s'\n", buf);
600
601 strtimeval_iso(&tv, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
602 buf, sizeof(buf));
603 printf("Full: '%s'\n", buf);
604
605 strtimeval_iso(&tv, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
606 printf("Zone: '%s'\n", buf);
607
608 return EXIT_SUCCESS;
609 }
610
611 #endif /* TEST_PROGRAM_TIMEUTILS */