chiark / gitweb /
13d57ba5cd70ecaa595065406bfb16ff98f2cbd6
[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         ts->realtime = u;
50
51         if (u == 0)
52                 ts->monotonic = 0;
53         else {
54                 delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
55
56                 ts->monotonic = now(CLOCK_MONOTONIC);
57
58                 if ((int64_t) ts->monotonic > delta)
59                         ts->monotonic -= delta;
60                 else
61                         ts->monotonic = 0;
62         }
63
64         return ts;
65 }
66
67 usec_t timespec_load(const struct timespec *ts) {
68         assert(ts);
69
70         if (ts->tv_sec == (time_t) -1 &&
71             ts->tv_nsec == (long) -1)
72                 return (usec_t) -1;
73
74         if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
75                 return (usec_t) -1;
76
77         return
78                 (usec_t) ts->tv_sec * USEC_PER_SEC +
79                 (usec_t) ts->tv_nsec / NSEC_PER_USEC;
80 }
81
82 struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
83         assert(ts);
84
85         if (u == (usec_t) -1) {
86                 ts->tv_sec = (time_t) -1;
87                 ts->tv_nsec = (long) -1;
88                 return ts;
89         }
90
91         ts->tv_sec = (time_t) (u / USEC_PER_SEC);
92         ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
93
94         return ts;
95 }
96
97 usec_t timeval_load(const struct timeval *tv) {
98         assert(tv);
99
100         if (tv->tv_sec == (time_t) -1 &&
101             tv->tv_usec == (suseconds_t) -1)
102                 return (usec_t) -1;
103
104         if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
105                 return (usec_t) -1;
106
107         return
108                 (usec_t) tv->tv_sec * USEC_PER_SEC +
109                 (usec_t) tv->tv_usec;
110 }
111
112 struct timeval *timeval_store(struct timeval *tv, usec_t u) {
113         assert(tv);
114
115         if (u == (usec_t) -1) {
116                 tv->tv_sec = (time_t) -1;
117                 tv->tv_usec = (suseconds_t) -1;
118                 return tv;
119         }
120
121         tv->tv_sec = (time_t) (u / USEC_PER_SEC);
122         tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
123
124         return tv;
125 }
126
127 char *format_timestamp(char *buf, size_t l, usec_t t) {
128         struct tm tm;
129         time_t sec;
130
131         assert(buf);
132         assert(l > 0);
133
134         if (t <= 0)
135                 return NULL;
136
137         sec = (time_t) (t / USEC_PER_SEC);
138
139         if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0)
140                 return NULL;
141
142         return buf;
143 }
144
145 char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
146         usec_t n, d;
147
148         n = now(CLOCK_REALTIME);
149
150         if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t)
151                 return NULL;
152
153         d = n - t;
154
155         if (d >= USEC_PER_YEAR)
156                 snprintf(buf, l, "%llu years %llu months ago",
157                          (unsigned long long) (d / USEC_PER_YEAR),
158                          (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH));
159         else if (d >= USEC_PER_MONTH)
160                 snprintf(buf, l, "%llu months %llu days ago",
161                          (unsigned long long) (d / USEC_PER_MONTH),
162                          (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY));
163         else if (d >= USEC_PER_WEEK)
164                 snprintf(buf, l, "%llu weeks %llu days ago",
165                          (unsigned long long) (d / USEC_PER_WEEK),
166                          (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY));
167         else if (d >= 2*USEC_PER_DAY)
168                 snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY));
169         else if (d >= 25*USEC_PER_HOUR)
170                 snprintf(buf, l, "1 day %lluh ago",
171                          (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR));
172         else if (d >= 6*USEC_PER_HOUR)
173                 snprintf(buf, l, "%lluh ago",
174                          (unsigned long long) (d / USEC_PER_HOUR));
175         else if (d >= USEC_PER_HOUR)
176                 snprintf(buf, l, "%lluh %llumin ago",
177                          (unsigned long long) (d / USEC_PER_HOUR),
178                          (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE));
179         else if (d >= 5*USEC_PER_MINUTE)
180                 snprintf(buf, l, "%llumin ago",
181                          (unsigned long long) (d / USEC_PER_MINUTE));
182         else if (d >= USEC_PER_MINUTE)
183                 snprintf(buf, l, "%llumin %llus ago",
184                          (unsigned long long) (d / USEC_PER_MINUTE),
185                          (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC));
186         else if (d >= USEC_PER_SEC)
187                 snprintf(buf, l, "%llus ago",
188                          (unsigned long long) (d / USEC_PER_SEC));
189         else if (d >= USEC_PER_MSEC)
190                 snprintf(buf, l, "%llums ago",
191                          (unsigned long long) (d / USEC_PER_MSEC));
192         else if (d > 0)
193                 snprintf(buf, l, "%lluus ago",
194                          (unsigned long long) d);
195         else
196                 snprintf(buf, l, "now");
197
198         buf[l-1] = 0;
199         return buf;
200 }
201
202 char *format_timespan(char *buf, size_t l, usec_t t) {
203         static const struct {
204                 const char *suffix;
205                 usec_t usec;
206         } table[] = {
207                 { "w", USEC_PER_WEEK },
208                 { "d", USEC_PER_DAY },
209                 { "h", USEC_PER_HOUR },
210                 { "min", USEC_PER_MINUTE },
211                 { "s", USEC_PER_SEC },
212                 { "ms", USEC_PER_MSEC },
213                 { "us", 1 },
214         };
215
216         unsigned i;
217         char *p = buf;
218
219         assert(buf);
220         assert(l > 0);
221
222         if (t == (usec_t) -1)
223                 return NULL;
224
225         if (t == 0) {
226                 snprintf(p, l, "0");
227                 p[l-1] = 0;
228                 return p;
229         }
230
231         /* The result of this function can be parsed with parse_usec */
232
233         for (i = 0; i < ELEMENTSOF(table); i++) {
234                 int k;
235                 size_t n;
236
237                 if (t < table[i].usec)
238                         continue;
239
240                 if (l <= 1)
241                         break;
242
243                 k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix);
244                 n = MIN((size_t) k, l);
245
246                 l -= n;
247                 p += n;
248
249                 t %= table[i].usec;
250         }
251
252         *p = 0;
253
254         return buf;
255 }
256
257 void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
258
259         assert(f);
260         assert(name);
261         assert(t);
262
263         if (!dual_timestamp_is_set(t))
264                 return;
265
266         fprintf(f, "%s=%llu %llu\n",
267                 name,
268                 (unsigned long long) t->realtime,
269                 (unsigned long long) t->monotonic);
270 }
271
272 void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
273         unsigned long long a, b;
274
275         assert(value);
276         assert(t);
277
278         if (sscanf(value, "%lli %llu", &a, &b) != 2)
279                 log_debug("Failed to parse finish timestamp value %s", value);
280         else {
281                 t->realtime = a;
282                 t->monotonic = b;
283         }
284 }
285
286 int parse_timestamp(const char *t, usec_t *usec) {
287         static const struct {
288                 const char *name;
289                 const int nr;
290         } day_nr[] = {
291                 { "Sunday",    0 },
292                 { "Sun",       0 },
293                 { "Monday",    1 },
294                 { "Mon",       1 },
295                 { "Tuesday",   2 },
296                 { "Tue",       2 },
297                 { "Wednesday", 3 },
298                 { "Wed",       3 },
299                 { "Thursday",  4 },
300                 { "Thu",       4 },
301                 { "Friday",    5 },
302                 { "Fri",       5 },
303                 { "Saturday",  6 },
304                 { "Sat",       6 },
305         };
306
307         const char *k;
308         struct tm tm, copy;
309         time_t x;
310         usec_t plus = 0, minus = 0, ret;
311         int r, weekday = -1;
312         unsigned i;
313
314         /*
315          * Allowed syntaxes:
316          *
317          *   2012-09-22 16:34:22
318          *   2012-09-22 16:34     (seconds will be set to 0)
319          *   2012-09-22           (time will be set to 00:00:00)
320          *   16:34:22             (date will be set to today)
321          *   16:34                (date will be set to today, seconds to 0)
322          *   now
323          *   yesterday            (time is set to 00:00:00)
324          *   today                (time is set to 00:00:00)
325          *   tomorrow             (time is set to 00:00:00)
326          *   +5min
327          *   -5days
328          *
329          */
330
331         assert(t);
332         assert(usec);
333
334         x = time(NULL);
335         assert_se(localtime_r(&x, &tm));
336         tm.tm_isdst = -1;
337
338         if (streq(t, "now"))
339                 goto finish;
340
341         else if (streq(t, "today")) {
342                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
343                 goto finish;
344
345         } else if (streq(t, "yesterday")) {
346                 tm.tm_mday --;
347                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
348                 goto finish;
349
350         } else if (streq(t, "tomorrow")) {
351                 tm.tm_mday ++;
352                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
353                 goto finish;
354
355         } else if (t[0] == '+') {
356
357                 r = parse_usec(t+1, &plus);
358                 if (r < 0)
359                         return r;
360
361                 goto finish;
362         } else if (t[0] == '-') {
363
364                 r = parse_usec(t+1, &minus);
365                 if (r < 0)
366                         return r;
367
368                 goto finish;
369
370         } else if (endswith(t, " ago")) {
371                 _cleanup_free_ char *z;
372
373                 z = strndup(t, strlen(t) - 4);
374                 if (!z)
375                         return -ENOMEM;
376
377                 r = parse_usec(z, &minus);
378                 if (r < 0)
379                         return r;
380
381                 goto finish;
382         }
383
384         for (i = 0; i < ELEMENTSOF(day_nr); i++) {
385                 size_t skip;
386
387                 if (!startswith_no_case(t, day_nr[i].name))
388                         continue;
389
390                 skip = strlen(day_nr[i].name);
391                 if (t[skip] != ' ')
392                         continue;
393
394                 weekday = day_nr[i].nr;
395                 t += skip + 1;
396                 break;
397         }
398
399         copy = tm;
400         k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
401         if (k && *k == 0)
402                 goto finish;
403
404         tm = copy;
405         k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
406         if (k && *k == 0)
407                 goto finish;
408
409         tm = copy;
410         k = strptime(t, "%y-%m-%d %H:%M", &tm);
411         if (k && *k == 0) {
412                 tm.tm_sec = 0;
413                 goto finish;
414         }
415
416         tm = copy;
417         k = strptime(t, "%Y-%m-%d %H:%M", &tm);
418         if (k && *k == 0) {
419                 tm.tm_sec = 0;
420                 goto finish;
421         }
422
423         tm = copy;
424         k = strptime(t, "%y-%m-%d", &tm);
425         if (k && *k == 0) {
426                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
427                 goto finish;
428         }
429
430         tm = copy;
431         k = strptime(t, "%Y-%m-%d", &tm);
432         if (k && *k == 0) {
433                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
434                 goto finish;
435         }
436
437         tm = copy;
438         k = strptime(t, "%H:%M:%S", &tm);
439         if (k && *k == 0)
440                 goto finish;
441
442         tm = copy;
443         k = strptime(t, "%H:%M", &tm);
444         if (k && *k == 0) {
445                 tm.tm_sec = 0;
446                 goto finish;
447         }
448
449         return -EINVAL;
450
451 finish:
452         x = mktime(&tm);
453         if (x == (time_t) -1)
454                 return -EINVAL;
455
456         if (weekday >= 0 && tm.tm_wday != weekday)
457                 return -EINVAL;
458
459         ret = (usec_t) x * USEC_PER_SEC;
460
461         ret += plus;
462         if (ret > minus)
463                 ret -= minus;
464         else
465                 ret = 0;
466
467         *usec = ret;
468
469         return 0;
470 }
471
472 int parse_usec(const char *t, usec_t *usec) {
473         static const struct {
474                 const char *suffix;
475                 usec_t usec;
476         } table[] = {
477                 { "seconds", USEC_PER_SEC },
478                 { "second", USEC_PER_SEC },
479                 { "sec", USEC_PER_SEC },
480                 { "s", USEC_PER_SEC },
481                 { "minutes", USEC_PER_MINUTE },
482                 { "minute", USEC_PER_MINUTE },
483                 { "min", USEC_PER_MINUTE },
484                 { "months", USEC_PER_MONTH },
485                 { "month", USEC_PER_MONTH },
486                 { "msec", USEC_PER_MSEC },
487                 { "ms", USEC_PER_MSEC },
488                 { "m", USEC_PER_MINUTE },
489                 { "hours", USEC_PER_HOUR },
490                 { "hour", USEC_PER_HOUR },
491                 { "hr", USEC_PER_HOUR },
492                 { "h", USEC_PER_HOUR },
493                 { "days", USEC_PER_DAY },
494                 { "day", USEC_PER_DAY },
495                 { "d", USEC_PER_DAY },
496                 { "weeks", USEC_PER_WEEK },
497                 { "week", USEC_PER_WEEK },
498                 { "w", USEC_PER_WEEK },
499                 { "years", USEC_PER_YEAR },
500                 { "year", USEC_PER_YEAR },
501                 { "y", USEC_PER_YEAR },
502                 { "usec", 1ULL },
503                 { "us", 1ULL },
504                 { "", USEC_PER_SEC }, /* default is sec */
505         };
506
507         const char *p;
508         usec_t r = 0;
509
510         assert(t);
511         assert(usec);
512
513         p = t;
514         do {
515                 long long l;
516                 char *e;
517                 unsigned i;
518
519                 errno = 0;
520                 l = strtoll(p, &e, 10);
521
522                 if (errno != 0)
523                         return -errno;
524
525                 if (l < 0)
526                         return -ERANGE;
527
528                 if (e == p)
529                         return -EINVAL;
530
531                 e += strspn(e, WHITESPACE);
532
533                 for (i = 0; i < ELEMENTSOF(table); i++)
534                         if (startswith(e, table[i].suffix)) {
535                                 r += (usec_t) l * table[i].usec;
536                                 p = e + strlen(table[i].suffix);
537                                 break;
538                         }
539
540                 if (i >= ELEMENTSOF(table))
541                         return -EINVAL;
542
543         } while (*p != 0);
544
545         *usec = r;
546
547         return 0;
548 }
549
550 int parse_nsec(const char *t, nsec_t *nsec) {
551         static const struct {
552                 const char *suffix;
553                 nsec_t nsec;
554         } table[] = {
555                 { "seconds", NSEC_PER_SEC },
556                 { "second", NSEC_PER_SEC },
557                 { "sec", NSEC_PER_SEC },
558                 { "s", NSEC_PER_SEC },
559                 { "minutes", NSEC_PER_MINUTE },
560                 { "minute", NSEC_PER_MINUTE },
561                 { "min", NSEC_PER_MINUTE },
562                 { "months", NSEC_PER_MONTH },
563                 { "month", NSEC_PER_MONTH },
564                 { "msec", NSEC_PER_MSEC },
565                 { "ms", NSEC_PER_MSEC },
566                 { "m", NSEC_PER_MINUTE },
567                 { "hours", NSEC_PER_HOUR },
568                 { "hour", NSEC_PER_HOUR },
569                 { "hr", NSEC_PER_HOUR },
570                 { "h", NSEC_PER_HOUR },
571                 { "days", NSEC_PER_DAY },
572                 { "day", NSEC_PER_DAY },
573                 { "d", NSEC_PER_DAY },
574                 { "weeks", NSEC_PER_WEEK },
575                 { "week", NSEC_PER_WEEK },
576                 { "w", NSEC_PER_WEEK },
577                 { "years", NSEC_PER_YEAR },
578                 { "year", NSEC_PER_YEAR },
579                 { "y", NSEC_PER_YEAR },
580                 { "usec", NSEC_PER_USEC },
581                 { "us", NSEC_PER_USEC },
582                 { "nsec", 1ULL },
583                 { "ns", 1ULL },
584                 { "", 1ULL }, /* default is nsec */
585         };
586
587         const char *p;
588         nsec_t r = 0;
589
590         assert(t);
591         assert(nsec);
592
593         p = t;
594         do {
595                 long long l;
596                 char *e;
597                 unsigned i;
598
599                 errno = 0;
600                 l = strtoll(p, &e, 10);
601
602                 if (errno != 0)
603                         return -errno;
604
605                 if (l < 0)
606                         return -ERANGE;
607
608                 if (e == p)
609                         return -EINVAL;
610
611                 e += strspn(e, WHITESPACE);
612
613                 for (i = 0; i < ELEMENTSOF(table); i++)
614                         if (startswith(e, table[i].suffix)) {
615                                 r += (nsec_t) l * table[i].nsec;
616                                 p = e + strlen(table[i].suffix);
617                                 break;
618                         }
619
620                 if (i >= ELEMENTSOF(table))
621                         return -EINVAL;
622
623         } while (*p != 0);
624
625         *nsec = r;
626
627         return 0;
628 }