]>
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/>.
30 #include "alloc-util.h"
31 #include "calendarspec.h"
34 #include "parse-util.h"
35 #include "string-util.h"
37 #define BITS_WEEKDAYS 127
39 static void free_chain(CalendarComponent
*c
) {
49 void calendar_spec_free(CalendarSpec
*c
) {
58 free_chain(c
->minute
);
59 free_chain(c
->microsecond
);
64 static int component_compare(const void *_a
, const void *_b
) {
65 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
67 if ((*a
)->value
< (*b
)->value
)
69 if ((*a
)->value
> (*b
)->value
)
72 if ((*a
)->repeat
< (*b
)->repeat
)
74 if ((*a
)->repeat
> (*b
)->repeat
)
80 static void sort_chain(CalendarComponent
**c
) {
82 CalendarComponent
**b
, *i
, **j
, *next
;
86 for (i
= *c
; i
; i
= i
->next
)
92 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
93 for (i
= *c
; i
; i
= i
->next
)
96 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
101 /* Drop non-unique entries */
102 for (k
= n
-1; k
> 0; k
--) {
103 if (b
[k
-1]->value
== next
->value
&&
104 b
[k
-1]->repeat
== next
->repeat
) {
116 static void fix_year(CalendarComponent
*c
) {
117 /* Turns 12 → 2012, 89 → 1989 */
120 CalendarComponent
*n
= c
->next
;
122 if (c
->value
>= 0 && c
->value
< 70)
125 if (c
->value
>= 70 && c
->value
< 100)
132 int calendar_spec_normalize(CalendarSpec
*c
) {
135 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
136 c
->weekdays_bits
= -1;
140 sort_chain(&c
->year
);
141 sort_chain(&c
->month
);
143 sort_chain(&c
->hour
);
144 sort_chain(&c
->minute
);
145 sort_chain(&c
->microsecond
);
150 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
) {
154 if (c
->value
< from
|| c
->value
> to
)
157 if (c
->value
+ c
->repeat
> to
)
161 return chain_valid(c
->next
, from
, to
);
166 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
169 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
172 if (!chain_valid(c
->year
, 1970, 2199))
175 if (!chain_valid(c
->month
, 1, 12))
178 if (!chain_valid(c
->day
, 1, 31))
181 if (!chain_valid(c
->hour
, 0, 23))
184 if (!chain_valid(c
->minute
, 0, 59))
187 if (!chain_valid(c
->microsecond
, 0, 60*USEC_PER_SEC
-1))
193 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
194 static const char *const days
[] = {
205 bool need_colon
= false;
209 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
211 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
213 if (c
->weekdays_bits
& (1 << x
)) {
228 fputc(x
> l
+ 2 ? '-' : ',', f
);
236 if (l
>= 0 && x
> l
+ 1) {
237 fputc(x
> l
+ 2 ? '-' : ',', f
);
242 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
, bool usec
) {
250 assert(c
->value
>= 0);
252 fprintf(f
, "%0*i", space
, c
->value
);
253 else if (c
->value
% USEC_PER_SEC
== 0)
254 fprintf(f
, "%0*i", space
, (int) (c
->value
/ USEC_PER_SEC
));
256 fprintf(f
, "%0*i.%06i", space
, (int) (c
->value
/ USEC_PER_SEC
), (int) (c
->value
% USEC_PER_SEC
));
260 fprintf(f
, "/%i", c
->repeat
);
261 else if (c
->repeat
% USEC_PER_SEC
== 0)
262 fprintf(f
, "/%i", (int) (c
->repeat
/ USEC_PER_SEC
));
264 fprintf(f
, "/%i.%06i", (int) (c
->repeat
/ USEC_PER_SEC
), (int) (c
->repeat
% USEC_PER_SEC
));
269 format_chain(f
, space
, c
->next
, usec
);
273 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
282 f
= open_memstream(&buf
, &sz
);
286 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
287 format_weekdays(f
, c
);
291 format_chain(f
, 4, c
->year
, false);
293 format_chain(f
, 2, c
->month
, false);
295 format_chain(f
, 2, c
->day
, false);
297 format_chain(f
, 2, c
->hour
, false);
299 format_chain(f
, 2, c
->minute
, false);
301 format_chain(f
, 2, c
->microsecond
, true);
306 r
= fflush_and_check(f
);
319 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
320 static const struct {
350 if (!first
&& **p
== ' ')
353 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
356 if (!startswith_no_case(*p
, day_nr
[i
].name
))
359 skip
= strlen(day_nr
[i
].name
);
361 if ((*p
)[skip
] != '-' &&
367 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
372 if (l
> day_nr
[i
].nr
)
375 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
376 c
->weekdays_bits
|= 1 << j
;
383 /* Couldn't find this prefix, so let's assume the
384 weekday was not specified and let's continue with
386 if (i
>= ELEMENTSOF(day_nr
))
387 return first
? 0 : -EINVAL
;
389 /* We reached the end of the string */
393 /* We reached the end of the weekday spec part */
395 *p
+= strspn(*p
, " ");
412 static int parse_component_decimal(const char **p
, bool usec
, unsigned long *res
) {
414 const char *e
= NULL
;
419 value
= strtoul(*p
, &ee
, 10);
424 if ((unsigned long) (int) value
!= value
)
429 if (value
* USEC_PER_SEC
/ USEC_PER_SEC
!= value
)
432 value
*= USEC_PER_SEC
;
437 r
= parse_fractional_part_u(&e
, 6, &add
);
441 if (add
+ value
< value
)
453 static int prepend_component(const char **p
, bool usec
, CalendarComponent
**c
) {
454 unsigned long value
, repeat
= 0;
455 CalendarComponent
*cc
;
464 r
= parse_component_decimal(&e
, usec
, &value
);
470 r
= parse_component_decimal(&e
, usec
, &repeat
);
478 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= ':')
481 cc
= new0(CalendarComponent
, 1);
494 return prepend_component(p
, usec
, c
);
500 static int const_chain(int value
, CalendarComponent
**c
) {
501 CalendarComponent
*cc
= NULL
;
505 cc
= new0(CalendarComponent
, 1);
518 static int parse_chain(const char **p
, bool usec
, CalendarComponent
**c
) {
520 CalendarComponent
*cc
= NULL
;
530 r
= const_chain(0, c
);
533 (*c
)->repeat
= USEC_PER_SEC
;
541 r
= prepend_component(&t
, usec
, &cc
);
552 static int parse_date(const char **p
, CalendarSpec
*c
) {
555 CalendarComponent
*first
, *second
, *third
;
566 r
= parse_chain(&t
, false, &first
);
570 /* Already the end? A ':' as separator? In that case this was a time, not a date */
571 if (*t
== 0 || *t
== ':') {
582 r
= parse_chain(&t
, false, &second
);
588 /* Got two parts, hence it's month and day */
589 if (*t
== ' ' || *t
== 0) {
590 *p
= t
+ strspn(t
, " ");
603 r
= parse_chain(&t
, false, &third
);
610 /* Got tree parts, hence it is year, month and day */
611 if (*t
== ' ' || *t
== 0) {
612 *p
= t
+ strspn(t
, " ");
625 static int parse_calendar_time(const char **p
, CalendarSpec
*c
) {
626 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
637 /* If no time is specified at all, but a date of some
638 * kind, then this means 00:00:00 */
639 if (c
->day
|| c
->weekdays_bits
> 0)
645 r
= parse_chain(&t
, false, &h
);
655 r
= parse_chain(&t
, false, &m
);
659 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
673 r
= parse_chain(&t
, true, &s
);
677 /* At the end? Then it's hours, minutes and seconds */
685 r
= const_chain(0, &h
);
689 r
= const_chain(0, &m
);
694 r
= const_chain(0, &s
);
713 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
724 c
= new0(CalendarSpec
, 1);
728 utc
= endswith_no_case(p
, " UTC");
731 p
= strndupa(p
, utc
- p
);
734 if (strcaseeq(p
, "minutely")) {
735 r
= const_chain(0, &c
->microsecond
);
739 } else if (strcaseeq(p
, "hourly")) {
740 r
= const_chain(0, &c
->minute
);
743 r
= const_chain(0, &c
->microsecond
);
747 } else if (strcaseeq(p
, "daily")) {
748 r
= const_chain(0, &c
->hour
);
751 r
= const_chain(0, &c
->minute
);
754 r
= const_chain(0, &c
->microsecond
);
758 } else if (strcaseeq(p
, "monthly")) {
759 r
= const_chain(1, &c
->day
);
762 r
= const_chain(0, &c
->hour
);
765 r
= const_chain(0, &c
->minute
);
768 r
= const_chain(0, &c
->microsecond
);
772 } else if (strcaseeq(p
, "annually") ||
773 strcaseeq(p
, "yearly") ||
774 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
776 r
= const_chain(1, &c
->month
);
779 r
= const_chain(1, &c
->day
);
782 r
= const_chain(0, &c
->hour
);
785 r
= const_chain(0, &c
->minute
);
788 r
= const_chain(0, &c
->microsecond
);
792 } else if (strcaseeq(p
, "weekly")) {
794 c
->weekdays_bits
= 1;
796 r
= const_chain(0, &c
->hour
);
799 r
= const_chain(0, &c
->minute
);
802 r
= const_chain(0, &c
->microsecond
);
806 } else if (strcaseeq(p
, "quarterly")) {
808 r
= const_chain(1, &c
->month
);
811 r
= const_chain(4, &c
->month
);
814 r
= const_chain(7, &c
->month
);
817 r
= const_chain(10, &c
->month
);
820 r
= const_chain(1, &c
->day
);
823 r
= const_chain(0, &c
->hour
);
826 r
= const_chain(0, &c
->minute
);
829 r
= const_chain(0, &c
->microsecond
);
833 } else if (strcaseeq(p
, "biannually") ||
834 strcaseeq(p
, "bi-annually") ||
835 strcaseeq(p
, "semiannually") ||
836 strcaseeq(p
, "semi-annually")) {
838 r
= const_chain(1, &c
->month
);
841 r
= const_chain(7, &c
->month
);
844 r
= const_chain(1, &c
->day
);
847 r
= const_chain(0, &c
->hour
);
850 r
= const_chain(0, &c
->minute
);
853 r
= const_chain(0, &c
->microsecond
);
858 r
= parse_weekdays(&p
, c
);
862 r
= parse_date(&p
, c
);
866 r
= parse_calendar_time(&p
, c
);
876 r
= calendar_spec_normalize(c
);
880 if (!calendar_spec_valid(c
)) {
889 calendar_spec_free(c
);
893 static int find_matching_component(const CalendarComponent
*c
, int *val
) {
894 const CalendarComponent
*n
;
907 if (c
->value
>= *val
) {
909 if (!d_set
|| c
->value
< d
) {
914 } else if (c
->repeat
> 0) {
917 k
= c
->value
+ c
->repeat
* ((*val
- c
->value
+ c
->repeat
-1) / c
->repeat
);
919 if (!d_set
|| k
< d
) {
936 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
942 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
945 /* Did any normalization take place? If so, it was out of bounds before */
947 t
.tm_year
!= tm
->tm_year
||
948 t
.tm_mon
!= tm
->tm_mon
||
949 t
.tm_mday
!= tm
->tm_mday
||
950 t
.tm_hour
!= tm
->tm_hour
||
951 t
.tm_min
!= tm
->tm_min
||
952 t
.tm_sec
!= tm
->tm_sec
;
955 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
959 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
963 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
966 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
967 return (weekdays_bits
& (1 << k
));
970 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
, usec_t
*usec
) {
982 /* Normalize the current date */
983 mktime_or_timegm(&c
, spec
->utc
);
987 r
= find_matching_component(spec
->year
, &c
.tm_year
);
993 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
995 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
))
999 r
= find_matching_component(spec
->month
, &c
.tm_mon
);
1004 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1006 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1010 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1014 r
= find_matching_component(spec
->day
, &c
.tm_mday
);
1016 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1017 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1020 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1024 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
1026 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1030 r
= find_matching_component(spec
->hour
, &c
.tm_hour
);
1032 c
.tm_min
= c
.tm_sec
= 0;
1033 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1035 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1039 r
= find_matching_component(spec
->minute
, &c
.tm_min
);
1042 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1044 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1048 c
.tm_sec
= c
.tm_sec
* USEC_PER_SEC
+ tm_usec
;
1049 r
= find_matching_component(spec
->microsecond
, &c
.tm_sec
);
1050 tm_usec
= c
.tm_sec
% USEC_PER_SEC
;
1051 c
.tm_sec
/= USEC_PER_SEC
;
1053 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1055 c
.tm_sec
= tm_usec
= 0;
1065 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1075 t
= (time_t) (usec
/ USEC_PER_SEC
);
1076 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1077 tm_usec
= usec
% USEC_PER_SEC
;
1079 r
= find_next(spec
, &tm
, &tm_usec
);
1083 t
= mktime_or_timegm(&tm
, spec
->utc
);
1084 if (t
== (time_t) -1)
1087 *next
= (usec_t
) t
* USEC_PER_SEC
+ tm_usec
;