1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
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.
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.
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/>.
25 #include "calendarspec.h"
27 static void free_chain(CalendarComponent *c) {
37 void calendar_spec_free(CalendarSpec *c) {
46 free_chain(c->minute);
47 free_chain(c->second);
52 static int component_compare(const void *_a, const void *_b) {
53 CalendarComponent * const *a = _a, * const *b = _b;
55 if ((*a)->value < (*b)->value)
57 if ((*a)->value > (*b)->value)
60 if ((*a)->repeat < (*b)->repeat)
62 if ((*a)->repeat > (*b)->repeat)
68 static void sort_chain(CalendarComponent **c) {
70 CalendarComponent **b, *i, **j, *next;
74 for (i = *c; i; i = i->next)
80 j = b = alloca(sizeof(CalendarComponent*) * n);
81 for (i = *c; i; i = i->next)
84 qsort(b, n, sizeof(CalendarComponent*), component_compare);
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) {
104 static void fix_year(CalendarComponent *c) {
105 /* Turns 12 → 2012, 89 → 1989 */
108 CalendarComponent *n = c->next;
110 if (c->value >= 0 && c->value < 70)
113 if (c->value >= 70 && c->value < 100)
120 int calendar_spec_normalize(CalendarSpec *c) {
123 if (c->weekdays_bits <= 0 || c->weekdays_bits >= 127)
124 c->weekdays_bits = -1;
128 sort_chain(&c->year);
129 sort_chain(&c->month);
131 sort_chain(&c->hour);
132 sort_chain(&c->minute);
133 sort_chain(&c->second);
138 _pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
142 if (c->value < from || c->value > to)
145 if (c->value + c->repeat > to)
149 return chain_valid(c->next, from, to);
154 _pure_ bool calendar_spec_valid(CalendarSpec *c) {
157 if (c->weekdays_bits > 127)
160 if (!chain_valid(c->year, 1970, 2199))
163 if (!chain_valid(c->month, 1, 12))
166 if (!chain_valid(c->day, 1, 31))
169 if (!chain_valid(c->hour, 0, 23))
172 if (!chain_valid(c->minute, 0, 59))
175 if (!chain_valid(c->second, 0, 59))
181 static void format_weekdays(FILE *f, const CalendarSpec *c) {
182 static const char *const days[] = {
193 bool need_colon = false;
197 assert(c->weekdays_bits > 0 && c->weekdays_bits <= 127);
199 for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
201 if (c->weekdays_bits & (1 << x)) {
216 fputc(x > l + 2 ? '-' : ',', f);
224 if (l >= 0 && x > l + 1) {
225 fputc(x > l + 2 ? '-' : ',', f);
230 static void format_chain(FILE *f, int space, const CalendarComponent *c) {
238 assert(c->value >= 0);
239 fprintf(f, "%0*i", space, c->value);
242 fprintf(f, "/%i", c->repeat);
246 format_chain(f, space, c->next);
250 int calendar_spec_to_string(const CalendarSpec *c, char **p) {
258 f = open_memstream(&buf, &sz);
262 if (c->weekdays_bits > 0 && c->weekdays_bits <= 127) {
263 format_weekdays(f, c);
267 format_chain(f, 4, c->year);
269 format_chain(f, 2, c->month);
271 format_chain(f, 2, c->day);
273 format_chain(f, 2, c->hour);
275 format_chain(f, 2, c->minute);
277 format_chain(f, 2, c->second);
293 static int parse_weekdays(const char **p, CalendarSpec *c) {
294 static const struct {
324 if (!first && **p == ' ')
327 for (i = 0; i < ELEMENTSOF(day_nr); i++) {
330 if (!startswith_no_case(*p, day_nr[i].name))
333 skip = strlen(day_nr[i].name);
335 if ((*p)[skip] != '-' &&
341 c->weekdays_bits |= 1 << day_nr[i].nr;
346 if (l > day_nr[i].nr)
349 for (j = l + 1; j < day_nr[i].nr; j++)
350 c->weekdays_bits |= 1 << j;
357 /* Couldn't find this prefix, so let's assume the
358 weekday was not specified and let's continue with
360 if (i >= ELEMENTSOF(day_nr))
361 return first ? 0 : -EINVAL;
363 /* We reached the end of the string */
367 /* We reached the end of the weekday spec part */
369 *p += strspn(*p, " ");
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;
395 value = strtoul(*p, &e, 10);
400 if ((unsigned long) (int) value != value)
404 repeat = strtoul(e+1, &ee, 10);
409 if ((unsigned long) (int) repeat != repeat)
417 if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
420 cc = new0(CalendarComponent, 1);
433 return prepend_component(p, c);
439 static int parse_chain(const char **p, CalendarComponent **c) {
441 CalendarComponent *cc = NULL;
455 r = prepend_component(&t, &cc);
466 static int const_chain(int value, CalendarComponent **c) {
467 CalendarComponent *cc = NULL;
471 cc = new0(CalendarComponent, 1);
484 static int parse_date(const char **p, CalendarSpec *c) {
487 CalendarComponent *first, *second, *third;
498 r = parse_chain(&t, &first);
502 /* Already the end? A ':' as separator? In that case this was a time, not a date */
503 if (*t == 0 || *t == ':') {
514 r = parse_chain(&t, &second);
520 /* Got two parts, hence it's month and day */
521 if (*t == ' ' || *t == 0) {
522 *p = t + strspn(t, " ");
535 r = parse_chain(&t, &third);
542 /* Got tree parts, hence it is year, month and day */
543 if (*t == ' ' || *t == 0) {
544 *p = t + strspn(t, " ");
557 static int parse_time(const char **p, CalendarSpec *c) {
558 CalendarComponent *h = NULL, *m = NULL, *s = NULL;
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)
577 r = parse_chain(&t, &h);
587 r = parse_chain(&t, &m);
591 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
605 r = parse_chain(&t, &s);
609 /* At the end? Then it's hours, minutes and seconds */
617 r = const_chain(0, &h);
621 r = const_chain(0, &m);
626 r = const_chain(0, &s);
644 int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
654 c = new0(CalendarSpec, 1);
658 if (strcaseeq(p, "minutely")) {
659 r = const_chain(0, &c->second);
663 } else if (strcaseeq(p, "hourly")) {
664 r = const_chain(0, &c->minute);
667 r = const_chain(0, &c->second);
671 } else if (strcaseeq(p, "daily")) {
672 r = const_chain(0, &c->hour);
675 r = const_chain(0, &c->minute);
678 r = const_chain(0, &c->second);
682 } else if (strcaseeq(p, "monthly")) {
683 r = const_chain(1, &c->day);
686 r = const_chain(0, &c->hour);
689 r = const_chain(0, &c->minute);
692 r = const_chain(0, &c->second);
696 } else if (strcaseeq(p, "annually") ||
697 strcaseeq(p, "yearly") ||
698 strcaseeq(p, "anually") /* backwards compatibility */ ) {
700 r = const_chain(1, &c->month);
703 r = const_chain(1, &c->day);
706 r = const_chain(0, &c->hour);
709 r = const_chain(0, &c->minute);
712 r = const_chain(0, &c->second);
716 } else if (strcaseeq(p, "weekly")) {
718 c->weekdays_bits = 1;
720 r = const_chain(0, &c->hour);
723 r = const_chain(0, &c->minute);
726 r = const_chain(0, &c->second);
730 } else if (strcaseeq(p, "quarterly")) {
732 r = const_chain(1, &c->month);
735 r = const_chain(4, &c->month);
738 r = const_chain(7, &c->month);
741 r = const_chain(10, &c->month);
744 r = const_chain(1, &c->day);
747 r = const_chain(0, &c->hour);
750 r = const_chain(0, &c->minute);
753 r = const_chain(0, &c->second);
757 } else if (strcaseeq(p, "biannually") ||
758 strcaseeq(p, "bi-annually") ||
759 strcaseeq(p, "semiannually") ||
760 strcaseeq(p, "semi-annually")) {
762 r = const_chain(1, &c->month);
765 r = const_chain(7, &c->month);
768 r = const_chain(1, &c->day);
771 r = const_chain(0, &c->hour);
774 r = const_chain(0, &c->minute);
777 r = const_chain(0, &c->second);
782 r = parse_weekdays(&p, c);
786 r = parse_date(&p, c);
790 r = parse_time(&p, c);
800 r = calendar_spec_normalize(c);
804 if (!calendar_spec_valid(c)) {
813 calendar_spec_free(c);
817 static int find_matching_component(const CalendarComponent *c, int *val) {
818 const CalendarComponent *n;
831 if (c->value >= *val) {
833 if (!d_set || c->value < d) {
838 } else if (c->repeat > 0) {
841 k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
843 if (!d_set || k < d) {
860 static bool tm_out_of_bounds(const struct tm *tm) {
866 if (mktime(&t) == (time_t) -1)
869 /* Did any normalization take place? If so, it was out of bounds before */
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;
879 static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
883 if (weekdays_bits < 0 || weekdays_bits >= 127)
887 if (mktime(&t) == (time_t) -1)
890 k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
891 return (weekdays_bits & (1 << k));
894 static int find_next(const CalendarSpec *spec, struct tm *tm) {
904 /* Normalize the current date */
909 r = find_matching_component(spec->year, &c.tm_year);
915 c.tm_hour = c.tm_min = c.tm_sec = 0;
917 if (r < 0 || tm_out_of_bounds(&c))
921 r = find_matching_component(spec->month, &c.tm_mon);
926 c.tm_hour = c.tm_min = c.tm_sec = 0;
928 if (r < 0 || tm_out_of_bounds(&c)) {
932 c.tm_hour = c.tm_min = c.tm_sec = 0;
936 r = find_matching_component(spec->day, &c.tm_mday);
938 c.tm_hour = c.tm_min = c.tm_sec = 0;
939 if (r < 0 || tm_out_of_bounds(&c)) {
942 c.tm_hour = c.tm_min = c.tm_sec = 0;
946 if (!matches_weekday(spec->weekdays_bits, &c)) {
948 c.tm_hour = c.tm_min = c.tm_sec = 0;
952 r = find_matching_component(spec->hour, &c.tm_hour);
954 c.tm_min = c.tm_sec = 0;
955 if (r < 0 || tm_out_of_bounds(&c)) {
957 c.tm_hour = c.tm_min = c.tm_sec = 0;
961 r = find_matching_component(spec->minute, &c.tm_min);
964 if (r < 0 || tm_out_of_bounds(&c)) {
966 c.tm_min = c.tm_sec = 0;
970 r = find_matching_component(spec->second, &c.tm_sec);
971 if (r < 0 || tm_out_of_bounds(&c)) {
983 int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
991 t = (time_t) (usec / USEC_PER_SEC) + 1;
992 assert_se(localtime_r(&t, &tm));
994 r = find_next(spec, &tm);
999 if (t == (time_t) -1)
1002 *next = (usec_t) t * USEC_PER_SEC;