]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/calendarspec.c
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 "string-util.h"
26 #include "calendarspec.h"
28 #define BITS_WEEKDAYS 127
30 static void free_chain(CalendarComponent
*c
) {
40 void calendar_spec_free(CalendarSpec
*c
) {
49 free_chain(c
->minute
);
50 free_chain(c
->second
);
55 static int component_compare(const void *_a
, const void *_b
) {
56 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
58 if ((*a
)->value
< (*b
)->value
)
60 if ((*a
)->value
> (*b
)->value
)
63 if ((*a
)->repeat
< (*b
)->repeat
)
65 if ((*a
)->repeat
> (*b
)->repeat
)
71 static void sort_chain(CalendarComponent
**c
) {
73 CalendarComponent
**b
, *i
, **j
, *next
;
77 for (i
= *c
; i
; i
= i
->next
)
83 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
84 for (i
= *c
; i
; i
= i
->next
)
87 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
92 /* Drop non-unique entries */
93 for (k
= n
-1; k
> 0; k
--) {
94 if (b
[k
-1]->value
== next
->value
&&
95 b
[k
-1]->repeat
== next
->repeat
) {
107 static void fix_year(CalendarComponent
*c
) {
108 /* Turns 12 → 2012, 89 → 1989 */
111 CalendarComponent
*n
= c
->next
;
113 if (c
->value
>= 0 && c
->value
< 70)
116 if (c
->value
>= 70 && c
->value
< 100)
123 int calendar_spec_normalize(CalendarSpec
*c
) {
126 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
127 c
->weekdays_bits
= -1;
131 sort_chain(&c
->year
);
132 sort_chain(&c
->month
);
134 sort_chain(&c
->hour
);
135 sort_chain(&c
->minute
);
136 sort_chain(&c
->second
);
141 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
) {
145 if (c
->value
< from
|| c
->value
> to
)
148 if (c
->value
+ c
->repeat
> to
)
152 return chain_valid(c
->next
, from
, to
);
157 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
160 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
163 if (!chain_valid(c
->year
, 1970, 2199))
166 if (!chain_valid(c
->month
, 1, 12))
169 if (!chain_valid(c
->day
, 1, 31))
172 if (!chain_valid(c
->hour
, 0, 23))
175 if (!chain_valid(c
->minute
, 0, 59))
178 if (!chain_valid(c
->second
, 0, 59))
184 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
185 static const char *const days
[] = {
196 bool need_colon
= false;
200 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
202 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
204 if (c
->weekdays_bits
& (1 << x
)) {
219 fputc(x
> l
+ 2 ? '-' : ',', f
);
227 if (l
>= 0 && x
> l
+ 1) {
228 fputc(x
> l
+ 2 ? '-' : ',', f
);
233 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
) {
241 assert(c
->value
>= 0);
242 fprintf(f
, "%0*i", space
, c
->value
);
245 fprintf(f
, "/%i", c
->repeat
);
249 format_chain(f
, space
, c
->next
);
253 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
262 f
= open_memstream(&buf
, &sz
);
266 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
267 format_weekdays(f
, c
);
271 format_chain(f
, 4, c
->year
);
273 format_chain(f
, 2, c
->month
);
275 format_chain(f
, 2, c
->day
);
277 format_chain(f
, 2, c
->hour
);
279 format_chain(f
, 2, c
->minute
);
281 format_chain(f
, 2, c
->second
);
286 r
= fflush_and_check(f
);
299 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
300 static const struct {
330 if (!first
&& **p
== ' ')
333 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
336 if (!startswith_no_case(*p
, day_nr
[i
].name
))
339 skip
= strlen(day_nr
[i
].name
);
341 if ((*p
)[skip
] != '-' &&
347 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
352 if (l
> day_nr
[i
].nr
)
355 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
356 c
->weekdays_bits
|= 1 << j
;
363 /* Couldn't find this prefix, so let's assume the
364 weekday was not specified and let's continue with
366 if (i
>= ELEMENTSOF(day_nr
))
367 return first
? 0 : -EINVAL
;
369 /* We reached the end of the string */
373 /* We reached the end of the weekday spec part */
375 *p
+= strspn(*p
, " ");
392 static int prepend_component(const char **p
, CalendarComponent
**c
) {
393 unsigned long value
, repeat
= 0;
394 char *e
= NULL
, *ee
= NULL
;
395 CalendarComponent
*cc
;
401 value
= strtoul(*p
, &e
, 10);
406 if ((unsigned long) (int) value
!= value
)
410 repeat
= strtoul(e
+1, &ee
, 10);
415 if ((unsigned long) (int) repeat
!= repeat
)
423 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= ':')
426 cc
= new0(CalendarComponent
, 1);
439 return prepend_component(p
, c
);
445 static int parse_chain(const char **p
, CalendarComponent
**c
) {
447 CalendarComponent
*cc
= NULL
;
461 r
= prepend_component(&t
, &cc
);
472 static int const_chain(int value
, CalendarComponent
**c
) {
473 CalendarComponent
*cc
= NULL
;
477 cc
= new0(CalendarComponent
, 1);
490 static int parse_date(const char **p
, CalendarSpec
*c
) {
493 CalendarComponent
*first
, *second
, *third
;
504 r
= parse_chain(&t
, &first
);
508 /* Already the end? A ':' as separator? In that case this was a time, not a date */
509 if (*t
== 0 || *t
== ':') {
520 r
= parse_chain(&t
, &second
);
526 /* Got two parts, hence it's month and day */
527 if (*t
== ' ' || *t
== 0) {
528 *p
= t
+ strspn(t
, " ");
541 r
= parse_chain(&t
, &third
);
548 /* Got tree parts, hence it is year, month and day */
549 if (*t
== ' ' || *t
== 0) {
550 *p
= t
+ strspn(t
, " ");
563 static int parse_time(const char **p
, CalendarSpec
*c
) {
564 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
575 /* If no time is specified at all, but a date of some
576 * kind, then this means 00:00:00 */
577 if (c
->day
|| c
->weekdays_bits
> 0)
583 r
= parse_chain(&t
, &h
);
593 r
= parse_chain(&t
, &m
);
597 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
611 r
= parse_chain(&t
, &s
);
615 /* At the end? Then it's hours, minutes and seconds */
623 r
= const_chain(0, &h
);
627 r
= const_chain(0, &m
);
632 r
= const_chain(0, &s
);
650 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
661 c
= new0(CalendarSpec
, 1);
665 utc
= endswith_no_case(p
, " UTC");
668 p
= strndupa(p
, utc
- p
);
671 if (strcaseeq(p
, "minutely")) {
672 r
= const_chain(0, &c
->second
);
676 } else if (strcaseeq(p
, "hourly")) {
677 r
= const_chain(0, &c
->minute
);
680 r
= const_chain(0, &c
->second
);
684 } else if (strcaseeq(p
, "daily")) {
685 r
= const_chain(0, &c
->hour
);
688 r
= const_chain(0, &c
->minute
);
691 r
= const_chain(0, &c
->second
);
695 } else if (strcaseeq(p
, "monthly")) {
696 r
= const_chain(1, &c
->day
);
699 r
= const_chain(0, &c
->hour
);
702 r
= const_chain(0, &c
->minute
);
705 r
= const_chain(0, &c
->second
);
709 } else if (strcaseeq(p
, "annually") ||
710 strcaseeq(p
, "yearly") ||
711 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
713 r
= const_chain(1, &c
->month
);
716 r
= const_chain(1, &c
->day
);
719 r
= const_chain(0, &c
->hour
);
722 r
= const_chain(0, &c
->minute
);
725 r
= const_chain(0, &c
->second
);
729 } else if (strcaseeq(p
, "weekly")) {
731 c
->weekdays_bits
= 1;
733 r
= const_chain(0, &c
->hour
);
736 r
= const_chain(0, &c
->minute
);
739 r
= const_chain(0, &c
->second
);
743 } else if (strcaseeq(p
, "quarterly")) {
745 r
= const_chain(1, &c
->month
);
748 r
= const_chain(4, &c
->month
);
751 r
= const_chain(7, &c
->month
);
754 r
= const_chain(10, &c
->month
);
757 r
= const_chain(1, &c
->day
);
760 r
= const_chain(0, &c
->hour
);
763 r
= const_chain(0, &c
->minute
);
766 r
= const_chain(0, &c
->second
);
770 } else if (strcaseeq(p
, "biannually") ||
771 strcaseeq(p
, "bi-annually") ||
772 strcaseeq(p
, "semiannually") ||
773 strcaseeq(p
, "semi-annually")) {
775 r
= const_chain(1, &c
->month
);
778 r
= const_chain(7, &c
->month
);
781 r
= const_chain(1, &c
->day
);
784 r
= const_chain(0, &c
->hour
);
787 r
= const_chain(0, &c
->minute
);
790 r
= const_chain(0, &c
->second
);
795 r
= parse_weekdays(&p
, c
);
799 r
= parse_date(&p
, c
);
803 r
= parse_time(&p
, c
);
813 r
= calendar_spec_normalize(c
);
817 if (!calendar_spec_valid(c
)) {
826 calendar_spec_free(c
);
830 static int find_matching_component(const CalendarComponent
*c
, int *val
) {
831 const CalendarComponent
*n
;
844 if (c
->value
>= *val
) {
846 if (!d_set
|| c
->value
< d
) {
851 } else if (c
->repeat
> 0) {
854 k
= c
->value
+ c
->repeat
* ((*val
- c
->value
+ c
->repeat
-1) / c
->repeat
);
856 if (!d_set
|| k
< d
) {
873 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
879 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
882 /* Did any normalization take place? If so, it was out of bounds before */
884 t
.tm_year
!= tm
->tm_year
||
885 t
.tm_mon
!= tm
->tm_mon
||
886 t
.tm_mday
!= tm
->tm_mday
||
887 t
.tm_hour
!= tm
->tm_hour
||
888 t
.tm_min
!= tm
->tm_min
||
889 t
.tm_sec
!= tm
->tm_sec
;
892 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
896 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
900 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
903 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
904 return (weekdays_bits
& (1 << k
));
907 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
) {
917 /* Normalize the current date */
918 mktime_or_timegm(&c
, spec
->utc
);
922 r
= find_matching_component(spec
->year
, &c
.tm_year
);
928 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
930 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
))
934 r
= find_matching_component(spec
->month
, &c
.tm_mon
);
939 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
941 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
945 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
949 r
= find_matching_component(spec
->day
, &c
.tm_mday
);
951 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
952 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
955 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
959 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
961 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
965 r
= find_matching_component(spec
->hour
, &c
.tm_hour
);
967 c
.tm_min
= c
.tm_sec
= 0;
968 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
970 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
974 r
= find_matching_component(spec
->minute
, &c
.tm_min
);
977 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
979 c
.tm_min
= c
.tm_sec
= 0;
983 r
= find_matching_component(spec
->second
, &c
.tm_sec
);
984 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
996 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1004 t
= (time_t) (usec
/ USEC_PER_SEC
) + 1;
1005 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1007 r
= find_next(spec
, &tm
);
1011 t
= mktime_or_timegm(&tm
, spec
->utc
);
1012 if (t
== (time_t) -1)
1015 *next
= (usec_t
) t
* USEC_PER_SEC
;