]>
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 "alloc-util.h"
26 #include "string-util.h"
27 #include "calendarspec.h"
30 #define BITS_WEEKDAYS 127
32 static void free_chain(CalendarComponent
*c
) {
42 void calendar_spec_free(CalendarSpec
*c
) {
51 free_chain(c
->minute
);
52 free_chain(c
->second
);
57 static int component_compare(const void *_a
, const void *_b
) {
58 CalendarComponent
* const *a
= _a
, * const *b
= _b
;
60 if ((*a
)->value
< (*b
)->value
)
62 if ((*a
)->value
> (*b
)->value
)
65 if ((*a
)->repeat
< (*b
)->repeat
)
67 if ((*a
)->repeat
> (*b
)->repeat
)
73 static void sort_chain(CalendarComponent
**c
) {
75 CalendarComponent
**b
, *i
, **j
, *next
;
79 for (i
= *c
; i
; i
= i
->next
)
85 j
= b
= alloca(sizeof(CalendarComponent
*) * n
);
86 for (i
= *c
; i
; i
= i
->next
)
89 qsort(b
, n
, sizeof(CalendarComponent
*), component_compare
);
94 /* Drop non-unique entries */
95 for (k
= n
-1; k
> 0; k
--) {
96 if (b
[k
-1]->value
== next
->value
&&
97 b
[k
-1]->repeat
== next
->repeat
) {
109 static void fix_year(CalendarComponent
*c
) {
110 /* Turns 12 → 2012, 89 → 1989 */
113 CalendarComponent
*n
= c
->next
;
115 if (c
->value
>= 0 && c
->value
< 70)
118 if (c
->value
>= 70 && c
->value
< 100)
125 int calendar_spec_normalize(CalendarSpec
*c
) {
128 if (c
->weekdays_bits
<= 0 || c
->weekdays_bits
>= BITS_WEEKDAYS
)
129 c
->weekdays_bits
= -1;
133 sort_chain(&c
->year
);
134 sort_chain(&c
->month
);
136 sort_chain(&c
->hour
);
137 sort_chain(&c
->minute
);
138 sort_chain(&c
->second
);
143 _pure_
static bool chain_valid(CalendarComponent
*c
, int from
, int to
) {
147 if (c
->value
< from
|| c
->value
> to
)
150 if (c
->value
+ c
->repeat
> to
)
154 return chain_valid(c
->next
, from
, to
);
159 _pure_
bool calendar_spec_valid(CalendarSpec
*c
) {
162 if (c
->weekdays_bits
> BITS_WEEKDAYS
)
165 if (!chain_valid(c
->year
, 1970, 2199))
168 if (!chain_valid(c
->month
, 1, 12))
171 if (!chain_valid(c
->day
, 1, 31))
174 if (!chain_valid(c
->hour
, 0, 23))
177 if (!chain_valid(c
->minute
, 0, 59))
180 if (!chain_valid(c
->second
, 0, 59))
186 static void format_weekdays(FILE *f
, const CalendarSpec
*c
) {
187 static const char *const days
[] = {
198 bool need_colon
= false;
202 assert(c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
);
204 for (x
= 0, l
= -1; x
< (int) ELEMENTSOF(days
); x
++) {
206 if (c
->weekdays_bits
& (1 << x
)) {
221 fputc(x
> l
+ 2 ? '-' : ',', f
);
229 if (l
>= 0 && x
> l
+ 1) {
230 fputc(x
> l
+ 2 ? '-' : ',', f
);
235 static void format_chain(FILE *f
, int space
, const CalendarComponent
*c
) {
243 assert(c
->value
>= 0);
244 fprintf(f
, "%0*i", space
, c
->value
);
247 fprintf(f
, "/%i", c
->repeat
);
251 format_chain(f
, space
, c
->next
);
255 int calendar_spec_to_string(const CalendarSpec
*c
, char **p
) {
264 f
= open_memstream(&buf
, &sz
);
268 if (c
->weekdays_bits
> 0 && c
->weekdays_bits
<= BITS_WEEKDAYS
) {
269 format_weekdays(f
, c
);
273 format_chain(f
, 4, c
->year
);
275 format_chain(f
, 2, c
->month
);
277 format_chain(f
, 2, c
->day
);
279 format_chain(f
, 2, c
->hour
);
281 format_chain(f
, 2, c
->minute
);
283 format_chain(f
, 2, c
->second
);
288 r
= fflush_and_check(f
);
301 static int parse_weekdays(const char **p
, CalendarSpec
*c
) {
302 static const struct {
332 if (!first
&& **p
== ' ')
335 for (i
= 0; i
< ELEMENTSOF(day_nr
); i
++) {
338 if (!startswith_no_case(*p
, day_nr
[i
].name
))
341 skip
= strlen(day_nr
[i
].name
);
343 if ((*p
)[skip
] != '-' &&
349 c
->weekdays_bits
|= 1 << day_nr
[i
].nr
;
354 if (l
> day_nr
[i
].nr
)
357 for (j
= l
+ 1; j
< day_nr
[i
].nr
; j
++)
358 c
->weekdays_bits
|= 1 << j
;
365 /* Couldn't find this prefix, so let's assume the
366 weekday was not specified and let's continue with
368 if (i
>= ELEMENTSOF(day_nr
))
369 return first
? 0 : -EINVAL
;
371 /* We reached the end of the string */
375 /* We reached the end of the weekday spec part */
377 *p
+= strspn(*p
, " ");
394 static int prepend_component(const char **p
, CalendarComponent
**c
) {
395 unsigned long value
, repeat
= 0;
396 char *e
= NULL
, *ee
= NULL
;
397 CalendarComponent
*cc
;
403 value
= strtoul(*p
, &e
, 10);
408 if ((unsigned long) (int) value
!= value
)
412 repeat
= strtoul(e
+1, &ee
, 10);
417 if ((unsigned long) (int) repeat
!= repeat
)
425 if (*e
!= 0 && *e
!= ' ' && *e
!= ',' && *e
!= '-' && *e
!= ':')
428 cc
= new0(CalendarComponent
, 1);
441 return prepend_component(p
, c
);
447 static int parse_chain(const char **p
, CalendarComponent
**c
) {
449 CalendarComponent
*cc
= NULL
;
463 r
= prepend_component(&t
, &cc
);
474 static int const_chain(int value
, CalendarComponent
**c
) {
475 CalendarComponent
*cc
= NULL
;
479 cc
= new0(CalendarComponent
, 1);
492 static int parse_date(const char **p
, CalendarSpec
*c
) {
495 CalendarComponent
*first
, *second
, *third
;
506 r
= parse_chain(&t
, &first
);
510 /* Already the end? A ':' as separator? In that case this was a time, not a date */
511 if (*t
== 0 || *t
== ':') {
522 r
= parse_chain(&t
, &second
);
528 /* Got two parts, hence it's month and day */
529 if (*t
== ' ' || *t
== 0) {
530 *p
= t
+ strspn(t
, " ");
543 r
= parse_chain(&t
, &third
);
550 /* Got tree parts, hence it is year, month and day */
551 if (*t
== ' ' || *t
== 0) {
552 *p
= t
+ strspn(t
, " ");
565 static int parse_calendar_time(const char **p
, CalendarSpec
*c
) {
566 CalendarComponent
*h
= NULL
, *m
= NULL
, *s
= NULL
;
577 /* If no time is specified at all, but a date of some
578 * kind, then this means 00:00:00 */
579 if (c
->day
|| c
->weekdays_bits
> 0)
585 r
= parse_chain(&t
, &h
);
595 r
= parse_chain(&t
, &m
);
599 /* Already at the end? Then it's hours and minutes, and seconds are 0 */
613 r
= parse_chain(&t
, &s
);
617 /* At the end? Then it's hours, minutes and seconds */
625 r
= const_chain(0, &h
);
629 r
= const_chain(0, &m
);
634 r
= const_chain(0, &s
);
652 int calendar_spec_from_string(const char *p
, CalendarSpec
**spec
) {
663 c
= new0(CalendarSpec
, 1);
667 utc
= endswith_no_case(p
, " UTC");
670 p
= strndupa(p
, utc
- p
);
673 if (strcaseeq(p
, "minutely")) {
674 r
= const_chain(0, &c
->second
);
678 } else if (strcaseeq(p
, "hourly")) {
679 r
= const_chain(0, &c
->minute
);
682 r
= const_chain(0, &c
->second
);
686 } else if (strcaseeq(p
, "daily")) {
687 r
= const_chain(0, &c
->hour
);
690 r
= const_chain(0, &c
->minute
);
693 r
= const_chain(0, &c
->second
);
697 } else if (strcaseeq(p
, "monthly")) {
698 r
= const_chain(1, &c
->day
);
701 r
= const_chain(0, &c
->hour
);
704 r
= const_chain(0, &c
->minute
);
707 r
= const_chain(0, &c
->second
);
711 } else if (strcaseeq(p
, "annually") ||
712 strcaseeq(p
, "yearly") ||
713 strcaseeq(p
, "anually") /* backwards compatibility */ ) {
715 r
= const_chain(1, &c
->month
);
718 r
= const_chain(1, &c
->day
);
721 r
= const_chain(0, &c
->hour
);
724 r
= const_chain(0, &c
->minute
);
727 r
= const_chain(0, &c
->second
);
731 } else if (strcaseeq(p
, "weekly")) {
733 c
->weekdays_bits
= 1;
735 r
= const_chain(0, &c
->hour
);
738 r
= const_chain(0, &c
->minute
);
741 r
= const_chain(0, &c
->second
);
745 } else if (strcaseeq(p
, "quarterly")) {
747 r
= const_chain(1, &c
->month
);
750 r
= const_chain(4, &c
->month
);
753 r
= const_chain(7, &c
->month
);
756 r
= const_chain(10, &c
->month
);
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
->second
);
772 } else if (strcaseeq(p
, "biannually") ||
773 strcaseeq(p
, "bi-annually") ||
774 strcaseeq(p
, "semiannually") ||
775 strcaseeq(p
, "semi-annually")) {
777 r
= const_chain(1, &c
->month
);
780 r
= const_chain(7, &c
->month
);
783 r
= const_chain(1, &c
->day
);
786 r
= const_chain(0, &c
->hour
);
789 r
= const_chain(0, &c
->minute
);
792 r
= const_chain(0, &c
->second
);
797 r
= parse_weekdays(&p
, c
);
801 r
= parse_date(&p
, c
);
805 r
= parse_calendar_time(&p
, c
);
815 r
= calendar_spec_normalize(c
);
819 if (!calendar_spec_valid(c
)) {
828 calendar_spec_free(c
);
832 static int find_matching_component(const CalendarComponent
*c
, int *val
) {
833 const CalendarComponent
*n
;
846 if (c
->value
>= *val
) {
848 if (!d_set
|| c
->value
< d
) {
853 } else if (c
->repeat
> 0) {
856 k
= c
->value
+ c
->repeat
* ((*val
- c
->value
+ c
->repeat
-1) / c
->repeat
);
858 if (!d_set
|| k
< d
) {
875 static bool tm_out_of_bounds(const struct tm
*tm
, bool utc
) {
881 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
884 /* Did any normalization take place? If so, it was out of bounds before */
886 t
.tm_year
!= tm
->tm_year
||
887 t
.tm_mon
!= tm
->tm_mon
||
888 t
.tm_mday
!= tm
->tm_mday
||
889 t
.tm_hour
!= tm
->tm_hour
||
890 t
.tm_min
!= tm
->tm_min
||
891 t
.tm_sec
!= tm
->tm_sec
;
894 static bool matches_weekday(int weekdays_bits
, const struct tm
*tm
, bool utc
) {
898 if (weekdays_bits
< 0 || weekdays_bits
>= BITS_WEEKDAYS
)
902 if (mktime_or_timegm(&t
, utc
) == (time_t) -1)
905 k
= t
.tm_wday
== 0 ? 6 : t
.tm_wday
- 1;
906 return (weekdays_bits
& (1 << k
));
909 static int find_next(const CalendarSpec
*spec
, struct tm
*tm
) {
919 /* Normalize the current date */
920 mktime_or_timegm(&c
, spec
->utc
);
924 r
= find_matching_component(spec
->year
, &c
.tm_year
);
930 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
932 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
))
936 r
= find_matching_component(spec
->month
, &c
.tm_mon
);
941 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
943 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
947 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
951 r
= find_matching_component(spec
->day
, &c
.tm_mday
);
953 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
954 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
957 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
961 if (!matches_weekday(spec
->weekdays_bits
, &c
, spec
->utc
)) {
963 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
967 r
= find_matching_component(spec
->hour
, &c
.tm_hour
);
969 c
.tm_min
= c
.tm_sec
= 0;
970 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
972 c
.tm_hour
= c
.tm_min
= c
.tm_sec
= 0;
976 r
= find_matching_component(spec
->minute
, &c
.tm_min
);
979 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
981 c
.tm_min
= c
.tm_sec
= 0;
985 r
= find_matching_component(spec
->second
, &c
.tm_sec
);
986 if (r
< 0 || tm_out_of_bounds(&c
, spec
->utc
)) {
998 int calendar_spec_next_usec(const CalendarSpec
*spec
, usec_t usec
, usec_t
*next
) {
1006 t
= (time_t) (usec
/ USEC_PER_SEC
) + 1;
1007 assert_se(localtime_or_gmtime_r(&t
, &tm
, spec
->utc
));
1009 r
= find_next(spec
, &tm
);
1013 t
= mktime_or_timegm(&tm
, spec
->utc
);
1014 if (t
== (time_t) -1)
1017 *next
= (usec_t
) t
* USEC_PER_SEC
;