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