chiark / gitweb /
sd-dhcp-lease: use shared default prefixlen function
[elogind.git] / src / shared / calendarspec.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 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 <stdlib.h>
23 #include <string.h>
24
25 #include "calendarspec.h"
26
27 static void free_chain(CalendarComponent *c) {
28         CalendarComponent *n;
29
30         while (c) {
31                 n = c->next;
32                 free(c);
33                 c = n;
34         }
35 }
36
37 void calendar_spec_free(CalendarSpec *c) {
38
39         if (!c)
40                 return;
41
42         free_chain(c->year);
43         free_chain(c->month);
44         free_chain(c->day);
45         free_chain(c->hour);
46         free_chain(c->minute);
47         free_chain(c->second);
48
49         free(c);
50 }
51
52 static int component_compare(const void *_a, const void *_b) {
53         CalendarComponent * const *a = _a, * const *b = _b;
54
55         if ((*a)->value < (*b)->value)
56                 return -1;
57         if ((*a)->value > (*b)->value)
58                 return 1;
59
60         if ((*a)->repeat < (*b)->repeat)
61                 return -1;
62         if ((*a)->repeat > (*b)->repeat)
63                 return 1;
64
65         return 0;
66 }
67
68 static void sort_chain(CalendarComponent **c) {
69         unsigned n = 0, k;
70         CalendarComponent **b, *i, **j, *next;
71
72         assert(c);
73
74         for (i = *c; i; i = i->next)
75                 n++;
76
77         if (n <= 1)
78                 return;
79
80         j = b = alloca(sizeof(CalendarComponent*) * n);
81         for (i = *c; i; i = i->next)
82                 *(j++) = i;
83
84         qsort(b, n, sizeof(CalendarComponent*), component_compare);
85
86         b[n-1]->next = NULL;
87         next = b[n-1];
88
89         /* Drop non-unique entries */
90         for (k = n-1; k > 0; k--) {
91                 if (b[k-1]->value == next->value &&
92                     b[k-1]->repeat == next->repeat) {
93                         free(b[k-1]);
94                         continue;
95                 }
96
97                 b[k-1]->next = next;
98                 next = b[k-1];
99         }
100
101         *c = next;
102 }
103
104 static void fix_year(CalendarComponent *c) {
105         /* Turns 12 → 2012, 89 → 1989 */
106
107         while(c) {
108                 CalendarComponent *n = c->next;
109
110                 if (c->value >= 0 && c->value < 70)
111                         c->value += 2000;
112
113                 if (c->value >= 70 && c->value < 100)
114                         c->value += 1900;
115
116                 c = n;
117         }
118 }
119
120 int calendar_spec_normalize(CalendarSpec *c) {
121         assert(c);
122
123         if (c->weekdays_bits <= 0 || c->weekdays_bits >= 127)
124                 c->weekdays_bits = -1;
125
126         fix_year(c->year);
127
128         sort_chain(&c->year);
129         sort_chain(&c->month);
130         sort_chain(&c->day);
131         sort_chain(&c->hour);
132         sort_chain(&c->minute);
133         sort_chain(&c->second);
134
135         return 0;
136 }
137
138 _pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
139         if (!c)
140                 return true;
141
142         if (c->value < from || c->value > to)
143                 return false;
144
145         if (c->value + c->repeat > to)
146                 return false;
147
148         if (c->next)
149                 return chain_valid(c->next, from, to);
150
151         return true;
152 }
153
154 _pure_ bool calendar_spec_valid(CalendarSpec *c) {
155         assert(c);
156
157         if (c->weekdays_bits > 127)
158                 return false;
159
160         if (!chain_valid(c->year, 1970, 2199))
161                 return false;
162
163         if (!chain_valid(c->month, 1, 12))
164                 return false;
165
166         if (!chain_valid(c->day, 1, 31))
167                 return false;
168
169         if (!chain_valid(c->hour, 0, 23))
170                 return false;
171
172         if (!chain_valid(c->minute, 0, 59))
173                 return false;
174
175         if (!chain_valid(c->second, 0, 59))
176                 return false;
177
178         return true;
179 }
180
181 static void format_weekdays(FILE *f, const CalendarSpec *c) {
182         static const char *const days[] = {
183                 "Mon",
184                 "Tue",
185                 "Wed",
186                 "Thu",
187                 "Fri",
188                 "Sat",
189                 "Sun"
190         };
191
192         int l, x;
193         bool need_colon = false;
194
195         assert(f);
196         assert(c);
197         assert(c->weekdays_bits > 0 && c->weekdays_bits <= 127);
198
199         for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
200
201                 if (c->weekdays_bits & (1 << x)) {
202
203                         if (l < 0) {
204                                 if (need_colon)
205                                         fputc(',', f);
206                                 else
207                                         need_colon = true;
208
209                                 fputs(days[x], f);
210                                 l = x;
211                         }
212
213                 } else if (l >= 0) {
214
215                         if (x > l + 1) {
216                                 fputc(x > l + 2 ? '-' : ',', f);
217                                 fputs(days[x-1], f);
218                         }
219
220                         l = -1;
221                 }
222         }
223
224         if (l >= 0 && x > l + 1) {
225                 fputc(x > l + 2 ? '-' : ',', f);
226                 fputs(days[x-1], f);
227         }
228 }
229
230 static void format_chain(FILE *f, int space, const CalendarComponent *c) {
231         assert(f);
232
233         if (!c) {
234                 fputc('*', f);
235                 return;
236         }
237
238         assert(c->value >= 0);
239         fprintf(f, "%0*i", space, c->value);
240
241         if (c->repeat > 0)
242                 fprintf(f, "/%i", c->repeat);
243
244         if (c->next) {
245                 fputc(',', f);
246                 format_chain(f, space, c->next);
247         }
248 }
249
250 int calendar_spec_to_string(const CalendarSpec *c, char **p) {
251         char *buf = NULL;
252         size_t sz = 0;
253         FILE *f;
254
255         assert(c);
256         assert(p);
257
258         f = open_memstream(&buf, &sz);
259         if (!f)
260                 return -ENOMEM;
261
262         if (c->weekdays_bits > 0 && c->weekdays_bits <= 127) {
263                 format_weekdays(f, c);
264                 fputc(' ', f);
265         }
266
267         format_chain(f, 4, c->year);
268         fputc('-', f);
269         format_chain(f, 2, c->month);
270         fputc('-', f);
271         format_chain(f, 2, c->day);
272         fputc(' ', f);
273         format_chain(f, 2, c->hour);
274         fputc(':', f);
275         format_chain(f, 2, c->minute);
276         fputc(':', f);
277         format_chain(f, 2, c->second);
278
279         fflush(f);
280
281         if (ferror(f)) {
282                 free(buf);
283                 fclose(f);
284                 return -ENOMEM;
285         }
286
287         fclose(f);
288
289         *p = buf;
290         return 0;
291 }
292
293 static int parse_weekdays(const char **p, CalendarSpec *c) {
294         static const struct {
295                 const char *name;
296                 const int nr;
297         } day_nr[] = {
298                 { "Monday",    0 },
299                 { "Mon",       0 },
300                 { "Tuesday",   1 },
301                 { "Tue",       1 },
302                 { "Wednesday", 2 },
303                 { "Wed",       2 },
304                 { "Thursday",  3 },
305                 { "Thu",       3 },
306                 { "Friday",    4 },
307                 { "Fri",       4 },
308                 { "Saturday",  5 },
309                 { "Sat",       5 },
310                 { "Sunday",    6 },
311                 { "Sun",       6 }
312         };
313
314         int l = -1;
315         bool first = true;
316
317         assert(p);
318         assert(*p);
319         assert(c);
320
321         for (;;) {
322                 unsigned i;
323
324                 if (!first && **p == ' ')
325                         return 0;
326
327                 for (i = 0; i < ELEMENTSOF(day_nr); i++) {
328                         size_t skip;
329
330                         if (!startswith_no_case(*p, day_nr[i].name))
331                                 continue;
332
333                         skip = strlen(day_nr[i].name);
334
335                         if ((*p)[skip] != '-' &&
336                             (*p)[skip] != ',' &&
337                             (*p)[skip] != ' ' &&
338                             (*p)[skip] != 0)
339                                 return -EINVAL;
340
341                         c->weekdays_bits |= 1 << day_nr[i].nr;
342
343                         if (l >= 0) {
344                                 int j;
345
346                                 if (l > day_nr[i].nr)
347                                         return -EINVAL;
348
349                                 for (j = l + 1; j < day_nr[i].nr; j++)
350                                         c->weekdays_bits |= 1 << j;
351                         }
352
353                         *p += skip;
354                         break;
355                 }
356
357                 /* Couldn't find this prefix, so let's assume the
358                    weekday was not specified and let's continue with
359                    the date */
360                 if (i >= ELEMENTSOF(day_nr))
361                         return first ? 0 : -EINVAL;
362
363                 /* We reached the end of the string */
364                 if (**p == 0)
365                         return 0;
366
367                 /* We reached the end of the weekday spec part */
368                 if (**p == ' ') {
369                         *p += strspn(*p, " ");
370                         return 0;
371                 }
372
373                 if (**p == '-') {
374                         if (l >= 0)
375                                 return -EINVAL;
376
377                         l = day_nr[i].nr;
378                 } else
379                         l = -1;
380
381                 *p += 1;
382                 first = false;
383         }
384 }
385
386 static int prepend_component(const char **p, CalendarComponent **c) {
387         unsigned long value, repeat = 0;
388         char *e = NULL, *ee = NULL;
389         CalendarComponent *cc;
390
391         assert(p);
392         assert(c);
393
394         errno = 0;
395         value = strtoul(*p, &e, 10);
396         if (errno > 0)
397                 return -errno;
398         if (e == *p)
399                 return -EINVAL;
400         if ((unsigned long) (int) value != value)
401                 return -ERANGE;
402
403         if (*e == '/') {
404                 repeat = strtoul(e+1, &ee, 10);
405                 if (errno > 0)
406                         return -errno;
407                 if (ee == e+1)
408                         return -EINVAL;
409                 if ((unsigned long) (int) repeat != repeat)
410                         return -ERANGE;
411                 if (repeat <= 0)
412                         return -ERANGE;
413
414                 e = ee;
415         }
416
417         if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
418                 return -EINVAL;
419
420         cc = new0(CalendarComponent, 1);
421         if (!cc)
422                 return -ENOMEM;
423
424         cc->value = value;
425         cc->repeat = repeat;
426         cc->next = *c;
427
428         *p = e;
429         *c = cc;
430
431         if (*e ==',') {
432                 *p += 1;
433                 return prepend_component(p, c);
434         }
435
436         return 0;
437 }
438
439 static int parse_chain(const char **p, CalendarComponent **c) {
440         const char *t;
441         CalendarComponent *cc = NULL;
442         int r;
443
444         assert(p);
445         assert(c);
446
447         t = *p;
448
449         if (t[0] == '*') {
450                 *p = t + 1;
451                 *c = NULL;
452                 return 0;
453         }
454
455         r = prepend_component(&t, &cc);
456         if (r < 0) {
457                 free_chain(cc);
458                 return r;
459         }
460
461         *p = t;
462         *c = cc;
463         return 0;
464 }
465
466 static int const_chain(int value, CalendarComponent **c) {
467         CalendarComponent *cc = NULL;
468
469         assert(c);
470
471         cc = new0(CalendarComponent, 1);
472         if (!cc)
473                 return -ENOMEM;
474
475         cc->value = value;
476         cc->repeat = 0;
477         cc->next = *c;
478
479         *c = cc;
480
481         return 0;
482 }
483
484 static int parse_date(const char **p, CalendarSpec *c) {
485         const char *t;
486         int r;
487         CalendarComponent *first, *second, *third;
488
489         assert(p);
490         assert(*p);
491         assert(c);
492
493         t = *p;
494
495         if (*t == 0)
496                 return 0;
497
498         r = parse_chain(&t, &first);
499         if (r < 0)
500                 return r;
501
502         /* Already the end? A ':' as separator? In that case this was a time, not a date */
503         if (*t == 0 || *t == ':') {
504                 free_chain(first);
505                 return 0;
506         }
507
508         if (*t != '-') {
509                 free_chain(first);
510                 return -EINVAL;
511         }
512
513         t++;
514         r = parse_chain(&t, &second);
515         if (r < 0) {
516                 free_chain(first);
517                 return r;
518         }
519
520         /* Got two parts, hence it's month and day */
521         if (*t == ' ' || *t == 0) {
522                 *p = t + strspn(t, " ");
523                 c->month = first;
524                 c->day = second;
525                 return 0;
526         }
527
528         if (*t != '-') {
529                 free_chain(first);
530                 free_chain(second);
531                 return -EINVAL;
532         }
533
534         t++;
535         r = parse_chain(&t, &third);
536         if (r < 0) {
537                 free_chain(first);
538                 free_chain(second);
539                 return r;
540         }
541
542         /* Got tree parts, hence it is year, month and day */
543         if (*t == ' ' || *t == 0) {
544                 *p = t + strspn(t, " ");
545                 c->year = first;
546                 c->month = second;
547                 c->day = third;
548                 return 0;
549         }
550
551         free_chain(first);
552         free_chain(second);
553         free_chain(third);
554         return -EINVAL;
555 }
556
557 static int parse_time(const char **p, CalendarSpec *c) {
558         CalendarComponent *h = NULL, *m = NULL, *s = NULL;
559         const char *t;
560         int r;
561
562         assert(p);
563         assert(*p);
564         assert(c);
565
566         t = *p;
567
568         if (*t == 0) {
569                 /* If no time is specified at all, but a date of some
570                  * kind, then this means 00:00:00 */
571                 if (c->day || c->weekdays_bits > 0)
572                         goto null_hour;
573
574                 goto finish;
575         }
576
577         r = parse_chain(&t, &h);
578         if (r < 0)
579                 goto fail;
580
581         if (*t != ':') {
582                 r = -EINVAL;
583                 goto fail;
584         }
585
586         t++;
587         r = parse_chain(&t, &m);
588         if (r < 0)
589                 goto fail;
590
591         /* Already at the end? Then it's hours and minutes, and seconds are 0 */
592         if (*t == 0) {
593                 if (m != NULL)
594                         goto null_second;
595
596                 goto finish;
597         }
598
599         if (*t != ':') {
600                 r = -EINVAL;
601                 goto fail;
602         }
603
604         t++;
605         r = parse_chain(&t, &s);
606         if (r < 0)
607                 goto fail;
608
609         /* At the end? Then it's hours, minutes and seconds */
610         if (*t == 0)
611                 goto finish;
612
613         r = -EINVAL;
614         goto fail;
615
616 null_hour:
617         r = const_chain(0, &h);
618         if (r < 0)
619                 goto fail;
620
621         r = const_chain(0, &m);
622         if (r < 0)
623                 goto fail;
624
625 null_second:
626         r = const_chain(0, &s);
627         if (r < 0)
628                 goto fail;
629
630 finish:
631         *p = t;
632         c->hour = h;
633         c->minute = m;
634         c->second = s;
635         return 0;
636
637 fail:
638         free_chain(h);
639         free_chain(m);
640         free_chain(s);
641         return r;
642 }
643
644 int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
645         CalendarSpec *c;
646         int r;
647
648         assert(p);
649         assert(spec);
650
651         if (isempty(p))
652                 return -EINVAL;
653
654         c = new0(CalendarSpec, 1);
655         if (!c)
656                 return -ENOMEM;
657
658         if (strcaseeq(p, "minutely")) {
659                 r = const_chain(0, &c->second);
660                 if (r < 0)
661                         goto fail;
662
663         } else if (strcaseeq(p, "hourly")) {
664                 r = const_chain(0, &c->minute);
665                 if (r < 0)
666                         goto fail;
667                 r = const_chain(0, &c->second);
668                 if (r < 0)
669                         goto fail;
670
671         } else if (strcaseeq(p, "daily")) {
672                 r = const_chain(0, &c->hour);
673                 if (r < 0)
674                         goto fail;
675                 r = const_chain(0, &c->minute);
676                 if (r < 0)
677                         goto fail;
678                 r = const_chain(0, &c->second);
679                 if (r < 0)
680                         goto fail;
681
682         } else if (strcaseeq(p, "monthly")) {
683                 r = const_chain(1, &c->day);
684                 if (r < 0)
685                         goto fail;
686                 r = const_chain(0, &c->hour);
687                 if (r < 0)
688                         goto fail;
689                 r = const_chain(0, &c->minute);
690                 if (r < 0)
691                         goto fail;
692                 r = const_chain(0, &c->second);
693                 if (r < 0)
694                         goto fail;
695
696         } else if (strcaseeq(p, "annually") ||
697                    strcaseeq(p, "yearly") ||
698                    strcaseeq(p, "anually") /* backwards compatibility */ ) {
699
700                 r = const_chain(1, &c->month);
701                 if (r < 0)
702                         goto fail;
703                 r = const_chain(1, &c->day);
704                 if (r < 0)
705                         goto fail;
706                 r = const_chain(0, &c->hour);
707                 if (r < 0)
708                         goto fail;
709                 r = const_chain(0, &c->minute);
710                 if (r < 0)
711                         goto fail;
712                 r = const_chain(0, &c->second);
713                 if (r < 0)
714                         goto fail;
715
716         } else if (strcaseeq(p, "weekly")) {
717
718                 c->weekdays_bits = 1;
719
720                 r = const_chain(0, &c->hour);
721                 if (r < 0)
722                         goto fail;
723                 r = const_chain(0, &c->minute);
724                 if (r < 0)
725                         goto fail;
726                 r = const_chain(0, &c->second);
727                 if (r < 0)
728                         goto fail;
729
730         } else if (strcaseeq(p, "quarterly")) {
731
732                 r = const_chain(1, &c->month);
733                 if (r < 0)
734                         goto fail;
735                 r = const_chain(4, &c->month);
736                 if (r < 0)
737                         goto fail;
738                 r = const_chain(7, &c->month);
739                 if (r < 0)
740                         goto fail;
741                 r = const_chain(10, &c->month);
742                 if (r < 0)
743                         goto fail;
744                 r = const_chain(1, &c->day);
745                 if (r < 0)
746                         goto fail;
747                 r = const_chain(0, &c->hour);
748                 if (r < 0)
749                         goto fail;
750                 r = const_chain(0, &c->minute);
751                 if (r < 0)
752                         goto fail;
753                 r = const_chain(0, &c->second);
754                 if (r < 0)
755                         goto fail;
756
757         } else if (strcaseeq(p, "biannually") ||
758                    strcaseeq(p, "bi-annually") ||
759                    strcaseeq(p, "semiannually") ||
760                    strcaseeq(p, "semi-annually")) {
761
762                 r = const_chain(1, &c->month);
763                 if (r < 0)
764                         goto fail;
765                 r = const_chain(7, &c->month);
766                 if (r < 0)
767                         goto fail;
768                 r = const_chain(1, &c->day);
769                 if (r < 0)
770                         goto fail;
771                 r = const_chain(0, &c->hour);
772                 if (r < 0)
773                         goto fail;
774                 r = const_chain(0, &c->minute);
775                 if (r < 0)
776                         goto fail;
777                 r = const_chain(0, &c->second);
778                 if (r < 0)
779                         goto fail;
780
781         } else {
782                 r = parse_weekdays(&p, c);
783                 if (r < 0)
784                         goto fail;
785
786                 r = parse_date(&p, c);
787                 if (r < 0)
788                         goto fail;
789
790                 r = parse_time(&p, c);
791                 if (r < 0)
792                         goto fail;
793
794                 if (*p != 0) {
795                         r = -EINVAL;
796                         goto fail;
797                 }
798         }
799
800         r = calendar_spec_normalize(c);
801         if (r < 0)
802                 goto fail;
803
804         if (!calendar_spec_valid(c)) {
805                 r = -EINVAL;
806                 goto fail;
807         }
808
809         *spec = c;
810         return 0;
811
812 fail:
813         calendar_spec_free(c);
814         return r;
815 }
816
817 static int find_matching_component(const CalendarComponent *c, int *val) {
818         const CalendarComponent *n;
819         int d = -1;
820         bool d_set = false;
821         int r;
822
823         assert(val);
824
825         if (!c)
826                 return 0;
827
828         while (c) {
829                 n = c->next;
830
831                 if (c->value >= *val) {
832
833                         if (!d_set || c->value < d) {
834                                 d = c->value;
835                                 d_set = true;
836                         }
837
838                 } else if (c->repeat > 0) {
839                         int k;
840
841                         k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
842
843                         if (!d_set || k < d) {
844                                 d = k;
845                                 d_set = true;
846                         }
847                 }
848
849                 c = n;
850         }
851
852         if (!d_set)
853                 return -ENOENT;
854
855         r = *val != d;
856         *val = d;
857         return r;
858 }
859
860 static bool tm_out_of_bounds(const struct tm *tm) {
861         struct tm t;
862         assert(tm);
863
864         t = *tm;
865
866         if (mktime(&t) == (time_t) -1)
867                 return true;
868
869         /* Did any normalization take place? If so, it was out of bounds before */
870         return
871                 t.tm_year != tm->tm_year ||
872                 t.tm_mon != tm->tm_mon ||
873                 t.tm_mday != tm->tm_mday ||
874                 t.tm_hour != tm->tm_hour ||
875                 t.tm_min != tm->tm_min ||
876                 t.tm_sec != tm->tm_sec;
877 }
878
879 static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
880         struct tm t;
881         int k;
882
883         if (weekdays_bits < 0 || weekdays_bits >= 127)
884                 return true;
885
886         t = *tm;
887         if (mktime(&t) == (time_t) -1)
888                 return false;
889
890         k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
891         return (weekdays_bits & (1 << k));
892 }
893
894 static int find_next(const CalendarSpec *spec, struct tm *tm) {
895         struct tm c;
896         int r;
897
898         assert(spec);
899         assert(tm);
900
901         c = *tm;
902
903         for (;;) {
904                 /* Normalize the current date */
905                 mktime(&c);
906                 c.tm_isdst = -1;
907
908                 c.tm_year += 1900;
909                 r = find_matching_component(spec->year, &c.tm_year);
910                 c.tm_year -= 1900;
911
912                 if (r > 0) {
913                         c.tm_mon = 0;
914                         c.tm_mday = 1;
915                         c.tm_hour = c.tm_min = c.tm_sec = 0;
916                 }
917                 if (r < 0 || tm_out_of_bounds(&c))
918                         return r;
919
920                 c.tm_mon += 1;
921                 r = find_matching_component(spec->month, &c.tm_mon);
922                 c.tm_mon -= 1;
923
924                 if (r > 0) {
925                         c.tm_mday = 1;
926                         c.tm_hour = c.tm_min = c.tm_sec = 0;
927                 }
928                 if (r < 0 || tm_out_of_bounds(&c)) {
929                         c.tm_year ++;
930                         c.tm_mon = 0;
931                         c.tm_mday = 1;
932                         c.tm_hour = c.tm_min = c.tm_sec = 0;
933                         continue;
934                 }
935
936                 r = find_matching_component(spec->day, &c.tm_mday);
937                 if (r > 0)
938                         c.tm_hour = c.tm_min = c.tm_sec = 0;
939                 if (r < 0 || tm_out_of_bounds(&c)) {
940                         c.tm_mon ++;
941                         c.tm_mday = 1;
942                         c.tm_hour = c.tm_min = c.tm_sec = 0;
943                         continue;
944                 }
945
946                 if (!matches_weekday(spec->weekdays_bits, &c)) {
947                         c.tm_mday++;
948                         c.tm_hour = c.tm_min = c.tm_sec = 0;
949                         continue;
950                 }
951
952                 r = find_matching_component(spec->hour, &c.tm_hour);
953                 if (r > 0)
954                         c.tm_min = c.tm_sec = 0;
955                 if (r < 0 || tm_out_of_bounds(&c)) {
956                         c.tm_mday ++;
957                         c.tm_hour = c.tm_min = c.tm_sec = 0;
958                         continue;
959                 }
960
961                 r = find_matching_component(spec->minute, &c.tm_min);
962                 if (r > 0)
963                         c.tm_sec = 0;
964                 if (r < 0 || tm_out_of_bounds(&c)) {
965                         c.tm_hour ++;
966                         c.tm_min = c.tm_sec = 0;
967                         continue;
968                 }
969
970                 r = find_matching_component(spec->second, &c.tm_sec);
971                 if (r < 0 || tm_out_of_bounds(&c)) {
972                         c.tm_min ++;
973                         c.tm_sec = 0;
974                         continue;
975                 }
976
977
978                 *tm = c;
979                 return 0;
980         }
981 }
982
983 int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
984         struct tm tm;
985         time_t t;
986         int r;
987
988         assert(spec);
989         assert(next);
990
991         t = (time_t) (usec / USEC_PER_SEC) + 1;
992         assert_se(localtime_r(&t, &tm));
993
994         r = find_next(spec, &tm);
995         if (r < 0)
996                 return r;
997
998         t = mktime(&tm);
999         if (t == (time_t) -1)
1000                 return -EINVAL;
1001
1002         *next = (usec_t) t * USEC_PER_SEC;
1003         return 0;
1004 }