]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/calendarspec.c
2 This file is part of systemd.
4 Copyright 2012 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "alloc-util.h"
29 #include "calendarspec.h"
32 #include "parse-util.h"
33 #include "string-util.h"
35 #define BITS_WEEKDAYS 127
37 static void free_chain(CalendarComponent
*c
) {
47 void calendar_spec_free(CalendarSpec
*c
) {
56 free_chain(c
->minute
);
57 free_chain(c
->microsecond
);
62 static int component_compare(const void *_a
, const void *_b
) {
63 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
65 if ((*a
)->value
< (*b
)->value
)
67 if ((*a
)->value
> (*b
)->value
)
70 if ((*a
)->repeat
< (*b
)->repeat
)
72 if ((*a
)->repeat
> (*b
)->repeat
)
78 static void sort_chain(CalendarComponent
**c
) {
80 CalendarComponent
**b
, *i
, **j
, *next
;
84 for (i
= *c
; i
; i
= i
->next
)
90 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
91 for (i
= *c
; i
; i
= i
->next
)
94 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
99 /* Drop non-unique entries */
100 for (k
= n
-1; k
> 0; k
--) {
101 if (b
[k
-1]->value
== next
->value
&&
102 b
[k
-1]->repeat
== next
->repeat
) {
114 static void fix_year(CalendarComponent
*c
) {
115 /* Turns 12 → 2012, 89 → 1989 */
118 CalendarComponent
*n
= c
->next
;
120 if (c
->value
>= 0 && c
->value
< 70)
123 if (c
->value
>= 70 && c
->value
< 100)
130 int calendar_spec_normalize(CalendarSpec
*c
) {
133 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
134 c
->weekdays_bits
= -1;
138 sort_chain(&c
->year
);
139 sort_chain(&c
->month
);
141 sort_chain(&c
->hour
);
142 sort_chain(&c
->minute
);
143 sort_chain(&c
->microsecond
);
148 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
) {
152 if (c
->value
< from
|| c
->value
> to
)
155 if (c
->value
+ c
->repeat
> to
)
159 return chain_valid(c
->next
, from
, to
);
164 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
167 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
170 if (!chain_valid(c
->year
, 1970, 2199))
173 if (!chain_valid(c
->month
, 1, 12))
176 if (!chain_valid(c
->day
, 1, 31))
179 if (!chain_valid(c
->hour
, 0, 23))
182 if (!chain_valid(c
->minute
, 0, 59))
185 if (!chain_valid(c
->microsecond
, 0, 60*USEC_PER_SEC
-1))
191 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
192 static const char *const days
[] = {
203 bool need_colon
= false;
207 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
209 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
211 if (c
->weekdays_bits
& (1 << x
)) {
226 fputc(x
> l
+ 2 ? '-' : ',', f
);
234 if (l
>= 0 && x
> l
+ 1) {
235 fputc(x
> l
+ 2 ? '-' : ',', f
);
240 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
, bool usec
) {
248 assert(c
->value
>= 0);
250 fprintf(f
, "%0*i", space
, c
->value
);
251 else if (c
->value
% USEC_PER_SEC
== 0)
252 fprintf(f
, "%0*i", space
, (int) (c
->value
/ USEC_PER_SEC
));
254 fprintf(f
, "%0*i.%06i", space
, (int) (c
->value
/ USEC_PER_SEC
), (int) (c
->value
% USEC_PER_SEC
));
258 fprintf(f
, "/%i", c
->repeat
);
259 else if (c
->repeat
% USEC_PER_SEC
== 0)
260 fprintf(f
, "/%i", (int) (c
->repeat
/ USEC_PER_SEC
));
262 fprintf(f
, "/%i.%06i", (int) (c
->repeat
/ USEC_PER_SEC
), (int) (c
->repeat
% USEC_PER_SEC
));
267 format_chain(f
, space
, c
->next
, usec
);
271 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
280 f
= open_memstream(&buf
, &sz
);
284 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
285 format_weekdays(f
, c
);
289 format_chain(f
, 4, c
->year
, false);
291 format_chain(f
, 2, c
->month
, false);
293 format_chain(f
, 2, c
->day
, false);
295 format_chain(f
, 2, c
->hour
, false);
297 format_chain(f
, 2, c
->minute
, false);
299 format_chain(f
, 2, c
->microsecond
, true);
304 r
= fflush_and_check(f
);
317 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
318 static const struct {
348 if (!first
&& **p
== ' ')
351 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
354 if (!startswith_no_case(*p
, day_nr
[i
].name
))
357 skip
= strlen(day_nr
[i
].name
);
359 if ((*p
)[skip
] != '-' &&
365 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
370 if (l
> day_nr
[i
].nr
)
373 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
374 c
->weekdays_bits
|= 1 << j
;
381 /* Couldn't find this prefix, so let's assume the
382 weekday was not specified and let's continue with
384 if (i
>= ELEMENTSOF(day_nr
))
385 return first
? 0 : -EINVAL
;
387 /* We reached the end of the string */
391 /* We reached the end of the weekday spec part */
393 *p
+= strspn(*p
, " ");
410 static int parse_component_decimal(const char **p
, bool usec
, unsigned long *res
) {
412 const char *e
= NULL
;
417 value
= strtoul(*p
, &ee
, 10);
422 if ((unsigned long) (int) value
!= value
)
427 if (value
* USEC_PER_SEC
/ USEC_PER_SEC
!= value
)
430 value
*= USEC_PER_SEC
;
435 r
= parse_fractional_part_u(&e
, 6, &add
);
439 if (add
+ value
< value
)
451 static int prepend_component(const char **p
, bool usec
, CalendarComponent
**c
) {
452 unsigned long value
, repeat
= 0;
453 CalendarComponent
*cc
;
462 r
= parse_component_decimal(&e
, usec
, &value
);
468 r
= parse_component_decimal(&e
, usec
, &repeat
);
476 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= ':')
479 cc
= new0(CalendarComponent
, 1);
492 return prepend_component(p
, usec
, c
);
498 static int const_chain(int value
, CalendarComponent
**c
) {
499 CalendarComponent
*cc
= NULL
;
503 cc
= new0(CalendarComponent
, 1);
516 static int parse_chain(const char **p
, bool usec
, CalendarComponent
**c
) {
518 CalendarComponent
*cc
= NULL
;
528 r
= const_chain(0, c
);
531 (*c
)->repeat
= USEC_PER_SEC
;
539 r
= prepend_component(&t
, usec
, &cc
);
550 static int parse_date(const char **p
, CalendarSpec
*c
) {
553 CalendarComponent
*first
, *second
, *third
;
564 r
= parse_chain(&t
, false, &first
);
568 /* Already the end? A ':' as separator? In that case this was a time, not a date */
569 if (*t
== 0 || *t
== ':') {
580 r
= parse_chain(&t
, false, &second
);
586 /* Got two parts, hence it's month and day */
587 if (*t
== ' ' || *t
== 0) {
588 *p
= t
+ strspn(t
, " ");
601 r
= parse_chain(&t
, false, &third
);
608 /* Got tree parts, hence it is year, month and day */
609 if (*t
== ' ' || *t
== 0) {
610 *p
= t
+ strspn(t
, " ");
623 static int parse_calendar_time(const char **p
, CalendarSpec
*c
) {
624 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
635 /* If no time is specified at all, but a date of some
636 * kind, then this means 00:00:00 */
637 if (c
->day
|| c
->weekdays_bits
> 0)
643 r
= parse_chain(&t
, false, &h
);
653 r
= parse_chain(&t
, false, &m
);
657 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
671 r
= parse_chain(&t
, true, &s
);
675 /* At the end? Then it's hours, minutes and seconds */
683 r
= const_chain(0, &h
);
687 r
= const_chain(0, &m
);
692 r
= const_chain(0, &s
);
711 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
722 c
= new0(CalendarSpec
, 1);
726 utc
= endswith_no_case(p
, " UTC");
729 p
= strndupa(p
, utc
- p
);
732 if (strcaseeq(p
, "minutely")) {
733 r
= const_chain(0, &c
->microsecond
);
737 } else if (strcaseeq(p
, "hourly")) {
738 r
= const_chain(0, &c
->minute
);
741 r
= const_chain(0, &c
->microsecond
);
745 } else if (strcaseeq(p
, "daily")) {
746 r
= const_chain(0, &c
->hour
);
749 r
= const_chain(0, &c
->minute
);
752 r
= const_chain(0, &c
->microsecond
);
756 } else if (strcaseeq(p
, "monthly")) {
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
->microsecond
);
770 } else if (strcaseeq(p
, "annually") ||
771 strcaseeq(p
, "yearly") ||
772 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
774 r
= const_chain(1, &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
->microsecond
);
790 } else if (strcaseeq(p
, "weekly")) {
792 c
->weekdays_bits
= 1;
794 r
= const_chain(0, &c
->hour
);
797 r
= const_chain(0, &c
->minute
);
800 r
= const_chain(0, &c
->microsecond
);
804 } else if (strcaseeq(p
, "quarterly")) {
806 r
= const_chain(1, &c
->month
);
809 r
= const_chain(4, &c
->month
);
812 r
= const_chain(7, &c
->month
);
815 r
= const_chain(10, &c
->month
);
818 r
= const_chain(1, &c
->day
);
821 r
= const_chain(0, &c
->hour
);
824 r
= const_chain(0, &c
->minute
);
827 r
= const_chain(0, &c
->microsecond
);
831 } else if (strcaseeq(p
, "biannually") ||
832 strcaseeq(p
, "bi-annually") ||
833 strcaseeq(p
, "semiannually") ||
834 strcaseeq(p
, "semi-annually")) {
836 r
= const_chain(1, &c
->month
);
839 r
= const_chain(7, &c
->month
);
842 r
= const_chain(1, &c
->day
);
845 r
= const_chain(0, &c
->hour
);
848 r
= const_chain(0, &c
->minute
);
851 r
= const_chain(0, &c
->microsecond
);
856 r
= parse_weekdays(&p
, c
);
860 r
= parse_date(&p
, c
);
864 r
= parse_calendar_time(&p
, c
);
874 r
= calendar_spec_normalize(c
);
878 if (!calendar_spec_valid(c
)) {
887 calendar_spec_free(c
);
891 static int find_matching_component(const CalendarComponent
*c
, int *val
) {
892 const CalendarComponent
*n
;
905 if (c
->value
>= *val
) {
907 if (!d_set
|| c
->value
< d
) {
912 } else if (c
->repeat
> 0) {
915 k
= c
->value
+ c
->repeat
* ((*val
- c
->value
+ c
->repeat
-1) / c
->repeat
);
917 if (!d_set
|| k
< d
) {
934 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
940 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
943 /* Did any normalization take place? If so, it was out of bounds before */
945 t
.tm_year
!= tm
->tm_year
||
946 t
.tm_mon
!= tm
->tm_mon
||
947 t
.tm_mday
!= tm
->tm_mday
||
948 t
.tm_hour
!= tm
->tm_hour
||
949 t
.tm_min
!= tm
->tm_min
||
950 t
.tm_sec
!= tm
->tm_sec
;
953 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
957 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
961 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
964 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
965 return (weekdays_bits
& (1 << k
));
968 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
, usec_t
*usec
) {
980 /* Normalize the current date */
981 (void) mktime_or_timegm(&c
, spec
->utc
);
985 r
= find_matching_component(spec
->year
, &c
.tm_year
);
991 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
995 if (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
;