]>
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
) {
660 c
= new0(CalendarSpec
, 1);
664 c
->utc
= endswith_no_case(p
, "UTC");
666 p
= strndupa(p
, strlen(p
) - strlen(" UTC"));
668 if (strcaseeq(p
, "minutely")) {
669 r
= const_chain(0, &c
->second
);
673 } else if (strcaseeq(p
, "hourly")) {
674 r
= const_chain(0, &c
->minute
);
677 r
= const_chain(0, &c
->second
);
681 } else if (strcaseeq(p
, "daily")) {
682 r
= const_chain(0, &c
->hour
);
685 r
= const_chain(0, &c
->minute
);
688 r
= const_chain(0, &c
->second
);
692 } else if (strcaseeq(p
, "monthly")) {
693 r
= const_chain(1, &c
->day
);
696 r
= const_chain(0, &c
->hour
);
699 r
= const_chain(0, &c
->minute
);
702 r
= const_chain(0, &c
->second
);
706 } else if (strcaseeq(p
, "annually") ||
707 strcaseeq(p
, "yearly") ||
708 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
710 r
= const_chain(1, &c
->month
);
713 r
= const_chain(1, &c
->day
);
716 r
= const_chain(0, &c
->hour
);
719 r
= const_chain(0, &c
->minute
);
722 r
= const_chain(0, &c
->second
);
726 } else if (strcaseeq(p
, "weekly")) {
728 c
->weekdays_bits
= 1;
730 r
= const_chain(0, &c
->hour
);
733 r
= const_chain(0, &c
->minute
);
736 r
= const_chain(0, &c
->second
);
740 } else if (strcaseeq(p
, "quarterly")) {
742 r
= const_chain(1, &c
->month
);
745 r
= const_chain(4, &c
->month
);
748 r
= const_chain(7, &c
->month
);
751 r
= const_chain(10, &c
->month
);
754 r
= const_chain(1, &c
->day
);
757 r
= const_chain(0, &c
->hour
);
760 r
= const_chain(0, &c
->minute
);
763 r
= const_chain(0, &c
->second
);
767 } else if (strcaseeq(p
, "biannually") ||
768 strcaseeq(p
, "bi-annually") ||
769 strcaseeq(p
, "semiannually") ||
770 strcaseeq(p
, "semi-annually")) {
772 r
= const_chain(1, &c
->month
);
775 r
= const_chain(7, &c
->month
);
778 r
= const_chain(1, &c
->day
);
781 r
= const_chain(0, &c
->hour
);
784 r
= const_chain(0, &c
->minute
);
787 r
= const_chain(0, &c
->second
);
792 r
= parse_weekdays(&p
, c
);
796 r
= parse_date(&p
, c
);
800 r
= parse_time(&p
, c
);
810 r
= calendar_spec_normalize(c
);
814 if (!calendar_spec_valid(c
)) {
823 calendar_spec_free(c
);
827 static int find_matching_component(const CalendarComponent
*c
, int *val
) {
828 const CalendarComponent
*n
;
841 if (c
->value
>= *val
) {
843 if (!d_set
|| c
->value
< d
) {
848 } else if (c
->repeat
> 0) {
851 k
= c
->value
+ c
->repeat
* ((*val
- c
->value
+ c
->repeat
-1) / c
->repeat
);
853 if (!d_set
|| k
< d
) {
870 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
876 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
879 /* Did any normalization take place? If so, it was out of bounds before */
881 t
.tm_year
!= tm
->tm_year
||
882 t
.tm_mon
!= tm
->tm_mon
||
883 t
.tm_mday
!= tm
->tm_mday
||
884 t
.tm_hour
!= tm
->tm_hour
||
885 t
.tm_min
!= tm
->tm_min
||
886 t
.tm_sec
!= tm
->tm_sec
;
889 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
893 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
897 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
900 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
901 return (weekdays_bits
& (1 << k
));
904 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
) {
914 /* Normalize the current date */
915 mktime_or_timegm(&c
, spec
->utc
);
919 r
= find_matching_component(spec
->year
, &c
.tm_year
);
925 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
927 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
))
931 r
= find_matching_component(spec
->month
, &c
.tm_mon
);
936 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
938 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
942 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
946 r
= find_matching_component(spec
->day
, &c
.tm_mday
);
948 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
949 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
952 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
956 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
958 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
962 r
= find_matching_component(spec
->hour
, &c
.tm_hour
);
964 c
.tm_min
= c
.tm_sec
= 0;
965 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
967 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
971 r
= find_matching_component(spec
->minute
, &c
.tm_min
);
974 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
976 c
.tm_min
= c
.tm_sec
= 0;
980 r
= find_matching_component(spec
->second
, &c
.tm_sec
);
981 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
993 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1001 t
= (time_t) (usec
/ USEC_PER_SEC
) + 1;
1002 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1004 r
= find_next(spec
, &tm
);
1008 t
= mktime_or_timegm(&tm
, spec
->utc
);
1009 if (t
== (time_t) -1)
1012 *next
= (usec_t
) t
* USEC_PER_SEC
;