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 #define BITS_WEEKDAYS 127
29 static void free_chain(CalendarComponent *c) {
39 void calendar_spec_free(CalendarSpec *c) {
48 free_chain(c->minute);
49 free_chain(c->second);
54 static int component_compare(const void *_a, const void *_b) {
55 CalendarComponent * const *a = _a, * const *b = _b;
57 if ((*a)->value < (*b)->value)
59 if ((*a)->value > (*b)->value)
62 if ((*a)->repeat < (*b)->repeat)
64 if ((*a)->repeat > (*b)->repeat)
70 static void sort_chain(CalendarComponent **c) {
72 CalendarComponent **b, *i, **j, *next;
76 for (i = *c; i; i = i->next)
82 j = b = alloca(sizeof(CalendarComponent*) * n);
83 for (i = *c; i; i = i->next)
86 qsort(b, n, sizeof(CalendarComponent*), component_compare);
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) {
106 static void fix_year(CalendarComponent *c) {
107 /* Turns 12 → 2012, 89 → 1989 */
110 CalendarComponent *n = c->next;
112 if (c->value >= 0 && c->value < 70)
115 if (c->value >= 70 && c->value < 100)
122 int calendar_spec_normalize(CalendarSpec *c) {
125 if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
126 c->weekdays_bits = -1;
130 sort_chain(&c->year);
131 sort_chain(&c->month);
133 sort_chain(&c->hour);
134 sort_chain(&c->minute);
135 sort_chain(&c->second);
140 _pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
144 if (c->value < from || c->value > to)
147 if (c->value + c->repeat > to)
151 return chain_valid(c->next, from, to);
156 _pure_ bool calendar_spec_valid(CalendarSpec *c) {
159 if (c->weekdays_bits > BITS_WEEKDAYS)
162 if (!chain_valid(c->year, 1970, 2199))
165 if (!chain_valid(c->month, 1, 12))
168 if (!chain_valid(c->day, 1, 31))
171 if (!chain_valid(c->hour, 0, 23))
174 if (!chain_valid(c->minute, 0, 59))
177 if (!chain_valid(c->second, 0, 59))
183 static void format_weekdays(FILE *f, const CalendarSpec *c) {
184 static const char *const days[] = {
195 bool need_colon = false;
199 assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
201 for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
203 if (c->weekdays_bits & (1 << x)) {
218 fputc(x > l + 2 ? '-' : ',', f);
226 if (l >= 0 && x > l + 1) {
227 fputc(x > l + 2 ? '-' : ',', f);
232 static void format_chain(FILE *f, int space, const CalendarComponent *c) {
240 assert(c->value >= 0);
241 fprintf(f, "%0*i", space, c->value);
244 fprintf(f, "/%i", c->repeat);
248 format_chain(f, space, c->next);
252 int calendar_spec_to_string(const CalendarSpec *c, char **p) {
260 f = open_memstream(&buf, &sz);
264 if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
265 format_weekdays(f, c);
269 format_chain(f, 4, c->year);
271 format_chain(f, 2, c->month);
273 format_chain(f, 2, c->day);
275 format_chain(f, 2, c->hour);
277 format_chain(f, 2, c->minute);
279 format_chain(f, 2, c->second);
295 static int parse_weekdays(const char **p, CalendarSpec *c) {
296 static const struct {
326 if (!first && **p == ' ')
329 for (i = 0; i < ELEMENTSOF(day_nr); i++) {
332 if (!startswith_no_case(*p, day_nr[i].name))
335 skip = strlen(day_nr[i].name);
337 if ((*p)[skip] != '-' &&
343 c->weekdays_bits |= 1 << day_nr[i].nr;
348 if (l > day_nr[i].nr)
351 for (j = l + 1; j < day_nr[i].nr; j++)
352 c->weekdays_bits |= 1 << j;
359 /* Couldn't find this prefix, so let's assume the
360 weekday was not specified and let's continue with
362 if (i >= ELEMENTSOF(day_nr))
363 return first ? 0 : -EINVAL;
365 /* We reached the end of the string */
369 /* We reached the end of the weekday spec part */
371 *p += strspn(*p, " ");
388 static int prepend_component(const char **p, CalendarComponent **c) {
389 unsigned long value, repeat = 0;
390 char *e = NULL, *ee = NULL;
391 CalendarComponent *cc;
397 value = strtoul(*p, &e, 10);
402 if ((unsigned long) (int) value != value)
406 repeat = strtoul(e+1, &ee, 10);
411 if ((unsigned long) (int) repeat != repeat)
419 if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
422 cc = new0(CalendarComponent, 1);
435 return prepend_component(p, c);
441 static int parse_chain(const char **p, CalendarComponent **c) {
443 CalendarComponent *cc = NULL;
457 r = prepend_component(&t, &cc);
468 static int const_chain(int value, CalendarComponent **c) {
469 CalendarComponent *cc = NULL;
473 cc = new0(CalendarComponent, 1);
486 static int parse_date(const char **p, CalendarSpec *c) {
489 CalendarComponent *first, *second, *third;
500 r = parse_chain(&t, &first);
504 /* Already the end? A ':' as separator? In that case this was a time, not a date */
505 if (*t == 0 || *t == ':') {
516 r = parse_chain(&t, &second);
522 /* Got two parts, hence it's month and day */
523 if (*t == ' ' || *t == 0) {
524 *p = t + strspn(t, " ");
537 r = parse_chain(&t, &third);
544 /* Got tree parts, hence it is year, month and day */
545 if (*t == ' ' || *t == 0) {
546 *p = t + strspn(t, " ");
559 static int parse_time(const char **p, CalendarSpec *c) {
560 CalendarComponent *h = NULL, *m = NULL, *s = NULL;
571 /* If no time is specified at all, but a date of some
572 * kind, then this means 00:00:00 */
573 if (c->day || c->weekdays_bits > 0)
579 r = parse_chain(&t, &h);
589 r = parse_chain(&t, &m);
593 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
607 r = parse_chain(&t, &s);
611 /* At the end? Then it's hours, minutes and seconds */
619 r = const_chain(0, &h);
623 r = const_chain(0, &m);
628 r = const_chain(0, &s);
646 int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
656 c = new0(CalendarSpec, 1);
660 if (strcaseeq(p, "minutely")) {
661 r = const_chain(0, &c->second);
665 } else if (strcaseeq(p, "hourly")) {
666 r = const_chain(0, &c->minute);
669 r = const_chain(0, &c->second);
673 } else if (strcaseeq(p, "daily")) {
674 r = const_chain(0, &c->hour);
677 r = const_chain(0, &c->minute);
680 r = const_chain(0, &c->second);
684 } else if (strcaseeq(p, "monthly")) {
685 r = const_chain(1, &c->day);
688 r = const_chain(0, &c->hour);
691 r = const_chain(0, &c->minute);
694 r = const_chain(0, &c->second);
698 } else if (strcaseeq(p, "annually") ||
699 strcaseeq(p, "yearly") ||
700 strcaseeq(p, "anually") /* backwards compatibility */ ) {
702 r = const_chain(1, &c->month);
705 r = const_chain(1, &c->day);
708 r = const_chain(0, &c->hour);
711 r = const_chain(0, &c->minute);
714 r = const_chain(0, &c->second);
718 } else if (strcaseeq(p, "weekly")) {
720 c->weekdays_bits = 1;
722 r = const_chain(0, &c->hour);
725 r = const_chain(0, &c->minute);
728 r = const_chain(0, &c->second);
732 } else if (strcaseeq(p, "quarterly")) {
734 r = const_chain(1, &c->month);
737 r = const_chain(4, &c->month);
740 r = const_chain(7, &c->month);
743 r = const_chain(10, &c->month);
746 r = const_chain(1, &c->day);
749 r = const_chain(0, &c->hour);
752 r = const_chain(0, &c->minute);
755 r = const_chain(0, &c->second);
759 } else if (strcaseeq(p, "biannually") ||
760 strcaseeq(p, "bi-annually") ||
761 strcaseeq(p, "semiannually") ||
762 strcaseeq(p, "semi-annually")) {
764 r = const_chain(1, &c->month);
767 r = const_chain(7, &c->month);
770 r = const_chain(1, &c->day);
773 r = const_chain(0, &c->hour);
776 r = const_chain(0, &c->minute);
779 r = const_chain(0, &c->second);
784 r = parse_weekdays(&p, c);
788 r = parse_date(&p, c);
792 r = parse_time(&p, c);
802 r = calendar_spec_normalize(c);
806 if (!calendar_spec_valid(c)) {
815 calendar_spec_free(c);
819 static int find_matching_component(const CalendarComponent *c, int *val) {
820 const CalendarComponent *n;
833 if (c->value >= *val) {
835 if (!d_set || c->value < d) {
840 } else if (c->repeat > 0) {
843 k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
845 if (!d_set || k < d) {
862 static bool tm_out_of_bounds(const struct tm *tm) {
868 if (mktime(&t) == (time_t) -1)
871 /* Did any normalization take place? If so, it was out of bounds before */
873 t.tm_year != tm->tm_year ||
874 t.tm_mon != tm->tm_mon ||
875 t.tm_mday != tm->tm_mday ||
876 t.tm_hour != tm->tm_hour ||
877 t.tm_min != tm->tm_min ||
878 t.tm_sec != tm->tm_sec;
881 static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
885 if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
889 if (mktime(&t) == (time_t) -1)
892 k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
893 return (weekdays_bits & (1 << k));
896 static int find_next(const CalendarSpec *spec, struct tm *tm) {
906 /* Normalize the current date */
911 r = find_matching_component(spec->year, &c.tm_year);
917 c.tm_hour = c.tm_min = c.tm_sec = 0;
919 if (r < 0 || tm_out_of_bounds(&c))
923 r = find_matching_component(spec->month, &c.tm_mon);
928 c.tm_hour = c.tm_min = c.tm_sec = 0;
930 if (r < 0 || tm_out_of_bounds(&c)) {
934 c.tm_hour = c.tm_min = c.tm_sec = 0;
938 r = find_matching_component(spec->day, &c.tm_mday);
940 c.tm_hour = c.tm_min = c.tm_sec = 0;
941 if (r < 0 || tm_out_of_bounds(&c)) {
944 c.tm_hour = c.tm_min = c.tm_sec = 0;
948 if (!matches_weekday(spec->weekdays_bits, &c)) {
950 c.tm_hour = c.tm_min = c.tm_sec = 0;
954 r = find_matching_component(spec->hour, &c.tm_hour);
956 c.tm_min = c.tm_sec = 0;
957 if (r < 0 || tm_out_of_bounds(&c)) {
959 c.tm_hour = c.tm_min = c.tm_sec = 0;
963 r = find_matching_component(spec->minute, &c.tm_min);
966 if (r < 0 || tm_out_of_bounds(&c)) {
968 c.tm_min = c.tm_sec = 0;
972 r = find_matching_component(spec->second, &c.tm_sec);
973 if (r < 0 || tm_out_of_bounds(&c)) {
985 int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
993 t = (time_t) (usec / USEC_PER_SEC) + 1;
994 assert_se(localtime_r(&t, &tm));
996 r = find_next(spec, &tm);
1001 if (t == (time_t) -1)
1004 *next = (usec_t) t * USEC_PER_SEC;