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