]>
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 "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
) {
261 f
= open_memstream(&buf
, &sz
);
265 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
266 format_weekdays(f
, c
);
270 format_chain(f
, 4, c
->year
);
272 format_chain(f
, 2, c
->month
);
274 format_chain(f
, 2, c
->day
);
276 format_chain(f
, 2, c
->hour
);
278 format_chain(f
, 2, c
->minute
);
280 format_chain(f
, 2, c
->second
);
285 r
= fflush_and_check(f
);
298 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
299 static const struct {
329 if (!first
&& **p
== ' ')
332 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
335 if (!startswith_no_case(*p
, day_nr
[i
].name
))
338 skip
= strlen(day_nr
[i
].name
);
340 if ((*p
)[skip
] != '-' &&
346 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
351 if (l
> day_nr
[i
].nr
)
354 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
355 c
->weekdays_bits
|= 1 << j
;
362 /* Couldn't find this prefix, so let's assume the
363 weekday was not specified and let's continue with
365 if (i
>= ELEMENTSOF(day_nr
))
366 return first
? 0 : -EINVAL
;
368 /* We reached the end of the string */
372 /* We reached the end of the weekday spec part */
374 *p
+= strspn(*p
, " ");
391 static int prepend_component(const char **p
, CalendarComponent
**c
) {
392 unsigned long value
, repeat
= 0;
393 char *e
= NULL
, *ee
= NULL
;
394 CalendarComponent
*cc
;
400 value
= strtoul(*p
, &e
, 10);
405 if ((unsigned long) (int) value
!= value
)
409 repeat
= strtoul(e
+1, &ee
, 10);
414 if ((unsigned long) (int) repeat
!= repeat
)
422 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= ':')
425 cc
= new0(CalendarComponent
, 1);
438 return prepend_component(p
, c
);
444 static int parse_chain(const char **p
, CalendarComponent
**c
) {
446 CalendarComponent
*cc
= NULL
;
460 r
= prepend_component(&t
, &cc
);
471 static int const_chain(int value
, CalendarComponent
**c
) {
472 CalendarComponent
*cc
= NULL
;
476 cc
= new0(CalendarComponent
, 1);
489 static int parse_date(const char **p
, CalendarSpec
*c
) {
492 CalendarComponent
*first
, *second
, *third
;
503 r
= parse_chain(&t
, &first
);
507 /* Already the end? A ':' as separator? In that case this was a time, not a date */
508 if (*t
== 0 || *t
== ':') {
519 r
= parse_chain(&t
, &second
);
525 /* Got two parts, hence it's month and day */
526 if (*t
== ' ' || *t
== 0) {
527 *p
= t
+ strspn(t
, " ");
540 r
= parse_chain(&t
, &third
);
547 /* Got tree parts, hence it is year, month and day */
548 if (*t
== ' ' || *t
== 0) {
549 *p
= t
+ strspn(t
, " ");
562 static int parse_time(const char **p
, CalendarSpec
*c
) {
563 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
574 /* If no time is specified at all, but a date of some
575 * kind, then this means 00:00:00 */
576 if (c
->day
|| c
->weekdays_bits
> 0)
582 r
= parse_chain(&t
, &h
);
592 r
= parse_chain(&t
, &m
);
596 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
610 r
= parse_chain(&t
, &s
);
614 /* At the end? Then it's hours, minutes and seconds */
622 r
= const_chain(0, &h
);
626 r
= const_chain(0, &m
);
631 r
= const_chain(0, &s
);
649 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
659 c
= new0(CalendarSpec
, 1);
663 c
->utc
= endswith_no_case(p
, "UTC");
665 p
= strndupa(p
, strlen(p
) - strlen(" UTC"));
667 if (strcaseeq(p
, "minutely")) {
668 r
= const_chain(0, &c
->second
);
672 } else if (strcaseeq(p
, "hourly")) {
673 r
= const_chain(0, &c
->minute
);
676 r
= const_chain(0, &c
->second
);
680 } else if (strcaseeq(p
, "daily")) {
681 r
= const_chain(0, &c
->hour
);
684 r
= const_chain(0, &c
->minute
);
687 r
= const_chain(0, &c
->second
);
691 } else if (strcaseeq(p
, "monthly")) {
692 r
= const_chain(1, &c
->day
);
695 r
= const_chain(0, &c
->hour
);
698 r
= const_chain(0, &c
->minute
);
701 r
= const_chain(0, &c
->second
);
705 } else if (strcaseeq(p
, "annually") ||
706 strcaseeq(p
, "yearly") ||
707 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
709 r
= const_chain(1, &c
->month
);
712 r
= const_chain(1, &c
->day
);
715 r
= const_chain(0, &c
->hour
);
718 r
= const_chain(0, &c
->minute
);
721 r
= const_chain(0, &c
->second
);
725 } else if (strcaseeq(p
, "weekly")) {
727 c
->weekdays_bits
= 1;
729 r
= const_chain(0, &c
->hour
);
732 r
= const_chain(0, &c
->minute
);
735 r
= const_chain(0, &c
->second
);
739 } else if (strcaseeq(p
, "quarterly")) {
741 r
= const_chain(1, &c
->month
);
744 r
= const_chain(4, &c
->month
);
747 r
= const_chain(7, &c
->month
);
750 r
= const_chain(10, &c
->month
);
753 r
= const_chain(1, &c
->day
);
756 r
= const_chain(0, &c
->hour
);
759 r
= const_chain(0, &c
->minute
);
762 r
= const_chain(0, &c
->second
);
766 } else if (strcaseeq(p
, "biannually") ||
767 strcaseeq(p
, "bi-annually") ||
768 strcaseeq(p
, "semiannually") ||
769 strcaseeq(p
, "semi-annually")) {
771 r
= const_chain(1, &c
->month
);
774 r
= const_chain(7, &c
->month
);
777 r
= const_chain(1, &c
->day
);
780 r
= const_chain(0, &c
->hour
);
783 r
= const_chain(0, &c
->minute
);
786 r
= const_chain(0, &c
->second
);
791 r
= parse_weekdays(&p
, c
);
795 r
= parse_date(&p
, c
);
799 r
= parse_time(&p
, c
);
809 r
= calendar_spec_normalize(c
);
813 if (!calendar_spec_valid(c
)) {
822 calendar_spec_free(c
);
826 static int find_matching_component(const CalendarComponent
*c
, int *val
) {
827 const CalendarComponent
*n
;
840 if (c
->value
>= *val
) {
842 if (!d_set
|| c
->value
< d
) {
847 } else if (c
->repeat
> 0) {
850 k
= c
->value
+ c
->repeat
* ((*val
- c
->value
+ c
->repeat
-1) / c
->repeat
);
852 if (!d_set
|| k
< d
) {
869 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
875 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
878 /* Did any normalization take place? If so, it was out of bounds before */
880 t
.tm_year
!= tm
->tm_year
||
881 t
.tm_mon
!= tm
->tm_mon
||
882 t
.tm_mday
!= tm
->tm_mday
||
883 t
.tm_hour
!= tm
->tm_hour
||
884 t
.tm_min
!= tm
->tm_min
||
885 t
.tm_sec
!= tm
->tm_sec
;
888 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
892 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
896 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
899 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
900 return (weekdays_bits
& (1 << k
));
903 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
) {
913 /* Normalize the current date */
914 mktime_or_timegm(&c
, spec
->utc
);
918 r
= find_matching_component(spec
->year
, &c
.tm_year
);
924 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
926 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
))
930 r
= find_matching_component(spec
->month
, &c
.tm_mon
);
935 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
937 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
941 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
945 r
= find_matching_component(spec
->day
, &c
.tm_mday
);
947 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
948 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
951 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
955 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
957 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
961 r
= find_matching_component(spec
->hour
, &c
.tm_hour
);
963 c
.tm_min
= c
.tm_sec
= 0;
964 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
966 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
970 r
= find_matching_component(spec
->minute
, &c
.tm_min
);
973 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
975 c
.tm_min
= c
.tm_sec
= 0;
979 r
= find_matching_component(spec
->second
, &c
.tm_sec
);
980 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
992 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1000 t
= (time_t) (usec
/ USEC_PER_SEC
) + 1;
1001 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1003 r
= find_next(spec
, &tm
);
1007 t
= mktime_or_timegm(&tm
, spec
->utc
);
1008 if (t
== (time_t) -1)
1011 *next
= (usec_t
) t
* USEC_PER_SEC
;