]>
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"
29 #define BITS_WEEKDAYS 127
31 static void free_chain(CalendarComponent
*c
) {
41 void calendar_spec_free(CalendarSpec
*c
) {
50 free_chain(c
->minute
);
51 free_chain(c
->second
);
56 static int component_compare(const void *_a
, const void *_b
) {
57 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
59 if ((*a
)->value
< (*b
)->value
)
61 if ((*a
)->value
> (*b
)->value
)
64 if ((*a
)->repeat
< (*b
)->repeat
)
66 if ((*a
)->repeat
> (*b
)->repeat
)
72 static void sort_chain(CalendarComponent
**c
) {
74 CalendarComponent
**b
, *i
, **j
, *next
;
78 for (i
= *c
; i
; i
= i
->next
)
84 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
85 for (i
= *c
; i
; i
= i
->next
)
88 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
93 /* Drop non-unique entries */
94 for (k
= n
-1; k
> 0; k
--) {
95 if (b
[k
-1]->value
== next
->value
&&
96 b
[k
-1]->repeat
== next
->repeat
) {
108 static void fix_year(CalendarComponent
*c
) {
109 /* Turns 12 → 2012, 89 → 1989 */
112 CalendarComponent
*n
= c
->next
;
114 if (c
->value
>= 0 && c
->value
< 70)
117 if (c
->value
>= 70 && c
->value
< 100)
124 int calendar_spec_normalize(CalendarSpec
*c
) {
127 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
128 c
->weekdays_bits
= -1;
132 sort_chain(&c
->year
);
133 sort_chain(&c
->month
);
135 sort_chain(&c
->hour
);
136 sort_chain(&c
->minute
);
137 sort_chain(&c
->second
);
142 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
) {
146 if (c
->value
< from
|| c
->value
> to
)
149 if (c
->value
+ c
->repeat
> to
)
153 return chain_valid(c
->next
, from
, to
);
158 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
161 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
164 if (!chain_valid(c
->year
, 1970, 2199))
167 if (!chain_valid(c
->month
, 1, 12))
170 if (!chain_valid(c
->day
, 1, 31))
173 if (!chain_valid(c
->hour
, 0, 23))
176 if (!chain_valid(c
->minute
, 0, 59))
179 if (!chain_valid(c
->second
, 0, 59))
185 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
186 static const char *const days
[] = {
197 bool need_colon
= false;
201 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
203 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
205 if (c
->weekdays_bits
& (1 << x
)) {
220 fputc(x
> l
+ 2 ? '-' : ',', f
);
228 if (l
>= 0 && x
> l
+ 1) {
229 fputc(x
> l
+ 2 ? '-' : ',', f
);
234 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
) {
242 assert(c
->value
>= 0);
243 fprintf(f
, "%0*i", space
, c
->value
);
246 fprintf(f
, "/%i", c
->repeat
);
250 format_chain(f
, space
, c
->next
);
254 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
263 f
= open_memstream(&buf
, &sz
);
267 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
268 format_weekdays(f
, c
);
272 format_chain(f
, 4, c
->year
);
274 format_chain(f
, 2, c
->month
);
276 format_chain(f
, 2, c
->day
);
278 format_chain(f
, 2, c
->hour
);
280 format_chain(f
, 2, c
->minute
);
282 format_chain(f
, 2, c
->second
);
287 r
= fflush_and_check(f
);
300 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
301 static const struct {
331 if (!first
&& **p
== ' ')
334 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
337 if (!startswith_no_case(*p
, day_nr
[i
].name
))
340 skip
= strlen(day_nr
[i
].name
);
342 if ((*p
)[skip
] != '-' &&
348 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
353 if (l
> day_nr
[i
].nr
)
356 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
357 c
->weekdays_bits
|= 1 << j
;
364 /* Couldn't find this prefix, so let's assume the
365 weekday was not specified and let's continue with
367 if (i
>= ELEMENTSOF(day_nr
))
368 return first
? 0 : -EINVAL
;
370 /* We reached the end of the string */
374 /* We reached the end of the weekday spec part */
376 *p
+= strspn(*p
, " ");
393 static int prepend_component(const char **p
, CalendarComponent
**c
) {
394 unsigned long value
, repeat
= 0;
395 char *e
= NULL
, *ee
= NULL
;
396 CalendarComponent
*cc
;
402 value
= strtoul(*p
, &e
, 10);
407 if ((unsigned long) (int) value
!= value
)
411 repeat
= strtoul(e
+1, &ee
, 10);
416 if ((unsigned long) (int) repeat
!= repeat
)
424 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= ':')
427 cc
= new0(CalendarComponent
, 1);
440 return prepend_component(p
, c
);
446 static int parse_chain(const char **p
, CalendarComponent
**c
) {
448 CalendarComponent
*cc
= NULL
;
462 r
= prepend_component(&t
, &cc
);
473 static int const_chain(int value
, CalendarComponent
**c
) {
474 CalendarComponent
*cc
= NULL
;
478 cc
= new0(CalendarComponent
, 1);
491 static int parse_date(const char **p
, CalendarSpec
*c
) {
494 CalendarComponent
*first
, *second
, *third
;
505 r
= parse_chain(&t
, &first
);
509 /* Already the end? A ':' as separator? In that case this was a time, not a date */
510 if (*t
== 0 || *t
== ':') {
521 r
= parse_chain(&t
, &second
);
527 /* Got two parts, hence it's month and day */
528 if (*t
== ' ' || *t
== 0) {
529 *p
= t
+ strspn(t
, " ");
542 r
= parse_chain(&t
, &third
);
549 /* Got tree parts, hence it is year, month and day */
550 if (*t
== ' ' || *t
== 0) {
551 *p
= t
+ strspn(t
, " ");
564 static int parse_time(const char **p
, CalendarSpec
*c
) {
565 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
576 /* If no time is specified at all, but a date of some
577 * kind, then this means 00:00:00 */
578 if (c
->day
|| c
->weekdays_bits
> 0)
584 r
= parse_chain(&t
, &h
);
594 r
= parse_chain(&t
, &m
);
598 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
612 r
= parse_chain(&t
, &s
);
616 /* At the end? Then it's hours, minutes and seconds */
624 r
= const_chain(0, &h
);
628 r
= const_chain(0, &m
);
633 r
= const_chain(0, &s
);
651 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
662 c
= new0(CalendarSpec
, 1);
666 utc
= endswith_no_case(p
, " UTC");
669 p
= strndupa(p
, utc
- p
);
672 if (strcaseeq(p
, "minutely")) {
673 r
= const_chain(0, &c
->second
);
677 } else if (strcaseeq(p
, "hourly")) {
678 r
= const_chain(0, &c
->minute
);
681 r
= const_chain(0, &c
->second
);
685 } else if (strcaseeq(p
, "daily")) {
686 r
= const_chain(0, &c
->hour
);
689 r
= const_chain(0, &c
->minute
);
692 r
= const_chain(0, &c
->second
);
696 } else if (strcaseeq(p
, "monthly")) {
697 r
= const_chain(1, &c
->day
);
700 r
= const_chain(0, &c
->hour
);
703 r
= const_chain(0, &c
->minute
);
706 r
= const_chain(0, &c
->second
);
710 } else if (strcaseeq(p
, "annually") ||
711 strcaseeq(p
, "yearly") ||
712 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
714 r
= const_chain(1, &c
->month
);
717 r
= const_chain(1, &c
->day
);
720 r
= const_chain(0, &c
->hour
);
723 r
= const_chain(0, &c
->minute
);
726 r
= const_chain(0, &c
->second
);
730 } else if (strcaseeq(p
, "weekly")) {
732 c
->weekdays_bits
= 1;
734 r
= const_chain(0, &c
->hour
);
737 r
= const_chain(0, &c
->minute
);
740 r
= const_chain(0, &c
->second
);
744 } else if (strcaseeq(p
, "quarterly")) {
746 r
= const_chain(1, &c
->month
);
749 r
= const_chain(4, &c
->month
);
752 r
= const_chain(7, &c
->month
);
755 r
= const_chain(10, &c
->month
);
758 r
= const_chain(1, &c
->day
);
761 r
= const_chain(0, &c
->hour
);
764 r
= const_chain(0, &c
->minute
);
767 r
= const_chain(0, &c
->second
);
771 } else if (strcaseeq(p
, "biannually") ||
772 strcaseeq(p
, "bi-annually") ||
773 strcaseeq(p
, "semiannually") ||
774 strcaseeq(p
, "semi-annually")) {
776 r
= const_chain(1, &c
->month
);
779 r
= const_chain(7, &c
->month
);
782 r
= const_chain(1, &c
->day
);
785 r
= const_chain(0, &c
->hour
);
788 r
= const_chain(0, &c
->minute
);
791 r
= const_chain(0, &c
->second
);
796 r
= parse_weekdays(&p
, c
);
800 r
= parse_date(&p
, c
);
804 r
= parse_time(&p
, c
);
814 r
= calendar_spec_normalize(c
);
818 if (!calendar_spec_valid(c
)) {
827 calendar_spec_free(c
);
831 static int find_matching_component(const CalendarComponent
*c
, int *val
) {
832 const CalendarComponent
*n
;
845 if (c
->value
>= *val
) {
847 if (!d_set
|| c
->value
< d
) {
852 } else if (c
->repeat
> 0) {
855 k
= c
->value
+ c
->repeat
* ((*val
- c
->value
+ c
->repeat
-1) / c
->repeat
);
857 if (!d_set
|| k
< d
) {
874 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
880 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
883 /* Did any normalization take place? If so, it was out of bounds before */
885 t
.tm_year
!= tm
->tm_year
||
886 t
.tm_mon
!= tm
->tm_mon
||
887 t
.tm_mday
!= tm
->tm_mday
||
888 t
.tm_hour
!= tm
->tm_hour
||
889 t
.tm_min
!= tm
->tm_min
||
890 t
.tm_sec
!= tm
->tm_sec
;
893 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
897 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
901 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
904 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
905 return (weekdays_bits
& (1 << k
));
908 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
) {
918 /* Normalize the current date */
919 mktime_or_timegm(&c
, spec
->utc
);
923 r
= find_matching_component(spec
->year
, &c
.tm_year
);
929 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
931 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
))
935 r
= find_matching_component(spec
->month
, &c
.tm_mon
);
940 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
942 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
946 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
950 r
= find_matching_component(spec
->day
, &c
.tm_mday
);
952 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
953 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
956 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
960 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
962 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
966 r
= find_matching_component(spec
->hour
, &c
.tm_hour
);
968 c
.tm_min
= c
.tm_sec
= 0;
969 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
971 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
975 r
= find_matching_component(spec
->minute
, &c
.tm_min
);
978 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
980 c
.tm_min
= c
.tm_sec
= 0;
984 r
= find_matching_component(spec
->second
, &c
.tm_sec
);
985 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
997 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1005 t
= (time_t) (usec
/ USEC_PER_SEC
) + 1;
1006 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1008 r
= find_next(spec
, &tm
);
1012 t
= mktime_or_timegm(&tm
, spec
->utc
);
1013 if (t
== (time_t) -1)
1016 *next
= (usec_t
) t
* USEC_PER_SEC
;