chiark / gitweb /
util: make time formatting a bit smarter
[elogind.git] / src / shared / time-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <time.h>
23 #include <string.h>
24
25 #include "util.h"
26 #include "time-util.h"
27
28 usec_t now(clockid_t clock_id) {
29         struct timespec ts;
30
31         assert_se(clock_gettime(clock_id, &ts) == 0);
32
33         return timespec_load(&ts);
34 }
35
36 dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
37         assert(ts);
38
39         ts->realtime = now(CLOCK_REALTIME);
40         ts->monotonic = now(CLOCK_MONOTONIC);
41
42         return ts;
43 }
44
45 dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
46         int64_t delta;
47         assert(ts);
48
49         if (u == (usec_t) -1) {
50                 ts->realtime = ts->monotonic = (usec_t) -1;
51                 return ts;
52         }
53
54         ts->realtime = u;
55
56         if (u == 0)
57                 ts->monotonic = 0;
58         else {
59                 delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
60
61                 ts->monotonic = now(CLOCK_MONOTONIC);
62
63                 if ((int64_t) ts->monotonic > delta)
64                         ts->monotonic -= delta;
65                 else
66                         ts->monotonic = 0;
67         }
68
69         return ts;
70 }
71
72 dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
73         int64_t delta;
74         assert(ts);
75
76         if (u == (usec_t) -1) {
77                 ts->realtime = ts->monotonic = (usec_t) -1;
78                 return ts;
79         }
80
81         ts->monotonic = u;
82         delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
83
84         ts->realtime = now(CLOCK_REALTIME);
85         if ((int64_t) ts->realtime > delta)
86                 ts->realtime -= delta;
87         else
88                 ts->realtime = 0;
89
90         return ts;
91 }
92
93 usec_t timespec_load(const struct timespec *ts) {
94         assert(ts);
95
96         if (ts->tv_sec == (time_t) -1 &&
97             ts->tv_nsec == (long) -1)
98                 return (usec_t) -1;
99
100         if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
101                 return (usec_t) -1;
102
103         return
104                 (usec_t) ts->tv_sec * USEC_PER_SEC +
105                 (usec_t) ts->tv_nsec / NSEC_PER_USEC;
106 }
107
108 struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
109         assert(ts);
110
111         if (u == (usec_t) -1) {
112                 ts->tv_sec = (time_t) -1;
113                 ts->tv_nsec = (long) -1;
114                 return ts;
115         }
116
117         ts->tv_sec = (time_t) (u / USEC_PER_SEC);
118         ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
119
120         return ts;
121 }
122
123 usec_t timeval_load(const struct timeval *tv) {
124         assert(tv);
125
126         if (tv->tv_sec == (time_t) -1 &&
127             tv->tv_usec == (suseconds_t) -1)
128                 return (usec_t) -1;
129
130         if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
131                 return (usec_t) -1;
132
133         return
134                 (usec_t) tv->tv_sec * USEC_PER_SEC +
135                 (usec_t) tv->tv_usec;
136 }
137
138 struct timeval *timeval_store(struct timeval *tv, usec_t u) {
139         assert(tv);
140
141         if (u == (usec_t) -1) {
142                 tv->tv_sec = (time_t) -1;
143                 tv->tv_usec = (suseconds_t) -1;
144                 return tv;
145         }
146
147         tv->tv_sec = (time_t) (u / USEC_PER_SEC);
148         tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
149
150         return tv;
151 }
152
153 char *format_timestamp(char *buf, size_t l, usec_t t) {
154         struct tm tm;
155         time_t sec;
156
157         assert(buf);
158         assert(l > 0);
159
160         if (t <= 0)
161                 return NULL;
162
163         sec = (time_t) (t / USEC_PER_SEC);
164
165         if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0)
166                 return NULL;
167
168         return buf;
169 }
170
171 char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
172         usec_t n, d;
173
174         n = now(CLOCK_REALTIME);
175
176         if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t)
177                 return NULL;
178
179         d = n - t;
180
181         if (d >= USEC_PER_YEAR)
182                 snprintf(buf, l, "%llu years %llu months ago",
183                          (unsigned long long) (d / USEC_PER_YEAR),
184                          (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH));
185         else if (d >= USEC_PER_MONTH)
186                 snprintf(buf, l, "%llu months %llu days ago",
187                          (unsigned long long) (d / USEC_PER_MONTH),
188                          (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY));
189         else if (d >= USEC_PER_WEEK)
190                 snprintf(buf, l, "%llu weeks %llu days ago",
191                          (unsigned long long) (d / USEC_PER_WEEK),
192                          (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY));
193         else if (d >= 2*USEC_PER_DAY)
194                 snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY));
195         else if (d >= 25*USEC_PER_HOUR)
196                 snprintf(buf, l, "1 day %lluh ago",
197                          (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR));
198         else if (d >= 6*USEC_PER_HOUR)
199                 snprintf(buf, l, "%lluh ago",
200                          (unsigned long long) (d / USEC_PER_HOUR));
201         else if (d >= USEC_PER_HOUR)
202                 snprintf(buf, l, "%lluh %llumin ago",
203                          (unsigned long long) (d / USEC_PER_HOUR),
204                          (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE));
205         else if (d >= 5*USEC_PER_MINUTE)
206                 snprintf(buf, l, "%llumin ago",
207                          (unsigned long long) (d / USEC_PER_MINUTE));
208         else if (d >= USEC_PER_MINUTE)
209                 snprintf(buf, l, "%llumin %llus ago",
210                          (unsigned long long) (d / USEC_PER_MINUTE),
211                          (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC));
212         else if (d >= USEC_PER_SEC)
213                 snprintf(buf, l, "%llus ago",
214                          (unsigned long long) (d / USEC_PER_SEC));
215         else if (d >= USEC_PER_MSEC)
216                 snprintf(buf, l, "%llums ago",
217                          (unsigned long long) (d / USEC_PER_MSEC));
218         else if (d > 0)
219                 snprintf(buf, l, "%lluus ago",
220                          (unsigned long long) d);
221         else
222                 snprintf(buf, l, "now");
223
224         buf[l-1] = 0;
225         return buf;
226 }
227
228 char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
229         static const struct {
230                 const char *suffix;
231                 usec_t usec;
232         } table[] = {
233                 { "y", USEC_PER_YEAR },
234                 { "month", USEC_PER_MONTH },
235                 { "w", USEC_PER_WEEK },
236                 { "d", USEC_PER_DAY },
237                 { "h", USEC_PER_HOUR },
238                 { "min", USEC_PER_MINUTE },
239                 { "s", USEC_PER_SEC },
240                 { "ms", USEC_PER_MSEC },
241                 { "us", 1 },
242         };
243
244         unsigned i;
245         char *p = buf;
246         bool something = false;
247
248         assert(buf);
249         assert(l > 0);
250
251         if (t == (usec_t) -1)
252                 return NULL;
253
254         /* The result of this function can be parsed with parse_sec */
255
256         for (i = 0; i < ELEMENTSOF(table); i++) {
257                 int k;
258                 size_t n;
259                 bool done = false;
260                 usec_t a, b;
261
262                 if (t == 0 || t < accuracy) {
263                         if (!something) {
264                                 snprintf(p, l, "0");
265                                 p[l-1] = 0;
266                                 return p;
267                         }
268
269                         break;
270                 }
271
272                 if (t < table[i].usec)
273                         continue;
274
275                 if (l <= 1)
276                         break;
277
278                 a = t / table[i].usec;
279                 b = t % table[i].usec;
280
281                 /* Let's see if we should shows this in dot notation */
282                 if (t < USEC_PER_MINUTE && b > 0) {
283                         usec_t cc;
284                         int j;
285
286                         j = 0;
287                         for (cc = table[i].usec; cc > 1; cc /= 10)
288                                 j++;
289
290                         for (cc = accuracy; cc > 1; cc /= 10) {
291                                 b /= 10;
292                                 j--;
293                         }
294
295                         if (j > 0) {
296                                 k = snprintf(p, l,
297                                              "%s%llu.%0*llu%s",
298                                              p > buf ? " " : "",
299                                              (unsigned long long) a,
300                                              j,
301                                              (unsigned long long) b,
302                                              table[i].suffix);
303
304                                 t = 0;
305                                 done = true;
306                         }
307                 }
308
309                 /* No? Then let's show it normally */
310                 if (!done) {
311                         k = snprintf(p, l,
312                                      "%s%llu%s",
313                                      p > buf ? " " : "",
314                                      (unsigned long long) a,
315                                      table[i].suffix);
316
317                         t = b;
318                 }
319
320                 n = MIN((size_t) k, l);
321
322                 l -= n;
323                 p += n;
324
325
326                 something = true;
327         }
328
329         *p = 0;
330
331         return buf;
332 }
333
334 void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
335
336         assert(f);
337         assert(name);
338         assert(t);
339
340         if (!dual_timestamp_is_set(t))
341                 return;
342
343         fprintf(f, "%s=%llu %llu\n",
344                 name,
345                 (unsigned long long) t->realtime,
346                 (unsigned long long) t->monotonic);
347 }
348
349 void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
350         unsigned long long a, b;
351
352         assert(value);
353         assert(t);
354
355         if (sscanf(value, "%lli %llu", &a, &b) != 2)
356                 log_debug("Failed to parse finish timestamp value %s", value);
357         else {
358                 t->realtime = a;
359                 t->monotonic = b;
360         }
361 }
362
363 int parse_timestamp(const char *t, usec_t *usec) {
364         static const struct {
365                 const char *name;
366                 const int nr;
367         } day_nr[] = {
368                 { "Sunday",    0 },
369                 { "Sun",       0 },
370                 { "Monday",    1 },
371                 { "Mon",       1 },
372                 { "Tuesday",   2 },
373                 { "Tue",       2 },
374                 { "Wednesday", 3 },
375                 { "Wed",       3 },
376                 { "Thursday",  4 },
377                 { "Thu",       4 },
378                 { "Friday",    5 },
379                 { "Fri",       5 },
380                 { "Saturday",  6 },
381                 { "Sat",       6 },
382         };
383
384         const char *k;
385         struct tm tm, copy;
386         time_t x;
387         usec_t plus = 0, minus = 0, ret;
388         int r, weekday = -1;
389         unsigned i;
390
391         /*
392          * Allowed syntaxes:
393          *
394          *   2012-09-22 16:34:22
395          *   2012-09-22 16:34     (seconds will be set to 0)
396          *   2012-09-22           (time will be set to 00:00:00)
397          *   16:34:22             (date will be set to today)
398          *   16:34                (date will be set to today, seconds to 0)
399          *   now
400          *   yesterday            (time is set to 00:00:00)
401          *   today                (time is set to 00:00:00)
402          *   tomorrow             (time is set to 00:00:00)
403          *   +5min
404          *   -5days
405          *
406          */
407
408         assert(t);
409         assert(usec);
410
411         x = time(NULL);
412         assert_se(localtime_r(&x, &tm));
413         tm.tm_isdst = -1;
414
415         if (streq(t, "now"))
416                 goto finish;
417
418         else if (streq(t, "today")) {
419                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
420                 goto finish;
421
422         } else if (streq(t, "yesterday")) {
423                 tm.tm_mday --;
424                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
425                 goto finish;
426
427         } else if (streq(t, "tomorrow")) {
428                 tm.tm_mday ++;
429                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
430                 goto finish;
431
432         } else if (t[0] == '+') {
433
434                 r = parse_sec(t+1, &plus);
435                 if (r < 0)
436                         return r;
437
438                 goto finish;
439         } else if (t[0] == '-') {
440
441                 r = parse_sec(t+1, &minus);
442                 if (r < 0)
443                         return r;
444
445                 goto finish;
446
447         } else if (endswith(t, " ago")) {
448                 _cleanup_free_ char *z;
449
450                 z = strndup(t, strlen(t) - 4);
451                 if (!z)
452                         return -ENOMEM;
453
454                 r = parse_sec(z, &minus);
455                 if (r < 0)
456                         return r;
457
458                 goto finish;
459         }
460
461         for (i = 0; i < ELEMENTSOF(day_nr); i++) {
462                 size_t skip;
463
464                 if (!startswith_no_case(t, day_nr[i].name))
465                         continue;
466
467                 skip = strlen(day_nr[i].name);
468                 if (t[skip] != ' ')
469                         continue;
470
471                 weekday = day_nr[i].nr;
472                 t += skip + 1;
473                 break;
474         }
475
476         copy = tm;
477         k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
478         if (k && *k == 0)
479                 goto finish;
480
481         tm = copy;
482         k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
483         if (k && *k == 0)
484                 goto finish;
485
486         tm = copy;
487         k = strptime(t, "%y-%m-%d %H:%M", &tm);
488         if (k && *k == 0) {
489                 tm.tm_sec = 0;
490                 goto finish;
491         }
492
493         tm = copy;
494         k = strptime(t, "%Y-%m-%d %H:%M", &tm);
495         if (k && *k == 0) {
496                 tm.tm_sec = 0;
497                 goto finish;
498         }
499
500         tm = copy;
501         k = strptime(t, "%y-%m-%d", &tm);
502         if (k && *k == 0) {
503                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
504                 goto finish;
505         }
506
507         tm = copy;
508         k = strptime(t, "%Y-%m-%d", &tm);
509         if (k && *k == 0) {
510                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
511                 goto finish;
512         }
513
514         tm = copy;
515         k = strptime(t, "%H:%M:%S", &tm);
516         if (k && *k == 0)
517                 goto finish;
518
519         tm = copy;
520         k = strptime(t, "%H:%M", &tm);
521         if (k && *k == 0) {
522                 tm.tm_sec = 0;
523                 goto finish;
524         }
525
526         return -EINVAL;
527
528 finish:
529         x = mktime(&tm);
530         if (x == (time_t) -1)
531                 return -EINVAL;
532
533         if (weekday >= 0 && tm.tm_wday != weekday)
534                 return -EINVAL;
535
536         ret = (usec_t) x * USEC_PER_SEC;
537
538         ret += plus;
539         if (ret > minus)
540                 ret -= minus;
541         else
542                 ret = 0;
543
544         *usec = ret;
545
546         return 0;
547 }
548
549 int parse_sec(const char *t, usec_t *usec) {
550         static const struct {
551                 const char *suffix;
552                 usec_t usec;
553         } table[] = {
554                 { "seconds", USEC_PER_SEC },
555                 { "second", USEC_PER_SEC },
556                 { "sec", USEC_PER_SEC },
557                 { "s", USEC_PER_SEC },
558                 { "minutes", USEC_PER_MINUTE },
559                 { "minute", USEC_PER_MINUTE },
560                 { "min", USEC_PER_MINUTE },
561                 { "months", USEC_PER_MONTH },
562                 { "month", USEC_PER_MONTH },
563                 { "msec", USEC_PER_MSEC },
564                 { "ms", USEC_PER_MSEC },
565                 { "m", USEC_PER_MINUTE },
566                 { "hours", USEC_PER_HOUR },
567                 { "hour", USEC_PER_HOUR },
568                 { "hr", USEC_PER_HOUR },
569                 { "h", USEC_PER_HOUR },
570                 { "days", USEC_PER_DAY },
571                 { "day", USEC_PER_DAY },
572                 { "d", USEC_PER_DAY },
573                 { "weeks", USEC_PER_WEEK },
574                 { "week", USEC_PER_WEEK },
575                 { "w", USEC_PER_WEEK },
576                 { "years", USEC_PER_YEAR },
577                 { "year", USEC_PER_YEAR },
578                 { "y", USEC_PER_YEAR },
579                 { "usec", 1ULL },
580                 { "us", 1ULL },
581                 { "", USEC_PER_SEC }, /* default is sec */
582         };
583
584         const char *p;
585         usec_t r = 0;
586         bool something = false;
587
588         assert(t);
589         assert(usec);
590
591         p = t;
592         for (;;) {
593                 long long l, z = 0;
594                 char *e;
595                 unsigned i, n = 0;
596
597                 p += strspn(p, WHITESPACE);
598
599                 if (*p == 0) {
600                         if (!something)
601                                 return -EINVAL;
602
603                         break;
604                 }
605
606                 errno = 0;
607                 l = strtoll(p, &e, 10);
608
609                 if (errno > 0)
610                         return -errno;
611
612                 if (l < 0)
613                         return -ERANGE;
614
615                 if (*e == '.') {
616                         char *b = e + 1;
617
618                         errno = 0;
619                         z = strtoll(b, &e, 10);
620                         if (errno > 0)
621                                 return -errno;
622
623                         if (z < 0)
624                                 return -ERANGE;
625
626                         if (e == b)
627                                 return -EINVAL;
628
629                         n = e - b;
630
631                 } else if (e == p)
632                         return -EINVAL;
633
634                 e += strspn(e, WHITESPACE);
635
636                 for (i = 0; i < ELEMENTSOF(table); i++)
637                         if (startswith(e, table[i].suffix)) {
638                                 usec_t k = (usec_t) z * table[i].usec;
639
640                                 for (; n > 0; n--)
641                                         k /= 10;
642
643                                 r += (usec_t) l * table[i].usec + k;
644                                 p = e + strlen(table[i].suffix);
645
646                                 something = true;
647                                 break;
648                         }
649
650                 if (i >= ELEMENTSOF(table))
651                         return -EINVAL;
652
653         }
654
655         *usec = r;
656
657         return 0;
658 }
659
660 int parse_nsec(const char *t, nsec_t *nsec) {
661         static const struct {
662                 const char *suffix;
663                 nsec_t nsec;
664         } table[] = {
665                 { "seconds", NSEC_PER_SEC },
666                 { "second", NSEC_PER_SEC },
667                 { "sec", NSEC_PER_SEC },
668                 { "s", NSEC_PER_SEC },
669                 { "minutes", NSEC_PER_MINUTE },
670                 { "minute", NSEC_PER_MINUTE },
671                 { "min", NSEC_PER_MINUTE },
672                 { "months", NSEC_PER_MONTH },
673                 { "month", NSEC_PER_MONTH },
674                 { "msec", NSEC_PER_MSEC },
675                 { "ms", NSEC_PER_MSEC },
676                 { "m", NSEC_PER_MINUTE },
677                 { "hours", NSEC_PER_HOUR },
678                 { "hour", NSEC_PER_HOUR },
679                 { "hr", NSEC_PER_HOUR },
680                 { "h", NSEC_PER_HOUR },
681                 { "days", NSEC_PER_DAY },
682                 { "day", NSEC_PER_DAY },
683                 { "d", NSEC_PER_DAY },
684                 { "weeks", NSEC_PER_WEEK },
685                 { "week", NSEC_PER_WEEK },
686                 { "w", NSEC_PER_WEEK },
687                 { "years", NSEC_PER_YEAR },
688                 { "year", NSEC_PER_YEAR },
689                 { "y", NSEC_PER_YEAR },
690                 { "usec", NSEC_PER_USEC },
691                 { "us", NSEC_PER_USEC },
692                 { "nsec", 1ULL },
693                 { "ns", 1ULL },
694                 { "", 1ULL }, /* default is nsec */
695         };
696
697         const char *p;
698         nsec_t r = 0;
699         bool something = false;
700
701         assert(t);
702         assert(nsec);
703
704         p = t;
705         for (;;) {
706                 long long l, z = 0;
707                 char *e;
708                 unsigned i, n = 0;
709
710                 p += strspn(p, WHITESPACE);
711
712                 if (*p == 0) {
713                         if (!something)
714                                 return -EINVAL;
715
716                         break;
717                 }
718
719                 errno = 0;
720                 l = strtoll(p, &e, 10);
721
722                 if (errno > 0)
723                         return -errno;
724
725                 if (l < 0)
726                         return -ERANGE;
727
728                 if (*e == '.') {
729                         char *b = e + 1;
730
731                         errno = 0;
732                         z = strtoll(b, &e, 10);
733                         if (errno > 0)
734                                 return -errno;
735
736                         if (z < 0)
737                                 return -ERANGE;
738
739                         if (e == b)
740                                 return -EINVAL;
741
742                         n = e - b;
743
744                 } else if (e == p)
745                         return -EINVAL;
746
747                 e += strspn(e, WHITESPACE);
748
749                 for (i = 0; i < ELEMENTSOF(table); i++)
750                         if (startswith(e, table[i].suffix)) {
751                                 nsec_t k = (nsec_t) z * table[i].nsec;
752
753                                 for (; n > 0; n--)
754                                         k /= 10;
755
756                                 r += (nsec_t) l * table[i].nsec + k;
757                                 p = e + strlen(table[i].suffix);
758
759                                 something = true;
760                                 break;
761                         }
762
763                 if (i >= ELEMENTSOF(table))
764                         return -EINVAL;
765
766         }
767
768         *nsec = r;
769
770         return 0;
771 }