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