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/>.
30 #include "alloc-util.h"
31 #include "calendarspec.h"
34 #include "parse-util.h"
35 #include "string-util.h"
37 #define BITS_WEEKDAYS 127
41 static void free_chain(CalendarComponent
*c
) {
51 void calendar_spec_free(CalendarSpec
*c
) {
60 free_chain(c
->minute
);
61 free_chain(c
->microsecond
);
66 static int component_compare(const void *_a
, const void *_b
) {
67 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
69 if ((*a
)->start
< (*b
)->start
)
71 if ((*a
)->start
> (*b
)->start
)
74 if ((*a
)->stop
< (*b
)->stop
)
76 if ((*a
)->stop
> (*b
)->stop
)
79 if ((*a
)->repeat
< (*b
)->repeat
)
81 if ((*a
)->repeat
> (*b
)->repeat
)
87 static void normalize_chain(CalendarComponent
**c
) {
89 CalendarComponent
**b
, *i
, **j
, *next
;
93 for (i
= *c
; i
; i
= i
->next
) {
97 * While we're counting the chain, also normalize `stop`
98 * so the length of the range is a multiple of `repeat`
100 if (i
->stop
> i
->start
&& i
->repeat
> 0)
101 i
->stop
-= (i
->stop
- i
->start
) % i
->repeat
;
108 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
109 for (i
= *c
; i
; i
= i
->next
)
112 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
117 /* Drop non-unique entries */
118 for (k
= n
-1; k
> 0; k
--) {
119 if (b
[k
-1]->start
== next
->start
&&
120 b
[k
-1]->repeat
== next
->repeat
) {
132 static void fix_year(CalendarComponent
*c
) {
133 /* Turns 12 → 2012, 89 → 1989 */
136 if (c
->start
>= 0 && c
->start
< 70)
139 if (c
->stop
>= 0 && c
->stop
< 70)
142 if (c
->start
>= 70 && c
->start
< 100)
145 if (c
->stop
>= 70 && c
->stop
< 100)
152 int calendar_spec_normalize(CalendarSpec
*c
) {
155 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
156 c
->weekdays_bits
= -1;
158 if (c
->end_of_month
&& !c
->day
)
159 c
->end_of_month
= false;
163 normalize_chain(&c
->year
);
164 normalize_chain(&c
->month
);
165 normalize_chain(&c
->day
);
166 normalize_chain(&c
->hour
);
167 normalize_chain(&c
->minute
);
168 normalize_chain(&c
->microsecond
);
173 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
, bool end_of_month
) {
177 /* Forbid dates more than 28 days from the end of the month */
181 if (c
->start
< from
|| c
->start
> to
)
185 * c->repeat must be short enough so at least one repetition may
186 * occur before the end of the interval. For dates scheduled
187 * relative to the end of the month, c->start and c->stop
188 * correspond to the Nth last day of the month.
191 if (c
->stop
< from
|| c
->stop
> to
)
194 if (c
->start
+ c
->repeat
> c
->stop
)
197 if (end_of_month
&& c
->start
- c
->repeat
< from
)
200 if (!end_of_month
&& c
->start
+ c
->repeat
> to
)
205 return chain_valid(c
->next
, from
, to
, end_of_month
);
210 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
213 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
216 if (!chain_valid(c
->year
, MIN_YEAR
, MAX_YEAR
, false))
219 if (!chain_valid(c
->month
, 1, 12, false))
222 if (!chain_valid(c
->day
, 1, 31, c
->end_of_month
))
225 if (!chain_valid(c
->hour
, 0, 23, false))
228 if (!chain_valid(c
->minute
, 0, 59, false))
231 if (!chain_valid(c
->microsecond
, 0, 60*USEC_PER_SEC
-1, false))
237 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
238 static const char *const days
[] = {
249 bool need_comma
= false;
253 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
255 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
257 if (c
->weekdays_bits
& (1 << x
)) {
272 fputs(x
> l
+ 2 ? ".." : ",", f
);
280 if (l
>= 0 && x
> l
+ 1) {
281 fputs(x
> l
+ 2 ? ".." : ",", f
);
286 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
, bool usec
) {
287 int d
= usec
? (int) USEC_PER_SEC
: 1;
296 if (usec
&& c
->start
== 0 && c
->repeat
== USEC_PER_SEC
&& !c
->next
) {
301 assert(c
->start
>= 0);
303 fprintf(f
, "%0*i", space
, c
->start
/ d
);
304 if (c
->start
% d
> 0)
305 fprintf(f
, ".%06i", c
->start
% d
);
308 fprintf(f
, "..%0*i", space
, c
->stop
/ d
);
310 fprintf(f
, ".%06i", c
->stop
% d
);
312 if (c
->repeat
> 0 && !(c
->stop
> 0 && c
->repeat
== d
))
313 fprintf(f
, "/%i", c
->repeat
/ d
);
314 if (c
->repeat
% d
> 0)
315 fprintf(f
, ".%06i", c
->repeat
% d
);
319 format_chain(f
, space
, c
->next
, usec
);
323 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
332 f
= open_memstream(&buf
, &sz
);
336 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
337 format_weekdays(f
, c
);
341 format_chain(f
, 4, c
->year
, false);
343 format_chain(f
, 2, c
->month
, false);
344 fputc(c
->end_of_month
? '~' : '-', f
);
345 format_chain(f
, 2, c
->day
, false);
347 format_chain(f
, 2, c
->hour
, false);
349 format_chain(f
, 2, c
->minute
, false);
351 format_chain(f
, 2, c
->microsecond
, true);
355 else if (IN_SET(c
->dst
, 0, 1)) {
357 /* If daylight saving is explicitly on or off, let's show the used timezone. */
361 if (!isempty(tzname
[c
->dst
])) {
363 fputs(tzname
[c
->dst
], f
);
367 r
= fflush_and_check(f
);
380 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
381 static const struct {
411 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
414 if (!startswith_no_case(*p
, day_nr
[i
].name
))
417 skip
= strlen(day_nr
[i
].name
);
419 if ((*p
)[skip
] != '-' &&
426 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
431 if (l
> day_nr
[i
].nr
)
434 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
435 c
->weekdays_bits
|= 1 << j
;
442 /* Couldn't find this prefix, so let's assume the
443 weekday was not specified and let's continue with
445 if (i
>= ELEMENTSOF(day_nr
))
446 return first
? 0 : -EINVAL
;
448 /* We reached the end of the string */
452 /* We reached the end of the weekday spec part */
454 *p
+= strspn(*p
, " ");
468 /* Support ranges with "-" for backwards compatibility */
469 } else if (**p
== '-') {
476 } else if (**p
== ',') {
481 /* Allow a trailing comma but not an open range */
482 if (**p
== 0 || **p
== ' ') {
483 *p
+= strspn(*p
, " ");
484 return l
< 0 ? 0 : -EINVAL
;
491 static int parse_component_decimal(const char **p
, bool usec
, int *res
) {
493 const char *e
= NULL
;
501 value
= strtoul(*p
, &ee
, 10);
509 if (value
* USEC_PER_SEC
/ USEC_PER_SEC
!= value
)
512 value
*= USEC_PER_SEC
;
514 /* One "." is a decimal point, but ".." is a range separator */
515 if (e
[0] == '.' && e
[1] != '.') {
519 r
= parse_fractional_part_u(&e
, 6, &add
);
523 if (add
+ value
< value
)
538 static int const_chain(int value
, CalendarComponent
**c
) {
539 CalendarComponent
*cc
= NULL
;
543 cc
= new0(CalendarComponent
, 1);
557 static int prepend_component(const char **p
, bool usec
, CalendarComponent
**c
) {
558 int r
, start
, stop
= -1, repeat
= 0;
559 CalendarComponent
*cc
;
567 r
= parse_component_decimal(&e
, usec
, &start
);
571 if (e
[0] == '.' && e
[1] == '.') {
573 r
= parse_component_decimal(&e
, usec
, &stop
);
577 repeat
= usec
? USEC_PER_SEC
: 1;
582 r
= parse_component_decimal(&e
, usec
, &repeat
);
590 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= '~' && *e
!= ':')
593 cc
= new0(CalendarComponent
, 1);
607 return prepend_component(p
, usec
, c
);
613 static int parse_chain(const char **p
, bool usec
, CalendarComponent
**c
) {
615 CalendarComponent
*cc
= NULL
;
625 r
= const_chain(0, c
);
628 (*c
)->repeat
= USEC_PER_SEC
;
636 r
= prepend_component(&t
, usec
, &cc
);
647 static int parse_date(const char **p
, CalendarSpec
*c
) {
650 CalendarComponent
*first
, *second
, *third
;
661 r
= parse_chain(&t
, false, &first
);
665 /* Already the end? A ':' as separator? In that case this was a time, not a date */
666 if (*t
== 0 || *t
== ':') {
672 c
->end_of_month
= true;
673 else if (*t
!= '-') {
679 r
= parse_chain(&t
, false, &second
);
685 /* Got two parts, hence it's month and day */
686 if (*t
== ' ' || *t
== 0) {
687 *p
= t
+ strspn(t
, " ");
691 } else if (c
->end_of_month
) {
698 c
->end_of_month
= true;
699 else if (*t
!= '-') {
706 r
= parse_chain(&t
, false, &third
);
713 /* Got three parts, hence it is year, month and day */
714 if (*t
== ' ' || *t
== 0) {
715 *p
= t
+ strspn(t
, " ");
728 static int parse_calendar_time(const char **p
, CalendarSpec
*c
) {
729 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
739 /* If no time is specified at all, then this means 00:00:00 */
743 r
= parse_chain(&t
, false, &h
);
753 r
= parse_chain(&t
, false, &m
);
757 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
767 r
= parse_chain(&t
, true, &s
);
771 /* At the end? Then it's hours, minutes and seconds */
779 r
= const_chain(0, &h
);
783 r
= const_chain(0, &m
);
788 r
= const_chain(0, &s
);
807 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
815 c
= new0(CalendarSpec
, 1);
820 utc
= endswith_no_case(p
, " UTC");
823 p
= strndupa(p
, utc
- p
);
825 const char *e
= NULL
;
830 /* Check if the local timezone was specified? */
831 for (j
= 0; j
<= 1; j
++) {
832 if (isempty(tzname
[j
]))
835 e
= endswith_no_case(p
, tzname
[j
]);
846 /* Found one of the two timezones specified? */
847 if (IN_SET(j
, 0, 1)) {
848 p
= strndupa(p
, e
- p
- 1);
858 if (strcaseeq(p
, "minutely")) {
859 r
= const_chain(0, &c
->microsecond
);
863 } else if (strcaseeq(p
, "hourly")) {
864 r
= const_chain(0, &c
->minute
);
867 r
= const_chain(0, &c
->microsecond
);
871 } else if (strcaseeq(p
, "daily")) {
872 r
= const_chain(0, &c
->hour
);
875 r
= const_chain(0, &c
->minute
);
878 r
= const_chain(0, &c
->microsecond
);
882 } else if (strcaseeq(p
, "monthly")) {
883 r
= const_chain(1, &c
->day
);
886 r
= const_chain(0, &c
->hour
);
889 r
= const_chain(0, &c
->minute
);
892 r
= const_chain(0, &c
->microsecond
);
896 } else if (strcaseeq(p
, "annually") ||
897 strcaseeq(p
, "yearly") ||
898 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
900 r
= const_chain(1, &c
->month
);
903 r
= const_chain(1, &c
->day
);
906 r
= const_chain(0, &c
->hour
);
909 r
= const_chain(0, &c
->minute
);
912 r
= const_chain(0, &c
->microsecond
);
916 } else if (strcaseeq(p
, "weekly")) {
918 c
->weekdays_bits
= 1;
920 r
= const_chain(0, &c
->hour
);
923 r
= const_chain(0, &c
->minute
);
926 r
= const_chain(0, &c
->microsecond
);
930 } else if (strcaseeq(p
, "quarterly")) {
932 r
= const_chain(1, &c
->month
);
935 r
= const_chain(4, &c
->month
);
938 r
= const_chain(7, &c
->month
);
941 r
= const_chain(10, &c
->month
);
944 r
= const_chain(1, &c
->day
);
947 r
= const_chain(0, &c
->hour
);
950 r
= const_chain(0, &c
->minute
);
953 r
= const_chain(0, &c
->microsecond
);
957 } else if (strcaseeq(p
, "biannually") ||
958 strcaseeq(p
, "bi-annually") ||
959 strcaseeq(p
, "semiannually") ||
960 strcaseeq(p
, "semi-annually")) {
962 r
= const_chain(1, &c
->month
);
965 r
= const_chain(7, &c
->month
);
968 r
= const_chain(1, &c
->day
);
971 r
= const_chain(0, &c
->hour
);
974 r
= const_chain(0, &c
->minute
);
977 r
= const_chain(0, &c
->microsecond
);
982 r
= parse_weekdays(&p
, c
);
986 r
= parse_date(&p
, c
);
990 r
= parse_calendar_time(&p
, c
);
1000 r
= calendar_spec_normalize(c
);
1004 if (!calendar_spec_valid(c
)) {
1013 calendar_spec_free(c
);
1017 static int find_end_of_month(struct tm
*tm
, bool utc
, int day
) {
1021 t
.tm_mday
= 1 - day
;
1023 if (mktime_or_timegm(&t
, utc
) < 0 ||
1024 t
.tm_mon
!= tm
->tm_mon
)
1030 static int find_matching_component(const CalendarSpec
*spec
, const CalendarComponent
*c
,
1031 struct tm
*tm
, int *val
) {
1032 const CalendarComponent
*p
= c
;
1033 int start
, stop
, d
= -1;
1046 if (spec
->end_of_month
&& p
== spec
->day
) {
1047 start
= find_end_of_month(tm
, spec
->utc
, start
);
1048 stop
= find_end_of_month(tm
, spec
->utc
, stop
);
1051 SWAP_TWO(start
, stop
);
1054 if (start
>= *val
) {
1056 if (!d_set
|| start
< d
) {
1061 } else if (c
->repeat
> 0) {
1064 k
= start
+ c
->repeat
* ((*val
- start
+ c
->repeat
- 1) / c
->repeat
);
1066 if ((!d_set
|| k
< d
) && (stop
< 0 || k
<= stop
)) {
1083 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
1089 if (mktime_or_timegm(&t
, utc
) < 0)
1093 * Set an upper bound on the year so impossible dates like "*-02-31"
1094 * don't cause find_next() to loop forever. tm_year contains years
1095 * since 1900, so adjust it accordingly.
1097 if (tm
->tm_year
+ 1900 > MAX_YEAR
)
1100 /* Did any normalization take place? If so, it was out of bounds before */
1102 t
.tm_year
!= tm
->tm_year
||
1103 t
.tm_mon
!= tm
->tm_mon
||
1104 t
.tm_mday
!= tm
->tm_mday
||
1105 t
.tm_hour
!= tm
->tm_hour
||
1106 t
.tm_min
!= tm
->tm_min
||
1107 t
.tm_sec
!= tm
->tm_sec
;
1110 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
1114 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
1118 if (mktime_or_timegm(&t
, utc
) < 0)
1121 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
1122 return (weekdays_bits
& (1 << k
));
1125 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
, usec_t
*usec
) {
1137 /* Normalize the current date */
1138 (void) mktime_or_timegm(&c
, spec
->utc
);
1139 c
.tm_isdst
= spec
->dst
;
1142 r
= find_matching_component(spec
, spec
->year
, &c
, &c
.tm_year
);
1148 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1152 if (tm_out_of_bounds(&c
, spec
->utc
))
1156 r
= find_matching_component(spec
, spec
->month
, &c
, &c
.tm_mon
);
1161 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1163 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1167 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1171 r
= find_matching_component(spec
, spec
->day
, &c
, &c
.tm_mday
);
1173 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1174 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1177 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1181 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
1183 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1187 r
= find_matching_component(spec
, spec
->hour
, &c
, &c
.tm_hour
);
1189 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1190 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1192 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1196 r
= find_matching_component(spec
, spec
->minute
, &c
, &c
.tm_min
);
1198 c
.tm_sec
= tm_usec
= 0;
1199 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1201 c
.tm_min
= c
.tm_sec
= tm_usec
= 0;
1205 c
.tm_sec
= c
.tm_sec
* USEC_PER_SEC
+ tm_usec
;
1206 r
= find_matching_component(spec
, spec
->microsecond
, &c
, &c
.tm_sec
);
1207 tm_usec
= c
.tm_sec
% USEC_PER_SEC
;
1208 c
.tm_sec
/= USEC_PER_SEC
;
1210 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
1212 c
.tm_sec
= tm_usec
= 0;
1222 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1231 if (usec
> USEC_TIMESTAMP_FORMATTABLE_MAX
)
1235 t
= (time_t) (usec
/ USEC_PER_SEC
);
1236 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1237 tm_usec
= usec
% USEC_PER_SEC
;
1239 r
= find_next(spec
, &tm
, &tm_usec
);
1243 t
= mktime_or_timegm(&tm
, spec
->utc
);
1247 *next
= (usec_t
) t
* USEC_PER_SEC
+ tm_usec
;