]>
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/>.
29 #include "alloc-util.h"
30 #include "calendarspec.h"
33 #include "parse-util.h"
34 #include "string-util.h"
36 /* Longest valid date/time range is 1970..2199 */
37 #define MAX_RANGE_LEN 230
40 #define BITS_WEEKDAYS 127
42 static void free_chain(CalendarComponent
*c
) {
52 void calendar_spec_free(CalendarSpec
*c
) {
61 free_chain(c
->minute
);
62 free_chain(c
->microsecond
);
67 static int component_compare(const void *_a
, const void *_b
) {
68 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
70 if ((*a
)->value
< (*b
)->value
)
72 if ((*a
)->value
> (*b
)->value
)
75 if ((*a
)->repeat
< (*b
)->repeat
)
77 if ((*a
)->repeat
> (*b
)->repeat
)
83 static void sort_chain(CalendarComponent
**c
) {
85 CalendarComponent
**b
, *i
, **j
, *next
;
89 for (i
= *c
; i
; i
= i
->next
)
95 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
96 for (i
= *c
; i
; i
= i
->next
)
99 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
104 /* Drop non-unique entries */
105 for (k
= n
-1; k
> 0; k
--) {
106 if (b
[k
-1]->value
== next
->value
&&
107 b
[k
-1]->repeat
== next
->repeat
) {
119 static void fix_year(CalendarComponent
*c
) {
120 /* Turns 12 → 2012, 89 → 1989 */
123 CalendarComponent
*n
= c
->next
;
125 if (c
->value
>= 0 && c
->value
< 70)
128 if (c
->value
>= 70 && c
->value
< 100)
135 int calendar_spec_normalize(CalendarSpec
*c
) {
138 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
139 c
->weekdays_bits
= -1;
141 if (c
->end_of_month
&& !c
->day
)
142 c
->end_of_month
= false;
146 sort_chain(&c
->year
);
147 sort_chain(&c
->month
);
149 sort_chain(&c
->hour
);
150 sort_chain(&c
->minute
);
151 sort_chain(&c
->microsecond
);
156 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
, bool eom
) {
160 if (c
->value
< from
|| c
->value
> to
)
164 * c->repeat must be short enough so at least one repetition may
165 * occur before the end of the interval. For dates scheduled
166 * relative to the end of the month (eom), c->value corresponds
167 * to the Nth last day of the month.
169 if (eom
&& c
->value
- c
->repeat
< from
)
172 if (!eom
&& c
->value
+ c
->repeat
> to
)
176 return chain_valid(c
->next
, from
, to
, eom
);
181 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
184 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
187 if (!chain_valid(c
->year
, MIN_YEAR
, MAX_YEAR
, false))
190 if (!chain_valid(c
->month
, 1, 12, false))
193 if (!chain_valid(c
->day
, 1, 31, c
->end_of_month
))
196 if (!chain_valid(c
->hour
, 0, 23, false))
199 if (!chain_valid(c
->minute
, 0, 59, false))
202 if (!chain_valid(c
->microsecond
, 0, 60*USEC_PER_SEC
-1, false))
208 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
209 static const char *const days
[] = {
220 bool need_comma
= false;
224 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
226 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
228 if (c
->weekdays_bits
& (1 << x
)) {
243 fputs(x
> l
+ 2 ? ".." : ",", f
);
251 if (l
>= 0 && x
> l
+ 1) {
252 fputs(x
> l
+ 2 ? ".." : ",", f
);
257 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
, bool usec
) {
265 assert(c
->value
>= 0);
267 fprintf(f
, "%0*i", space
, c
->value
);
268 else if (c
->value
% USEC_PER_SEC
== 0)
269 fprintf(f
, "%0*i", space
, (int) (c
->value
/ USEC_PER_SEC
));
271 fprintf(f
, "%0*i.%06i", space
, (int) (c
->value
/ USEC_PER_SEC
), (int) (c
->value
% USEC_PER_SEC
));
275 fprintf(f
, "/%i", c
->repeat
);
276 else if (c
->repeat
% USEC_PER_SEC
== 0)
277 fprintf(f
, "/%i", (int) (c
->repeat
/ USEC_PER_SEC
));
279 fprintf(f
, "/%i.%06i", (int) (c
->repeat
/ USEC_PER_SEC
), (int) (c
->repeat
% USEC_PER_SEC
));
284 format_chain(f
, space
, c
->next
, usec
);
288 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
289 CalendarComponent
*cc
;
298 f
= open_memstream(&buf
, &sz
);
302 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
303 format_weekdays(f
, c
);
307 format_chain(f
, 4, c
->year
, false);
309 format_chain(f
, 2, c
->month
, false);
310 fputc(c
->end_of_month
? '~' : '-', f
);
311 format_chain(f
, 2, c
->day
, false);
313 format_chain(f
, 2, c
->hour
, false);
315 format_chain(f
, 2, c
->minute
, false);
319 if (cc
&& !cc
->value
&& cc
->repeat
== USEC_PER_SEC
&& !cc
->next
)
322 format_chain(f
, 2, c
->microsecond
, true);
326 else if (IN_SET(c
->dst
, 0, 1)) {
328 /* If daylight saving is explicitly on or off, let's show the used timezone. */
332 if (!isempty(tzname
[c
->dst
])) {
334 fputs(tzname
[c
->dst
], f
);
338 r
= fflush_and_check(f
);
351 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
352 static const struct {
382 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
385 if (!startswith_no_case(*p
, day_nr
[i
].name
))
388 skip
= strlen(day_nr
[i
].name
);
390 if ((*p
)[skip
] != '-' &&
397 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
402 if (l
> day_nr
[i
].nr
)
405 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
406 c
->weekdays_bits
|= 1 << j
;
413 /* Couldn't find this prefix, so let's assume the
414 weekday was not specified and let's continue with
416 if (i
>= ELEMENTSOF(day_nr
))
417 return first
? 0 : -EINVAL
;
419 /* We reached the end of the string */
423 /* We reached the end of the weekday spec part */
425 *p
+= strspn(*p
, " ");
439 /* Support ranges with "-" for backwards compatibility */
440 } else if (**p
== '-') {
447 } else if (**p
== ',') {
452 /* Allow a trailing comma but not an open range */
453 if (**p
== 0 || **p
== ' ') {
454 *p
+= strspn(*p
, " ");
455 return l
< 0 ? 0 : -EINVAL
;
462 static int parse_component_decimal(const char **p
, bool usec
, unsigned long *res
) {
464 const char *e
= NULL
;
472 value
= strtoul(*p
, &ee
, 10);
477 if ((unsigned long) (int) value
!= value
)
482 if (value
* USEC_PER_SEC
/ USEC_PER_SEC
!= value
)
485 value
*= USEC_PER_SEC
;
489 /* This is the start of a range, not a fractional part */
494 r
= parse_fractional_part_u(&e
, 6, &add
);
498 if (add
+ value
< value
)
511 static int const_chain(int value
, CalendarComponent
**c
) {
512 CalendarComponent
*cc
= NULL
;
516 cc
= new0(CalendarComponent
, 1);
529 static int prepend_component(const char **p
, bool usec
, CalendarComponent
**c
) {
530 unsigned long i
, value
, range_end
, range_inc
, repeat
= 0;
531 CalendarComponent
*cc
;
540 r
= parse_component_decimal(&e
, usec
, &value
);
546 r
= parse_component_decimal(&e
, usec
, &repeat
);
552 } else if (e
[0] == '.' && e
[1] == '.') {
554 r
= parse_component_decimal(&e
, usec
, &range_end
);
558 if (value
>= range_end
)
561 range_inc
= usec
? USEC_PER_SEC
: 1;
563 /* Don't allow impossibly large ranges... */
564 if (range_end
- value
>= MAX_RANGE_LEN
* range_inc
)
567 /* ...or ranges with only a single element */
568 if (range_end
- value
< range_inc
)
571 for (i
= value
; i
<= range_end
; i
+= range_inc
) {
572 r
= const_chain(i
, c
);
578 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= '~' && *e
!= ':')
581 cc
= new0(CalendarComponent
, 1);
594 return prepend_component(p
, usec
, c
);
600 static int parse_chain(const char **p
, bool usec
, CalendarComponent
**c
) {
602 CalendarComponent
*cc
= NULL
;
612 r
= const_chain(0, c
);
615 (*c
)->repeat
= USEC_PER_SEC
;
623 r
= prepend_component(&t
, usec
, &cc
);
634 static int parse_date(const char **p
, CalendarSpec
*c
) {
637 CalendarComponent
*first
, *second
, *third
;
648 r
= parse_chain(&t
, false, &first
);
652 /* Already the end? A ':' as separator? In that case this was a time, not a date */
653 if (*t
== 0 || *t
== ':') {
659 c
->end_of_month
= true;
660 else if (*t
!= '-') {
666 r
= parse_chain(&t
, false, &second
);
672 /* Got two parts, hence it's month and day */
673 if (*t
== ' ' || *t
== 0) {
674 *p
= t
+ strspn(t
, " ");
678 } else if (c
->end_of_month
)
682 c
->end_of_month
= true;
683 else if (*t
!= '-') {
690 r
= parse_chain(&t
, false, &third
);
697 /* Got three parts, hence it is year, month and day */
698 if (*t
== ' ' || *t
== 0) {
699 *p
= t
+ strspn(t
, " ");
712 static int parse_calendar_time(const char **p
, CalendarSpec
*c
) {
713 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
723 /* If no time is specified at all, then this means 00:00:00 */
727 r
= parse_chain(&t
, false, &h
);
737 r
= parse_chain(&t
, false, &m
);
741 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
755 r
= parse_chain(&t
, true, &s
);
759 /* At the end? Then it's hours, minutes and seconds */
767 r
= const_chain(0, &h
);
771 r
= const_chain(0, &m
);
776 r
= const_chain(0, &s
);
795 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
803 c
= new0(CalendarSpec
, 1);
808 utc
= endswith_no_case(p
, " UTC");
811 p
= strndupa(p
, utc
- p
);
813 const char *e
= NULL
;
818 /* Check if the local timezone was specified? */
819 for (j
= 0; j
<= 1; j
++) {
820 if (isempty(tzname
[j
]))
823 e
= endswith_no_case(p
, tzname
[j
]);
834 /* Found one of the two timezones specified? */
835 if (IN_SET(j
, 0, 1)) {
836 p
= strndupa(p
, e
- p
- 1);
846 if (strcaseeq(p
, "minutely")) {
847 r
= const_chain(0, &c
->microsecond
);
851 } else if (strcaseeq(p
, "hourly")) {
852 r
= const_chain(0, &c
->minute
);
855 r
= const_chain(0, &c
->microsecond
);
859 } else if (strcaseeq(p
, "daily")) {
860 r
= const_chain(0, &c
->hour
);
863 r
= const_chain(0, &c
->minute
);
866 r
= const_chain(0, &c
->microsecond
);
870 } else if (strcaseeq(p
, "monthly")) {
871 r
= const_chain(1, &c
->day
);
874 r
= const_chain(0, &c
->hour
);
877 r
= const_chain(0, &c
->minute
);
880 r
= const_chain(0, &c
->microsecond
);
884 } else if (strcaseeq(p
, "annually") ||
885 strcaseeq(p
, "yearly") ||
886 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
888 r
= const_chain(1, &c
->month
);
891 r
= const_chain(1, &c
->day
);
894 r
= const_chain(0, &c
->hour
);
897 r
= const_chain(0, &c
->minute
);
900 r
= const_chain(0, &c
->microsecond
);
904 } else if (strcaseeq(p
, "weekly")) {
906 c
->weekdays_bits
= 1;
908 r
= const_chain(0, &c
->hour
);
911 r
= const_chain(0, &c
->minute
);
914 r
= const_chain(0, &c
->microsecond
);
918 } else if (strcaseeq(p
, "quarterly")) {
920 r
= const_chain(1, &c
->month
);
923 r
= const_chain(4, &c
->month
);
926 r
= const_chain(7, &c
->month
);
929 r
= const_chain(10, &c
->month
);
932 r
= const_chain(1, &c
->day
);
935 r
= const_chain(0, &c
->hour
);
938 r
= const_chain(0, &c
->minute
);
941 r
= const_chain(0, &c
->microsecond
);
945 } else if (strcaseeq(p
, "biannually") ||
946 strcaseeq(p
, "bi-annually") ||
947 strcaseeq(p
, "semiannually") ||
948 strcaseeq(p
, "semi-annually")) {
950 r
= const_chain(1, &c
->month
);
953 r
= const_chain(7, &c
->month
);
956 r
= const_chain(1, &c
->day
);
959 r
= const_chain(0, &c
->hour
);
962 r
= const_chain(0, &c
->minute
);
965 r
= const_chain(0, &c
->microsecond
);
970 r
= parse_weekdays(&p
, c
);
974 r
= parse_date(&p
, c
);
978 r
= parse_calendar_time(&p
, c
);
988 r
= calendar_spec_normalize(c
);
992 if (!calendar_spec_valid(c
)) {
1001 calendar_spec_free(c
);
1005 static int find_matching_component(const CalendarSpec
*spec
, const CalendarComponent
*c
,
1006 struct tm
*tm
, int *val
) {
1007 const CalendarComponent
*n
, *p
= c
;
1021 if (spec
->end_of_month
&& p
== spec
->day
) {
1024 t
.tm_mday
= 1 - c
->value
;
1026 if (mktime_or_timegm(&t
, spec
->utc
) == (time_t) -1 ||
1027 t
.tm_mon
!= tm
->tm_mon
)
1036 if (!d_set
|| v
< d
) {
1041 } else if (c
->repeat
> 0) {
1044 k
= v
+ c
->repeat
* ((*val
- v
+ c
->repeat
-1) / c
->repeat
);
1046 if (!d_set
|| k
< d
) {
1063 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
1069 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
1073 * Set an upper bound on the year so impossible dates like "*-02-31"
1074 * don't cause find_next() to loop forever. tm_year contains years
1075 * since 1900, so adjust it accordingly.
1077 if (tm
->tm_year
+ 1900 > MAX_YEAR
)
1080 /* Did any normalization take place? If so, it was out of bounds before */
1082 t
.tm_year
!= tm
->tm_year
||
1083 t
.tm_mon
!= tm
->tm_mon
||
1084 t
.tm_mday
!= tm
->tm_mday
||
1085 t
.tm_hour
!= tm
->tm_hour
||
1086 t
.tm_min
!= tm
->tm_min
||
1087 t
.tm_sec
!= tm
->tm_sec
;
1090 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
1094 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
1098 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
1101 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
1102 return (weekdays_bits
& (1 << k
));
1105 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
, usec_t
*usec
) {
1117 /* Normalize the current date */
1118 (void) mktime_or_timegm(&c
, spec
->utc
);
1119 c
.tm_isdst
= spec
->dst
;
1122 r
= find_matching_component(spec
, spec
->year
, &c
, &c
.tm_year
);
1128 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1132 if (tm_out_of_bounds(&c
, spec
->utc
))
1136 r
= find_matching_component(spec
, spec
->month
, &c
, &c
.tm_mon
);
1141 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1143 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1147 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1151 r
= find_matching_component(spec
, spec
->day
, &c
, &c
.tm_mday
);
1153 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1154 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1157 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1161 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
1163 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1167 r
= find_matching_component(spec
, spec
->hour
, &c
, &c
.tm_hour
);
1169 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1170 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1172 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1176 r
= find_matching_component(spec
, spec
->minute
, &c
, &c
.tm_min
);
1178 c
.tm_sec
= tm_usec
= 0;
1179 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1181 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1185 c
.tm_sec
= c
.tm_sec
* USEC_PER_SEC
+ tm_usec
;
1186 r
= find_matching_component(spec
, spec
->microsecond
, &c
, &c
.tm_sec
);
1187 tm_usec
= c
.tm_sec
% USEC_PER_SEC
;
1188 c
.tm_sec
/= USEC_PER_SEC
;
1190 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1192 c
.tm_sec
= tm_usec
= 0;
1202 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1212 t
= (time_t) (usec
/ USEC_PER_SEC
);
1213 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1214 tm_usec
= usec
% USEC_PER_SEC
;
1216 r
= find_next(spec
, &tm
, &tm_usec
);
1220 t
= mktime_or_timegm(&tm
, spec
->utc
);
1221 if (t
== (time_t) -1)
1224 *next
= (usec_t
) t
* USEC_PER_SEC
+ tm_usec
;