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) {
44 free_chain(c->minute);
45 free_chain(c->second);
50 static int component_compare(const void *_a, const void *_b) {
51 CalendarComponent * const *a = _a, * const *b = _b;
53 if ((*a)->value < (*b)->value)
55 if ((*a)->value > (*b)->value)
58 if ((*a)->repeat < (*b)->repeat)
60 if ((*a)->repeat > (*b)->repeat)
66 static void sort_chain(CalendarComponent **c) {
68 CalendarComponent **b, *i, **j, *next;
72 for (i = *c; i; i = i->next)
78 j = b = alloca(sizeof(CalendarComponent*) * n);
79 for (i = *c; i; i = i->next)
82 qsort(b, n, sizeof(CalendarComponent*), component_compare);
87 /* Drop non-unique entries */
88 for (k = n-1; k > 0; k--) {
89 if (b[k-1]->value == next->value &&
90 b[k-1]->repeat == next->repeat) {
102 static void fix_year(CalendarComponent *c) {
103 /* Turns 12 → 2012, 89 → 1989 */
106 CalendarComponent *n = c->next;
108 if (c->value >= 0 && c->value < 70)
111 if (c->value >= 70 && c->value < 100)
118 int calendar_spec_normalize(CalendarSpec *c) {
121 if (c->weekdays_bits <= 0 || c->weekdays_bits >= 127)
122 c->weekdays_bits = -1;
126 sort_chain(&c->year);
127 sort_chain(&c->month);
129 sort_chain(&c->hour);
130 sort_chain(&c->minute);
131 sort_chain(&c->second);
136 _pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
140 if (c->value < from || c->value > to)
143 if (c->value + c->repeat > to)
147 return chain_valid(c->next, from, to);
152 _pure_ bool calendar_spec_valid(CalendarSpec *c) {
155 if (c->weekdays_bits > 127)
158 if (!chain_valid(c->year, 1970, 2199))
161 if (!chain_valid(c->month, 1, 12))
164 if (!chain_valid(c->day, 1, 31))
167 if (!chain_valid(c->hour, 0, 23))
170 if (!chain_valid(c->minute, 0, 59))
173 if (!chain_valid(c->second, 0, 59))
179 static void format_weekdays(FILE *f, const CalendarSpec *c) {
180 static const char *const days[] = {
191 bool need_colon = false;
195 assert(c->weekdays_bits > 0 && c->weekdays_bits <= 127);
197 for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
199 if (c->weekdays_bits & (1 << x)) {
214 fputc(x > l + 2 ? '-' : ',', f);
222 if (l >= 0 && x > l + 1) {
223 fputc(x > l + 2 ? '-' : ',', f);
228 static void format_chain(FILE *f, int space, const CalendarComponent *c) {
236 assert(c->value >= 0);
237 fprintf(f, "%0*i", space, c->value);
240 fprintf(f, "/%i", c->repeat);
244 format_chain(f, space, c->next);
248 int calendar_spec_to_string(const CalendarSpec *c, char **p) {
256 f = open_memstream(&buf, &sz);
260 if (c->weekdays_bits > 0 && c->weekdays_bits <= 127) {
261 format_weekdays(f, c);
265 format_chain(f, 4, c->year);
267 format_chain(f, 2, c->month);
269 format_chain(f, 2, c->day);
271 format_chain(f, 2, c->hour);
273 format_chain(f, 2, c->minute);
275 format_chain(f, 2, c->second);
291 static int parse_weekdays(const char **p, CalendarSpec *c) {
292 static const struct {
322 if (!first && **p == ' ')
325 for (i = 0; i < ELEMENTSOF(day_nr); i++) {
328 if (!startswith_no_case(*p, day_nr[i].name))
331 skip = strlen(day_nr[i].name);
333 if ((*p)[skip] != '-' &&
339 c->weekdays_bits |= 1 << day_nr[i].nr;
344 if (l > day_nr[i].nr)
347 for (j = l + 1; j < day_nr[i].nr; j++)
348 c->weekdays_bits |= 1 << j;
355 /* Couldn't find this prefix, so let's assume the
356 weekday was not specified and let's continue with
358 if (i >= ELEMENTSOF(day_nr))
359 return first ? 0 : -EINVAL;
361 /* We reached the end of the string */
365 /* We reached the end of the weekday spec part */
367 *p += strspn(*p, " ");
384 static int prepend_component(const char **p, CalendarComponent **c) {
385 unsigned long value, repeat = 0;
386 char *e = NULL, *ee = NULL;
387 CalendarComponent *cc;
393 value = strtoul(*p, &e, 10);
398 if ((unsigned long) (int) value != value)
402 repeat = strtoul(e+1, &ee, 10);
407 if ((unsigned long) (int) repeat != repeat)
415 if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
418 cc = new0(CalendarComponent, 1);
431 return prepend_component(p, c);
437 static int parse_chain(const char **p, CalendarComponent **c) {
439 CalendarComponent *cc = NULL;
453 r = prepend_component(&t, &cc);
464 static int const_chain(int value, CalendarComponent **c) {
465 CalendarComponent *cc = NULL;
469 cc = new0(CalendarComponent, 1);
482 static int parse_date(const char **p, CalendarSpec *c) {
485 CalendarComponent *first, *second, *third;
496 r = parse_chain(&t, &first);
500 /* Already the end? A ':' as separator? In that case this was a time, not a date */
501 if (*t == 0 || *t == ':') {
512 r = parse_chain(&t, &second);
518 /* Got two parts, hence it's month and day */
519 if (*t == ' ' || *t == 0) {
520 *p = t + strspn(t, " ");
533 r = parse_chain(&t, &third);
540 /* Got tree parts, hence it is year, month and day */
541 if (*t == ' ' || *t == 0) {
542 *p = t + strspn(t, " ");
555 static int parse_time(const char **p, CalendarSpec *c) {
556 CalendarComponent *h = NULL, *m = NULL, *s = NULL;
567 /* If no time is specified at all, but a date of some
568 * kind, then this means 00:00:00 */
569 if (c->day || c->weekdays_bits > 0)
575 r = parse_chain(&t, &h);
585 r = parse_chain(&t, &m);
589 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
603 r = parse_chain(&t, &s);
607 /* At the end? Then it's hours, minutes and seconds */
615 r = const_chain(0, &h);
619 r = const_chain(0, &m);
624 r = const_chain(0, &s);
642 int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
652 c = new0(CalendarSpec, 1);
656 if (strcaseeq(p, "hourly")) {
657 r = const_chain(0, &c->minute);
660 r = const_chain(0, &c->second);
664 } else if (strcaseeq(p, "daily")) {
665 r = const_chain(0, &c->hour);
668 r = const_chain(0, &c->minute);
671 r = const_chain(0, &c->second);
675 } else if (strcaseeq(p, "monthly")) {
676 r = const_chain(1, &c->day);
679 r = const_chain(0, &c->hour);
682 r = const_chain(0, &c->minute);
685 r = const_chain(0, &c->second);
689 } else if (strcaseeq(p, "weekly")) {
691 c->weekdays_bits = 1;
693 r = const_chain(0, &c->hour);
696 r = const_chain(0, &c->minute);
699 r = const_chain(0, &c->second);
704 r = parse_weekdays(&p, c);
708 r = parse_date(&p, c);
712 r = parse_time(&p, c);
722 r = calendar_spec_normalize(c);
726 if (!calendar_spec_valid(c)) {
735 calendar_spec_free(c);
739 static int find_matching_component(const CalendarComponent *c, int *val) {
740 const CalendarComponent *n;
753 if (c->value >= *val) {
755 if (!d_set || c->value < d) {
760 } else if (c->repeat > 0) {
763 k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
765 if (!d_set || k < d) {
782 static bool tm_out_of_bounds(const struct tm *tm) {
788 if (mktime(&t) == (time_t) -1)
791 /* Did any normalization take place? If so, it was out of bounds before */
793 t.tm_year != tm->tm_year ||
794 t.tm_mon != tm->tm_mon ||
795 t.tm_mday != tm->tm_mday ||
796 t.tm_hour != tm->tm_hour ||
797 t.tm_min != tm->tm_min ||
798 t.tm_sec != tm->tm_sec;
801 static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
805 if (weekdays_bits < 0 || weekdays_bits >= 127)
809 if (mktime(&t) == (time_t) -1)
812 k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
813 return (weekdays_bits & (1 << k));
816 static int find_next(const CalendarSpec *spec, struct tm *tm) {
826 /* Normalize the current date */
831 r = find_matching_component(spec->year, &c.tm_year);
837 c.tm_hour = c.tm_min = c.tm_sec = 0;
839 if (r < 0 || tm_out_of_bounds(&c))
843 r = find_matching_component(spec->month, &c.tm_mon);
848 c.tm_hour = c.tm_min = c.tm_sec = 0;
850 if (r < 0 || tm_out_of_bounds(&c)) {
854 c.tm_hour = c.tm_min = c.tm_sec = 0;
858 r = find_matching_component(spec->day, &c.tm_mday);
860 c.tm_hour = c.tm_min = c.tm_sec = 0;
861 if (r < 0 || tm_out_of_bounds(&c)) {
864 c.tm_hour = c.tm_min = c.tm_sec = 0;
868 if (!matches_weekday(spec->weekdays_bits, &c)) {
870 c.tm_hour = c.tm_min = c.tm_sec = 0;
874 r = find_matching_component(spec->hour, &c.tm_hour);
876 c.tm_min = c.tm_sec = 0;
877 if (r < 0 || tm_out_of_bounds(&c)) {
879 c.tm_hour = c.tm_min = c.tm_sec = 0;
883 r = find_matching_component(spec->minute, &c.tm_min);
886 if (r < 0 || tm_out_of_bounds(&c)) {
888 c.tm_min = c.tm_sec = 0;
892 r = find_matching_component(spec->second, &c.tm_sec);
893 if (r < 0 || tm_out_of_bounds(&c)) {
905 int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
913 t = (time_t) (usec / USEC_PER_SEC) + 1;
914 assert_se(localtime_r(&t, &tm));
916 r = find_next(spec, &tm);
921 if (t == (time_t) -1)
925 *next = (usec_t) t * USEC_PER_SEC;