3434
3535/* Longest valid date/time range is 1970..2199 */
3636#define MAX_RANGE_LEN 230
37+ #define MIN_YEAR 1970
38+ #define MAX_YEAR 2199
3739#define BITS_WEEKDAYS 127
3840
3941static void free_chain (CalendarComponent * c ) {
@@ -135,6 +137,9 @@ int calendar_spec_normalize(CalendarSpec *c) {
135137 if (c -> weekdays_bits <= 0 || c -> weekdays_bits >= BITS_WEEKDAYS )
136138 c -> weekdays_bits = -1 ;
137139
140+ if (c -> end_of_month && !c -> day )
141+ c -> end_of_month = false;
142+
138143 fix_year (c -> year );
139144
140145 sort_chain (& c -> year );
@@ -147,18 +152,27 @@ int calendar_spec_normalize(CalendarSpec *c) {
147152 return 0 ;
148153}
149154
150- _pure_ static bool chain_valid (CalendarComponent * c , int from , int to ) {
155+ _pure_ static bool chain_valid (CalendarComponent * c , int from , int to , bool eom ) {
151156 if (!c )
152157 return true;
153158
154159 if (c -> value < from || c -> value > to )
155160 return false;
156161
157- if (c -> value + c -> repeat > to )
162+ /*
163+ * c->repeat must be short enough so at least one repetition may
164+ * occur before the end of the interval. For dates scheduled
165+ * relative to the end of the month (eom), c->value corresponds
166+ * to the Nth last day of the month.
167+ */
168+ if (eom && c -> value - c -> repeat < from )
169+ return false;
170+
171+ if (!eom && c -> value + c -> repeat > to )
158172 return false;
159173
160174 if (c -> next )
161- return chain_valid (c -> next , from , to );
175+ return chain_valid (c -> next , from , to , eom );
162176
163177 return true;
164178}
@@ -169,22 +183,22 @@ _pure_ bool calendar_spec_valid(CalendarSpec *c) {
169183 if (c -> weekdays_bits > BITS_WEEKDAYS )
170184 return false;
171185
172- if (!chain_valid (c -> year , 1970 , 2199 ))
186+ if (!chain_valid (c -> year , MIN_YEAR , MAX_YEAR , false ))
173187 return false;
174188
175- if (!chain_valid (c -> month , 1 , 12 ))
189+ if (!chain_valid (c -> month , 1 , 12 , false ))
176190 return false;
177191
178- if (!chain_valid (c -> day , 1 , 31 ))
192+ if (!chain_valid (c -> day , 1 , 31 , c -> end_of_month ))
179193 return false;
180194
181- if (!chain_valid (c -> hour , 0 , 23 ))
195+ if (!chain_valid (c -> hour , 0 , 23 , false ))
182196 return false;
183197
184- if (!chain_valid (c -> minute , 0 , 59 ))
198+ if (!chain_valid (c -> minute , 0 , 59 , false ))
185199 return false;
186200
187- if (!chain_valid (c -> microsecond , 0 , 60 * USEC_PER_SEC - 1 ))
201+ if (!chain_valid (c -> microsecond , 0 , 60 * USEC_PER_SEC - 1 , false ))
188202 return false;
189203
190204 return true;
@@ -291,7 +305,7 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
291305 format_chain (f , 4 , c -> year , false);
292306 fputc ('-' , f );
293307 format_chain (f , 2 , c -> month , false);
294- fputc ('-' , f );
308+ fputc (c -> end_of_month ? '~' : '-' , f );
295309 format_chain (f , 2 , c -> day , false);
296310 fputc (' ' , f );
297311 format_chain (f , 2 , c -> hour , false);
@@ -540,7 +554,7 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
540554 }
541555 }
542556
543- if (* e != 0 && * e != ' ' && * e != ',' && * e != '-' && * e != ':' )
557+ if (* e != 0 && * e != ' ' && * e != ',' && * e != '-' && * e != '~' && * e != ' :' )
544558 return - EINVAL ;
545559
546560 cc = new0 (CalendarComponent , 1 );
@@ -620,7 +634,9 @@ static int parse_date(const char **p, CalendarSpec *c) {
620634 return 0 ;
621635 }
622636
623- if (* t != '-' ) {
637+ if (* t == '~' )
638+ c -> end_of_month = true;
639+ else if (* t != '-' ) {
624640 free_chain (first );
625641 return - EINVAL ;
626642 }
@@ -638,9 +654,12 @@ static int parse_date(const char **p, CalendarSpec *c) {
638654 c -> month = first ;
639655 c -> day = second ;
640656 return 0 ;
641- }
657+ } else if (c -> end_of_month )
658+ return - EINVAL ;
642659
643- if (* t != '-' ) {
660+ if (* t == '~' )
661+ c -> end_of_month = true;
662+ else if (* t != '-' ) {
644663 free_chain (first );
645664 free_chain (second );
646665 return - EINVAL ;
@@ -654,7 +673,7 @@ static int parse_date(const char **p, CalendarSpec *c) {
654673 return r ;
655674 }
656675
657- /* Got tree parts, hence it is year, month and day */
676+ /* Got three parts, hence it is year, month and day */
658677 if (* t == ' ' || * t == 0 ) {
659678 * p = t + strspn (t , " " );
660679 c -> year = first ;
@@ -965,9 +984,11 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
965984 return r ;
966985}
967986
968- static int find_matching_component (const CalendarComponent * c , int * val ) {
969- const CalendarComponent * n ;
970- int d = -1 ;
987+ static int find_matching_component (const CalendarSpec * spec , const CalendarComponent * c ,
988+ struct tm * tm , int * val ) {
989+ const CalendarComponent * n , * p = c ;
990+ struct tm t ;
991+ int v , d = -1 ;
971992 bool d_set = false;
972993 int r ;
973994
@@ -979,17 +1000,30 @@ static int find_matching_component(const CalendarComponent *c, int *val) {
9791000 while (c ) {
9801001 n = c -> next ;
9811002
982- if (c -> value >= * val ) {
1003+ if (spec -> end_of_month && p == spec -> day ) {
1004+ t = * tm ;
1005+ t .tm_mon ++ ;
1006+ t .tm_mday = 1 - c -> value ;
1007+
1008+ if (mktime_or_timegm (& t , spec -> utc ) == (time_t ) - 1 ||
1009+ t .tm_mon != tm -> tm_mon )
1010+ v = -1 ;
1011+ else
1012+ v = t .tm_mday ;
1013+ } else
1014+ v = c -> value ;
1015+
1016+ if (v >= * val ) {
9831017
984- if (!d_set || c -> value < d ) {
985- d = c -> value ;
1018+ if (!d_set || v < d ) {
1019+ d = v ;
9861020 d_set = true;
9871021 }
9881022
9891023 } else if (c -> repeat > 0 ) {
9901024 int k ;
9911025
992- k = c -> value + c -> repeat * ((* val - c -> value + c -> repeat - 1 ) / c -> repeat );
1026+ k = v + c -> repeat * ((* val - v + c -> repeat - 1 ) / c -> repeat );
9931027
9941028 if (!d_set || k < d ) {
9951029 d = k ;
@@ -1017,6 +1051,14 @@ static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
10171051 if (mktime_or_timegm (& t , utc ) == (time_t ) - 1 )
10181052 return true;
10191053
1054+ /*
1055+ * Set an upper bound on the year so impossible dates like "*-02-31"
1056+ * don't cause find_next() to loop forever. tm_year contains years
1057+ * since 1900, so adjust it accordingly.
1058+ */
1059+ if (tm -> tm_year + 1900 > MAX_YEAR )
1060+ return true;
1061+
10201062 /* Did any normalization take place? If so, it was out of bounds before */
10211063 return
10221064 t .tm_year != tm -> tm_year ||
@@ -1059,7 +1101,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
10591101 c .tm_isdst = spec -> dst ;
10601102
10611103 c .tm_year += 1900 ;
1062- r = find_matching_component (spec -> year , & c .tm_year );
1104+ r = find_matching_component (spec , spec -> year , & c , & c .tm_year );
10631105 c .tm_year -= 1900 ;
10641106
10651107 if (r > 0 ) {
@@ -1073,7 +1115,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
10731115 return - ENOENT ;
10741116
10751117 c .tm_mon += 1 ;
1076- r = find_matching_component (spec -> month , & c .tm_mon );
1118+ r = find_matching_component (spec , spec -> month , & c , & c .tm_mon );
10771119 c .tm_mon -= 1 ;
10781120
10791121 if (r > 0 ) {
@@ -1088,7 +1130,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
10881130 continue ;
10891131 }
10901132
1091- r = find_matching_component (spec -> day , & c .tm_mday );
1133+ r = find_matching_component (spec , spec -> day , & c , & c .tm_mday );
10921134 if (r > 0 )
10931135 c .tm_hour = c .tm_min = c .tm_sec = tm_usec = 0 ;
10941136 if (r < 0 || tm_out_of_bounds (& c , spec -> utc )) {
@@ -1104,7 +1146,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
11041146 continue ;
11051147 }
11061148
1107- r = find_matching_component (spec -> hour , & c .tm_hour );
1149+ r = find_matching_component (spec , spec -> hour , & c , & c .tm_hour );
11081150 if (r > 0 )
11091151 c .tm_min = c .tm_sec = tm_usec = 0 ;
11101152 if (r < 0 || tm_out_of_bounds (& c , spec -> utc )) {
@@ -1113,7 +1155,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
11131155 continue ;
11141156 }
11151157
1116- r = find_matching_component (spec -> minute , & c .tm_min );
1158+ r = find_matching_component (spec , spec -> minute , & c , & c .tm_min );
11171159 if (r > 0 )
11181160 c .tm_sec = tm_usec = 0 ;
11191161 if (r < 0 || tm_out_of_bounds (& c , spec -> utc )) {
@@ -1123,7 +1165,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
11231165 }
11241166
11251167 c .tm_sec = c .tm_sec * USEC_PER_SEC + tm_usec ;
1126- r = find_matching_component (spec -> microsecond , & c .tm_sec );
1168+ r = find_matching_component (spec , spec -> microsecond , & c , & c .tm_sec );
11271169 tm_usec = c .tm_sec % USEC_PER_SEC ;
11281170 c .tm_sec /= USEC_PER_SEC ;
11291171
0 commit comments