chiark / gitweb /
util: tweak format_timespan() a bit
[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         if (t <= 0) {
255                 snprintf(p, l, "0");
256                 p[l-1] = 0;
257                 return p;
258         }
259
260         /* The result of this function can be parsed with parse_sec */
261
262         for (i = 0; i < ELEMENTSOF(table); i++) {
263                 int k;
264                 size_t n;
265                 bool done = false;
266                 usec_t a, b;
267
268                 if (t <= 0)
269                         break;
270
271                 if (t < accuracy && something)
272                         break;
273
274                 if (t < table[i].usec)
275                         continue;
276
277                 if (l <= 1)
278                         break;
279
280                 a = t / table[i].usec;
281                 b = t % table[i].usec;
282
283                 /* Let's see if we should shows this in dot notation */
284                 if (t < USEC_PER_MINUTE && b > 0) {
285                         usec_t cc;
286                         int j;
287
288                         j = 0;
289                         for (cc = table[i].usec; cc > 1; cc /= 10)
290                                 j++;
291
292                         for (cc = accuracy; cc > 1; cc /= 10) {
293                                 b /= 10;
294                                 j--;
295                         }
296
297                         if (j > 0) {
298                                 k = snprintf(p, l,
299                                              "%s%llu.%0*llu%s",
300                                              p > buf ? " " : "",
301                                              (unsigned long long) a,
302                                              j,
303                                              (unsigned long long) b,
304                                              table[i].suffix);
305
306                                 t = 0;
307                                 done = true;
308                         }
309                 }
310
311                 /* No? Then let's show it normally */
312                 if (!done) {
313                         k = snprintf(p, l,
314                                      "%s%llu%s",
315                                      p > buf ? " " : "",
316                                      (unsigned long long) a,
317                                      table[i].suffix);
318
319                         t = b;
320                 }
321
322                 n = MIN((size_t) k, l);
323
324                 l -= n;
325                 p += n;
326
327                 something = true;
328         }
329
330         *p = 0;
331
332         return buf;
333 }
334
335 void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
336
337         assert(f);
338         assert(name);
339         assert(t);
340
341         if (!dual_timestamp_is_set(t))
342                 return;
343
344         fprintf(f, "%s=%llu %llu\n",
345                 name,
346                 (unsigned long long) t->realtime,
347                 (unsigned long long) t->monotonic);
348 }
349
350 void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
351         unsigned long long a, b;
352
353         assert(value);
354         assert(t);
355
356         if (sscanf(value, "%lli %llu", &a, &b) != 2)
357                 log_debug("Failed to parse finish timestamp value %s", value);
358         else {
359                 t->realtime = a;
360                 t->monotonic = b;
361         }
362 }
363
364 int parse_timestamp(const char *t, usec_t *usec) {
365         static const struct {
366                 const char *name;
367                 const int nr;
368         } day_nr[] = {
369                 { "Sunday",    0 },
370                 { "Sun",       0 },
371                 { "Monday",    1 },
372                 { "Mon",       1 },
373                 { "Tuesday",   2 },
374                 { "Tue",       2 },
375                 { "Wednesday", 3 },
376                 { "Wed",       3 },
377                 { "Thursday",  4 },
378                 { "Thu",       4 },
379                 { "Friday",    5 },
380                 { "Fri",       5 },
381                 { "Saturday",  6 },
382                 { "Sat",       6 },
383         };
384
385         const char *k;
386         struct tm tm, copy;
387         time_t x;
388         usec_t plus = 0, minus = 0, ret;
389         int r, weekday = -1;
390         unsigned i;
391
392         /*
393          * Allowed syntaxes:
394          *
395          *   2012-09-22 16:34:22
396          *   2012-09-22 16:34     (seconds will be set to 0)
397          *   2012-09-22           (time will be set to 00:00:00)
398          *   16:34:22             (date will be set to today)
399          *   16:34                (date will be set to today, seconds to 0)
400          *   now
401          *   yesterday            (time is set to 00:00:00)
402          *   today                (time is set to 00:00:00)
403          *   tomorrow             (time is set to 00:00:00)
404          *   +5min
405          *   -5days
406          *
407          */
408
409         assert(t);
410         assert(usec);
411
412         x = time(NULL);
413         assert_se(localtime_r(&x, &tm));
414         tm.tm_isdst = -1;
415
416         if (streq(t, "now"))
417                 goto finish;
418
419         else if (streq(t, "today")) {
420                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
421                 goto finish;
422
423         } else if (streq(t, "yesterday")) {
424                 tm.tm_mday --;
425                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
426                 goto finish;
427
428         } else if (streq(t, "tomorrow")) {
429                 tm.tm_mday ++;
430                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
431                 goto finish;
432
433         } else if (t[0] == '+') {
434
435                 r = parse_sec(t+1, &plus);
436                 if (r < 0)
437                         return r;
438
439                 goto finish;
440         } else if (t[0] == '-') {
441
442                 r = parse_sec(t+1, &minus);
443                 if (r < 0)
444                         return r;
445
446                 goto finish;
447
448         } else if (endswith(t, " ago")) {
449                 _cleanup_free_ char *z;
450
451                 z = strndup(t, strlen(t) - 4);
452                 if (!z)
453                         return -ENOMEM;
454
455                 r = parse_sec(z, &minus);
456                 if (r < 0)
457                         return r;
458
459                 goto finish;
460         }
461
462         for (i = 0; i < ELEMENTSOF(day_nr); i++) {
463                 size_t skip;
464
465                 if (!startswith_no_case(t, day_nr[i].name))
466                         continue;
467
468                 skip = strlen(day_nr[i].name);
469                 if (t[skip] != ' ')
470                         continue;
471
472                 weekday = day_nr[i].nr;
473                 t += skip + 1;
474                 break;
475         }
476
477         copy = tm;
478         k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
479         if (k && *k == 0)
480                 goto finish;
481
482         tm = copy;
483         k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
484         if (k && *k == 0)
485                 goto finish;
486
487         tm = copy;
488         k = strptime(t, "%y-%m-%d %H:%M", &tm);
489         if (k && *k == 0) {
490                 tm.tm_sec = 0;
491                 goto finish;
492         }
493
494         tm = copy;
495         k = strptime(t, "%Y-%m-%d %H:%M", &tm);
496         if (k && *k == 0) {
497                 tm.tm_sec = 0;
498                 goto finish;
499         }
500
501         tm = copy;
502         k = strptime(t, "%y-%m-%d", &tm);
503         if (k && *k == 0) {
504                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
505                 goto finish;
506         }
507
508         tm = copy;
509         k = strptime(t, "%Y-%m-%d", &tm);
510         if (k && *k == 0) {
511                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
512                 goto finish;
513         }
514
515         tm = copy;
516         k = strptime(t, "%H:%M:%S", &tm);
517         if (k && *k == 0)
518                 goto finish;
519
520         tm = copy;
521         k = strptime(t, "%H:%M", &tm);
522         if (k && *k == 0) {
523                 tm.tm_sec = 0;
524                 goto finish;
525         }
526
527         return -EINVAL;
528
529 finish:
530         x = mktime(&tm);
531         if (x == (time_t) -1)
532                 return -EINVAL;
533
534         if (weekday >= 0 && tm.tm_wday != weekday)
535                 return -EINVAL;
536
537         ret = (usec_t) x * USEC_PER_SEC;
538
539         ret += plus;
540         if (ret > minus)
541                 ret -= minus;
542         else
543                 ret = 0;
544
545         *usec = ret;
546
547         return 0;
548 }
549
550 int parse_sec(const char *t, usec_t *usec) {
551         static const struct {
552                 const char *suffix;
553                 usec_t usec;
554         } table[] = {
555                 { "seconds", USEC_PER_SEC },
556                 { "second", USEC_PER_SEC },
557                 { "sec", USEC_PER_SEC },
558                 { "s", USEC_PER_SEC },
559                 { "minutes", USEC_PER_MINUTE },
560                 { "minute", USEC_PER_MINUTE },
561                 { "min", USEC_PER_MINUTE },
562                 { "months", USEC_PER_MONTH },
563                 { "month", USEC_PER_MONTH },
564                 { "msec", USEC_PER_MSEC },
565                 { "ms", USEC_PER_MSEC },
566                 { "m", USEC_PER_MINUTE },
567                 { "hours", USEC_PER_HOUR },
568                 { "hour", USEC_PER_HOUR },
569                 { "hr", USEC_PER_HOUR },
570                 { "h", USEC_PER_HOUR },
571                 { "days", USEC_PER_DAY },
572                 { "day", USEC_PER_DAY },
573                 { "d", USEC_PER_DAY },
574                 { "weeks", USEC_PER_WEEK },
575                 { "week", USEC_PER_WEEK },
576                 { "w", USEC_PER_WEEK },
577                 { "years", USEC_PER_YEAR },
578                 { "year", USEC_PER_YEAR },
579                 { "y", USEC_PER_YEAR },
580                 { "usec", 1ULL },
581                 { "us", 1ULL },
582                 { "", USEC_PER_SEC }, /* default is sec */
583         };
584
585         const char *p;
586         usec_t r = 0;
587         bool something = false;
588
589         assert(t);
590         assert(usec);
591
592         p = t;
593         for (;;) {
594                 long long l, z = 0;
595                 char *e;
596                 unsigned i, n = 0;
597
598                 p += strspn(p, WHITESPACE);
599
600                 if (*p == 0) {
601                         if (!something)
602                                 return -EINVAL;
603
604                         break;
605                 }
606
607                 errno = 0;
608                 l = strtoll(p, &e, 10);
609
610                 if (errno > 0)
611                         return -errno;
612
613                 if (l < 0)
614                         return -ERANGE;
615
616                 if (*e == '.') {
617                         char *b = e + 1;
618
619                         errno = 0;
620                         z = strtoll(b, &e, 10);
621                         if (errno > 0)
622                                 return -errno;
623
624                         if (z < 0)
625                                 return -ERANGE;
626
627                         if (e == b)
628                                 return -EINVAL;
629
630                         n = e - b;
631
632                 } else if (e == p)
633                         return -EINVAL;
634
635                 e += strspn(e, WHITESPACE);
636
637                 for (i = 0; i < ELEMENTSOF(table); i++)
638                         if (startswith(e, table[i].suffix)) {
639                                 usec_t k = (usec_t) z * table[i].usec;
640
641                                 for (; n > 0; n--)
642                                         k /= 10;
643
644                                 r += (usec_t) l * table[i].usec + k;
645                                 p = e + strlen(table[i].suffix);
646
647                                 something = true;
648                                 break;
649                         }
650
651                 if (i >= ELEMENTSOF(table))
652                         return -EINVAL;
653
654         }
655
656         *usec = r;
657
658         return 0;
659 }
660
661 int parse_nsec(const char *t, nsec_t *nsec) {
662         static const struct {
663                 const char *suffix;
664                 nsec_t nsec;
665         } table[] = {
666                 { "seconds", NSEC_PER_SEC },
667                 { "second", NSEC_PER_SEC },
668                 { "sec", NSEC_PER_SEC },
669                 { "s", NSEC_PER_SEC },
670                 { "minutes", NSEC_PER_MINUTE },
671                 { "minute", NSEC_PER_MINUTE },
672                 { "min", NSEC_PER_MINUTE },
673                 { "months", NSEC_PER_MONTH },
674                 { "month", NSEC_PER_MONTH },
675                 { "msec", NSEC_PER_MSEC },
676                 { "ms", NSEC_PER_MSEC },
677                 { "m", NSEC_PER_MINUTE },
678                 { "hours", NSEC_PER_HOUR },
679                 { "hour", NSEC_PER_HOUR },
680                 { "hr", NSEC_PER_HOUR },
681                 { "h", NSEC_PER_HOUR },
682                 { "days", NSEC_PER_DAY },
683                 { "day", NSEC_PER_DAY },
684                 { "d", NSEC_PER_DAY },
685                 { "weeks", NSEC_PER_WEEK },
686                 { "week", NSEC_PER_WEEK },
687                 { "w", NSEC_PER_WEEK },
688                 { "years", NSEC_PER_YEAR },
689                 { "year", NSEC_PER_YEAR },
690                 { "y", NSEC_PER_YEAR },
691                 { "usec", NSEC_PER_USEC },
692                 { "us", NSEC_PER_USEC },
693                 { "nsec", 1ULL },
694                 { "ns", 1ULL },
695                 { "", 1ULL }, /* default is nsec */
696         };
697
698         const char *p;
699         nsec_t r = 0;
700         bool something = false;
701
702         assert(t);
703         assert(nsec);
704
705         p = t;
706         for (;;) {
707                 long long l, z = 0;
708                 char *e;
709                 unsigned i, n = 0;
710
711                 p += strspn(p, WHITESPACE);
712
713                 if (*p == 0) {
714                         if (!something)
715                                 return -EINVAL;
716
717                         break;
718                 }
719
720                 errno = 0;
721                 l = strtoll(p, &e, 10);
722
723                 if (errno > 0)
724                         return -errno;
725
726                 if (l < 0)
727                         return -ERANGE;
728
729                 if (*e == '.') {
730                         char *b = e + 1;
731
732                         errno = 0;
733                         z = strtoll(b, &e, 10);
734                         if (errno > 0)
735                                 return -errno;
736
737                         if (z < 0)
738                                 return -ERANGE;
739
740                         if (e == b)
741                                 return -EINVAL;
742
743                         n = e - b;
744
745                 } else if (e == p)
746                         return -EINVAL;
747
748                 e += strspn(e, WHITESPACE);
749
750                 for (i = 0; i < ELEMENTSOF(table); i++)
751                         if (startswith(e, table[i].suffix)) {
752                                 nsec_t k = (nsec_t) z * table[i].nsec;
753
754                                 for (; n > 0; n--)
755                                         k /= 10;
756
757                                 r += (nsec_t) l * table[i].nsec + k;
758                                 p = e + strlen(table[i].suffix);
759
760                                 something = true;
761                                 break;
762                         }
763
764                 if (i >= ELEMENTSOF(table))
765                         return -EINVAL;
766
767         }
768
769         *nsec = r;
770
771         return 0;
772 }