forked from staticlibs/ccronexpr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ccronexpr.c
1791 lines (1630 loc) · 60.6 KB
/
ccronexpr.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2015, alex at staticlibs.net
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* File: CronExprParser.cpp
* Author: alex
*
* Created on February 24, 2015, 9:35 AM
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include "ccronexpr.h"
#define CRON_MAX_SECONDS 60
#define CRON_MAX_MINUTES 60
#define CRON_MAX_HOURS 24
#define CRON_MAX_DAYS_OF_WEEK 8
#define CRON_MAX_DAYS_OF_MONTH 32
#define CRON_MAX_MONTHS 13
// Bit 0...11 = Month
// Bit 13 ... 15 are used for W and L flags
#define CRON_L_DOW_BIT 13
#define CRON_L_DOM_BIT 14
#define CRON_W_DOM_BIT 15
#define W_DOM_FLAG (1<<0)
#define L_DOM_FLAG (1<<1)
#define L_DOW_FLAG (1<<2)
typedef enum {
CRON_CF_SECOND = 0,
CRON_CF_MINUTE,
CRON_CF_HOUR_OF_DAY,
CRON_CF_DAY_OF_WEEK,
CRON_CF_DAY_OF_MONTH,
CRON_CF_MONTH,
CRON_CF_YEAR
} cron_cf;
typedef enum {
CRON_FIELD_SECOND = 0,
CRON_FIELD_MINUTE,
CRON_FIELD_HOUR,
CRON_FIELD_DAY_OF_MONTH,
CRON_FIELD_MONTH,
CRON_FIELD_DAY_OF_WEEK
} cron_fieldpos;
#define CRON_CF_ARR_LEN 7 /* or (CRON_CF_YEAR-CRON_CF_SECOND+1) */
#define CRON_INVALID_INSTANT ((time_t) -1)
static const char *DAYS_ARR[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
#define CRON_DAYS_ARR_LEN 7
static const char *MONTHS_ARR[] = {"FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV",
"DEC"};
#define CRON_MONTHS_ARR_LEN 13
#define CRON_MAX_STR_LEN_TO_SPLIT 256
#define CRON_MAX_NUM_TO_SRING 1000000000
/* computes number of digits in decimal number */
#define CRON_NUM_OF_DIGITS(num) (abs(num) < 10 ? 1 : \
(abs(num) < 100 ? 2 : \
(abs(num) < 1000 ? 3 : \
(abs(num) < 10000 ? 4 : \
(abs(num) < 100000 ? 5 : \
(abs(num) < 1000000 ? 6 : \
(abs(num) < 10000000 ? 7 : \
(abs(num) < 100000000 ? 8 : \
(abs(num) < 1000000000 ? 9 : 10)))))))))
#ifndef _WIN32
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime_r(const time_t *timep, struct tm *result);
#endif
/* Defining 'cron_mktime' to use UTC (default) or local time */
#ifndef CRON_USE_LOCAL_TIME
/* http://stackoverflow.com/a/22557778 */
#ifdef _WIN32
time_t cron_mktime(struct tm* tm) {
return _mkgmtime(tm);
}
#else /* _WIN32 */
#ifndef ANDROID
/* can be hidden in time.h */
time_t timegm(struct tm *__tp);
#endif /* ANDROID */
time_t cron_mktime(struct tm *tm) {
#ifndef ANDROID
return timegm(tm);
#else /* ANDROID */
/* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */
static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1));
static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1));
time64_t result = timegm64(tm);
if (result < kTimeMin || result > kTimeMax) return -1;
return result;
#endif /* ANDROID */
}
#endif /* _WIN32 */
#ifndef CRON_TEST_MALLOC
#define cronFree(x) free(x)
#define cronMalloc(x) malloc(x)
#else
void *cronMalloc(size_t n);
void cronFree(void *p);
#endif
struct tm *cron_time(time_t *date, struct tm *out) {
#ifdef __MINGW32__
(void)(out); /* To avoid unused warning */
return gmtime(date);
#else /* __MINGW32__ */
#ifdef _WIN32
return gmtime_s(date, out);
#else /* _WIN32 */
return gmtime_r(date, out);
#endif /* _WIN32 */
#endif /* __MINGW32__ */
}
#else /* CRON_USE_LOCAL_TIME */
time_t cron_mktime(struct tm* tm) {
return mktime(tm);
}
struct tm* cron_time(time_t* date, struct tm* out) {
#ifdef _WIN32
errno_t err = localtime_s(out, date);
return 0 == err ? out : NULL;
#else /* _WIN32 */
return localtime_r(date, out);
#endif /* _WIN32 */
}
#endif /* CRON_USE_LOCAL_TIME */
static void free_splitted(char **splitted, size_t len) {
size_t i;
if (!splitted) return;
for (i = 0; i < len; i++) {
if (splitted[i]) {
cronFree(splitted[i]);
}
}
cronFree(splitted);
}
static char *strdupl(const char *str, size_t len) {
if (!str) return NULL;
char *res = (char *) cronMalloc(len + 1);
if (!res) return NULL;
memset(res, 0, len + 1);
memcpy(res, str, len);
return res;
}
/** Return next set bit position of bits starting at from_index as integer, set notfound to 1 if none was found.
* Interval: [from_index:max[
*/
static unsigned int next_set_bit(const uint8_t *bits, unsigned int max, unsigned int from_index, int *notfound) {
unsigned int i;
if (!bits) {
*notfound = 1;
return 0;
}
for (i = from_index; i < max; i++) {
if (cron_getBit(bits, i)) return i;
}
*notfound = 1;
return 0;
}
/// Clear bit in reset byte at position *fi* (*arr* is usually initialized with -1)
/// Example: push_to_fields_arr(array, CRON_CF_MINUTE)
/// - if used on a completely "new" array, would clear bit 2 (CRON_CF_MINUTE) and return
static void push_to_fields_arr(uint8_t *arr, cron_cf fi) {
if (!arr) {
return;
}
if (fi >= CRON_CF_ARR_LEN) {
return;
}
*arr &= ~(1 << fi); // Unset bit at position fi
}
static int add_to_field(struct tm *calendar, cron_cf field, int val) {
if (!calendar) {
return 1;
}
switch (field) {
case CRON_CF_SECOND:
calendar->tm_sec = calendar->tm_sec + val;
break;
case CRON_CF_MINUTE:
calendar->tm_min = calendar->tm_min + val;
break;
case CRON_CF_HOUR_OF_DAY:
calendar->tm_hour = calendar->tm_hour + val;
break;
case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */
case CRON_CF_DAY_OF_MONTH:
calendar->tm_mday = calendar->tm_mday + val;
break;
case CRON_CF_MONTH:
calendar->tm_mon = calendar->tm_mon + val;
break;
case CRON_CF_YEAR:
calendar->tm_year = calendar->tm_year + val;
break;
default:
return 1; /* unknown field */
}
time_t res = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == res) {
return 1;
}
return 0;
}
/**
* Reset the calendar field (at position field) to 0/1.
*/
static int reset(struct tm *calendar, cron_cf field) {
if (!calendar) {
return 1;
}
switch (field) {
case CRON_CF_SECOND:
calendar->tm_sec = 0;
break;
case CRON_CF_MINUTE:
calendar->tm_min = 0;
break;
case CRON_CF_HOUR_OF_DAY:
calendar->tm_hour = 0;
break;
case CRON_CF_DAY_OF_WEEK:
calendar->tm_wday = 0;
break;
case CRON_CF_DAY_OF_MONTH:
calendar->tm_mday = 1;
break;
case CRON_CF_MONTH:
calendar->tm_mon = 0;
break;
case CRON_CF_YEAR:
calendar->tm_year = 0;
break;
default:
return 1; /* unknown field */
}
time_t res = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == res) {
return 1;
}
return 0;
}
/**
* Reset reset_fields in calendar to 0/1 (if day of month), provided their field bit is not set.
*
* @param calendar Pointer to a struct tm; its reset_fields will be reset to 0/1, if the corresponding field bit is not set.
* @param reset_fields BitArray/BitFlags of CRON_CF_ARR_LEN; each bit is either 1 (no reset) or 0, so it will reset its corresponding field in calendar.
* @return 1 if a reset fails, 0 if successful.
*/
static int reset_all(struct tm *calendar, uint8_t *reset_fields) {
int i;
int res = 0;
if (!calendar || !reset_fields) {
return 1;
}
for (i = 0; i < CRON_CF_ARR_LEN; i++) {
if (!(*reset_fields & (1 << i))) { // reset when value was already considered "right", so bit was cleared
res = reset(calendar, i);
if (0 != res) return res;
*reset_fields |= 1 << i; // reset field bit here, so it will not be reset on next iteration if necessary
}
}
return 0;
}
static int set_field(struct tm *calendar, cron_cf field, unsigned int val) {
if (!calendar) {
return 1;
}
switch (field) {
case CRON_CF_SECOND:
calendar->tm_sec = (int) val;
break;
case CRON_CF_MINUTE:
calendar->tm_min = (int) val;
break;
case CRON_CF_HOUR_OF_DAY:
calendar->tm_hour = (int) val;
break;
case CRON_CF_DAY_OF_WEEK:
calendar->tm_wday = (int) val;
break;
case CRON_CF_DAY_OF_MONTH:
calendar->tm_mday = (int) val;
break;
case CRON_CF_MONTH:
calendar->tm_mon = (int) val;
break;
case CRON_CF_YEAR:
calendar->tm_year = (int) val;
break;
default:
return 1; /* unknown field */
}
time_t res = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == res) {
return 1;
}
return 0;
}
/**
* Search the bits provided for the next set bit after the value provided,
* and reset the calendar.
*
* @param bits The starting address of the searched field in the parsed cron expression.
* @param max Maximum value for field (for expression and calendar). (Bits in expression will only be checked up to this one)
* @param value Original value of field in calendar.
* @param calendar Pointer to calendar structure. Starting with the original date, its fields will be replaced with the next trigger time from seconds to year.
* @param field Integer showing which field is currently searched for, used to set/reset that field in calendar.
* @param nextField Integer showing which field is following the searched field, used to increment that field in calendar, if a roll-over happens.
* @param reset_fields 8 bit field marking which fields in calendar should be reset by [reset_all](#reset_all). 1 bit per field.
* @param res_out Pointer to error code output. (Will be checked by do_next().)
* @return Either next trigger value for or 0 if field could not be set in calendar or lower calendar fields could not be reset. (If failing, *res_out will be set to 1 as well.)
*/
static unsigned int
find_next(const uint8_t *bits, unsigned int max, unsigned int value, struct tm *calendar, cron_cf field,
cron_cf nextField, uint8_t *reset_fields, int *res_out) {
int notfound = 0;
int err = 0;
unsigned int next_value = next_set_bit(bits, max, value, ¬found);
/* roll over if needed */
if (notfound) {
err = add_to_field(calendar, nextField, 1);
if (err) goto return_error;
err = reset(calendar, field);
if (err) goto return_error;
notfound = 0;
next_value = next_set_bit(bits, max, 0, ¬found);
// Still no set bit in range from 0 to max found? Range must be empty, return error
if (notfound) goto return_error;
}
if (next_value != value) {
err = reset_all(calendar, reset_fields);
if (err) goto return_error;
err = set_field(calendar, field, next_value);
if (err) goto return_error;
}
return next_value;
return_error:
*res_out = 1;
return 0;
}
/** Add all days with L flags to the current month bitarray,
* to allow finding the next fitting day as usual.
*
* The calendar will be re-set to the day and month passed in.
*
* @param calendar struct tm with current month (and day of month);
* will be used to determine the last dom, but reset to the original day.
* @param cron_dom (copied) bits for the cron days of month
* @param cron_dow bits for the cron 'L' days of week; only read for the L flag in DOW, to get which day is used.
* @param lw_flags bitflags for set 'L' and 'W' flags types: Checked to see if 'L' flag in DOW is present.
* @param l_offset bits for cron days of month 'L' flags: One bit for each offset (0-30), only read
* @param res_out output for possible problems finding the 'L' days. Is '0' if no error occurred
*/
static void find_l_days(struct tm *calendar, uint8_t *cron_dom, const uint8_t *cron_dow, const uint8_t *l_offset,
const uint8_t lw_flags, int *res_out) {
int startday = calendar->tm_mday;
int startmonth = calendar->tm_mon;
int startyear = calendar->tm_year;
int no_lflag = 0;
// Set calendar to last day of current month by going to "0th" of next month; get max offset from it
calendar->tm_mday = 0;
calendar->tm_mon += 1;
time_t check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
// TODO: Return more detailed error code?
goto return_err;
}
int lastday = calendar->tm_mday;
// Can either use Last DOM or Last DOW, only one of the day fields can be set to specific values.
// Case 1:
// Find first offset for L DOM flags, from 0 to 30
if (lw_flags & L_DOM_FLAG) {
unsigned int offset = next_set_bit(l_offset, CRON_MAX_DAYS_OF_MONTH, 0, &no_lflag);
while (no_lflag == 0) {
if (offset >= lastday) {
// Allow at least one execution this month: Set the 1st bit
cron_setBit(cron_dom, 1);
} else {
// Set the corresponding bit in cron_dom, removing offset from lastday
cron_setBit(cron_dom, lastday - offset);
}
offset = next_set_bit(l_offset, lastday, offset+1, &no_lflag);
}
goto return_success;
}
// Case 2:
// Find day for L DOW flag, if set
if (lw_flags & L_DOW_FLAG) {
// Clear cur_dom, as otherwise all bits are already set
memset(cron_dom, 0, 4);
// Calendar is already set to the last day of the current month. Go back until the desired weekday is found
int cur_wday = calendar->tm_wday;
unsigned int offset = next_set_bit(cron_dow, CRON_MAX_DAYS_OF_WEEK, 0, &no_lflag);
if (no_lflag) {
// No DOW set?!
goto return_err;
}
unsigned int diff = 0;
if (offset <= cur_wday) {
diff = cur_wday - offset;
} else {
// cur_wday < offset; MON, but offset is FRI
// Add 7 days to signify cur_wday is in the week before the target day
diff = (cur_wday + 7) - offset;
}
// Go back diff days in month; will always stay in month as no month is only 6 days long
cron_setBit(cron_dom, lastday-diff);
}
// Successfully added cron bits; reset calendar and return
return_success:
calendar->tm_mday = startday;
calendar->tm_mon = startmonth;
calendar->tm_year = startyear;
check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
goto return_err;
}
return;
return_err:
calendar->tm_mday = startday;
calendar->tm_mon = startmonth;
calendar->tm_year = startyear;
cron_mktime(calendar);
*res_out = 1;
}
/** Add all days with W flags to the current month bitarray,
* to allow finding the next fitting day as usual.
*
* The calendar will be re-set to the day and month passed in.
*
* @param calendar struct tm with current month (and day of month);
* will be used to determine the last dom, but reset to the original day.
* @param cron_dom (copied) bits for the cron days of month, days will be set here
* @param w_flags bits for the 'W' flags for days of month; only read for calculations
* @param res_out output for possible problems finding the 'W' days. Is '0' if no error occurred
*/
static void find_w_days(struct tm *calendar, uint8_t *cron_dom, const uint8_t *w_flags, int *res_out) {
int startday = calendar->tm_mday;
int startmonth = calendar->tm_mon;
int startyear = calendar->tm_year;
// Get last day of month, boundary for searched W flags, may be needed for 'LW' flag
calendar->tm_mday = 0;
calendar->tm_mon += 1;
time_t check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
goto return_err;
}
int lastday = calendar->tm_mday;
// Reset to startday
calendar->tm_mday = startday;
calendar->tm_mon = startmonth;
calendar->tm_year = startyear;
check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
goto return_err;
}
int no_wflag = 0;
unsigned int next_w = next_set_bit(w_flags, lastday+1, 0, &no_wflag);
while (no_wflag == 0) {
if (next_w == 0) {
// Last weekday of the month (can only happen on first loop)
calendar->tm_mday = lastday;
check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
goto return_err;
}
switch (calendar->tm_wday) {
case 0: // SUN, 2 days back
calendar->tm_mday -= 2;
break;
case 6: // SAT, 1 day back
calendar->tm_mday -=1;
break;
default: // already a valid weekday
break;
}
// Set the bit in cron_dom here, and search next W flag for a day
cron_setBit(cron_dom, calendar->tm_mday);
next_w = next_set_bit(w_flags, lastday+1, 1, &no_wflag);
continue;
}
// Go to next W day
calendar->tm_mday = (int)next_w; // next_w will never be bigger than 31, is safe
check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
// TODO: Return more detailed error code?
goto return_err;
}
// Check if it is a valid weekday
if (calendar->tm_wday > 0 && calendar->tm_wday < 6) {
goto finish_loop;
}
// if not, go to the closest weekday in current month
// First of the month must only further in month (only +)
if (calendar->tm_mday == 1) {
if (calendar->tm_wday == 6) {
// Saturday: 2 days further
calendar->tm_mday += 2;
} else {
// Sunday: 1 day further
calendar->tm_mday++;
}
goto finish_loop;
}
// Other days: Go one day back (SAT), or one day further (SUN), and check that month hasn't rolled over (then back to FRI)
if (calendar->tm_wday == 6) {
calendar->tm_mday--;
goto finish_loop;
}
calendar->tm_mday++;
check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
// TODO: Return more detailed error code?
goto return_err;
}
if (calendar->tm_mon != startmonth) {
// go 3 days back
calendar->tm_mday -= 3;
check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
// TODO: Return more detailed error code?
goto return_err;
}
}
finish_loop:
cron_setBit(cron_dom, calendar->tm_mday);
// Find next flag
next_w = next_set_bit(w_flags, lastday+1, next_w+1, &no_wflag);
}
calendar->tm_mday = startday;
calendar->tm_mon = startmonth;
calendar->tm_year = startyear;
check = cron_mktime(calendar);
if (CRON_INVALID_INSTANT == check) {
goto return_err;
}
return;
return_err:
calendar->tm_mday = startday;
calendar->tm_mon = startmonth;
calendar->tm_year = startyear;
cron_mktime(calendar);
*res_out = 1;
}
static unsigned int
find_next_day(struct tm *calendar, const uint8_t *cron_dom, unsigned int day_of_month, const uint8_t *cron_dow,
unsigned int day_of_week, const uint8_t lw_flags, const uint8_t *l_dom_offset, const uint8_t *l_dow_flags,
const uint8_t *w_flags, uint8_t *reset_fields, int *res_out) {
int err;
unsigned int count = 0;
int max_days = 366; // without flags, advance 1 year may. days until months end with lw_flags; then the L- and W-Flags need to be recalculated
// Copy cron_dom to add days determined by L- and W- flags
uint8_t cur_doms[4];
memcpy(cur_doms, cron_dom, 4);
if (lw_flags) {
// Copy calendar to move day independently searching for L/W-Days; timezone MUST not be touched
// find_{L,W}_days() will not alter the tm struct
struct tm searcher;
memcpy(&searcher, calendar, sizeof searcher);
// Add L days and W days of current month to cur_doms
find_l_days(&searcher, cur_doms, l_dow_flags, l_dom_offset, lw_flags, res_out);
if (*res_out) {
// something went wrong; return 0
return 0;
}
find_w_days(&searcher, cur_doms, w_flags, res_out);
if (*res_out) {
// something went wrong; return 0
return 0;
}
// Get last day of current month: 0th day of next month
searcher.tm_mday = 0;
searcher.tm_mon += 1;
time_t check = cron_mktime(&searcher);
if (CRON_INVALID_INSTANT == check) {
*res_out = 1;
return 0;
}
max_days = (searcher.tm_mday - calendar->tm_mday) + 1; // 1 day more, to allow rolling over into next month
}
// Find next fitting day in month, or reset lower fields and roll over into next month
while ( (!cron_getBit(cur_doms, day_of_month) || !cron_getBit(cron_dow, day_of_week)) &&
count++ < max_days) {
err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1);
if (err) goto return_error;
day_of_month = calendar->tm_mday;
day_of_week = calendar->tm_wday;
reset_all(calendar, reset_fields);
}
return day_of_month;
return_error:
*res_out = 1;
return 0;
}
/**
* Find the next time at which the cron will be triggered.
* If successful, it will replace the start time in *calendar*.
*
* Principle from [cron_next](#cron_next):
* 1. Try to find matching seconds after start, if not found (in find_next()), raise minutes by one, reset seconds to 0 and start again.
* 2. Once matching seconds are found, check if minute is valid for CRON. If so, continue to find matching hours, if not, raise hours by one, reset minutes (and lower fields) to 0, and re-start.
* (Same for hours, day and month until a time fitting a CRON was found, or the next execution date is further than 4 years away.
*
* @param expr The parsed cron expression.
* @param calendar The time after which the next cron trigger should be found. If successful (see return), will be replaced with the next trigger time.
* @param dot Year of the original time. If no trigger is found even 4 years in the future, an error code (-1) is returned.
* @return Error code: 0 on success, other values (e. g. -1) mean failure.
*/
static int do_next(const cron_expr *expr, struct tm *calendar, unsigned int dot) {
int res = 0;
uint8_t reset_fields = 0xFE; // First bit (seconds) should always be unset, because if minutes roll over (and seconds didn't), seconds need to be reset as well
uint8_t second_reset_fields = 0xFF; // Only used for seconds; they shouldn't reset themselves after finding a match
unsigned int second = 0;
unsigned int update_second = 0;
unsigned int minute = 0;
unsigned int update_minute = 0;
unsigned int hour = 0;
unsigned int update_hour = 0;
unsigned int day_of_week = 0;
unsigned int day_of_month = 0;
unsigned int update_day_of_month = 0;
unsigned int month = 0;
unsigned int update_month = 0;
// L flags for DOM and DOW, or W flag for DOM
uint8_t l_flags = 0; // Bit 0: W (day of month), Bit 1: L (day of month), Bit 2: L (day of week)
if (cron_getBit(expr->months, CRON_L_DOM_BIT)) {
l_flags |= L_DOM_FLAG;
}
if (cron_getBit(expr->months, CRON_L_DOW_BIT)) {
l_flags |= L_DOW_FLAG;
}
if (cron_getBit(expr->months, CRON_W_DOM_BIT)) {
l_flags |= W_DOM_FLAG;
}
while (reset_fields) {
if (calendar->tm_year - dot > 5) {
res = -1;
goto return_result;
}
second = calendar->tm_sec;
update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE,
&second_reset_fields,
&res); // Value should not be changed since all bits are already set, so discarding const explicitly
if (0 != res) goto return_result;
if (second == update_second) {
push_to_fields_arr(&reset_fields, CRON_CF_SECOND);
}
minute = calendar->tm_min;
update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE,
CRON_CF_HOUR_OF_DAY, &reset_fields, &res);
if (0 != res) goto return_result;
if (minute == update_minute) { //
push_to_fields_arr(&reset_fields, CRON_CF_MINUTE);
} else {
continue;
}
hour = calendar->tm_hour;
update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK,
&reset_fields, &res);
if (0 != res) goto return_result;
if (hour == update_hour) {
push_to_fields_arr(&reset_fields, CRON_CF_HOUR_OF_DAY);
} else {
continue;
}
day_of_week = calendar->tm_wday;
day_of_month = calendar->tm_mday;
month = calendar->tm_mon;
update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week,
day_of_week, l_flags, expr->l_dom_offset, expr->l_dow_flags, expr->w_flags,
&reset_fields,&res);
if (0 != res) goto return_result;
if (day_of_month == update_day_of_month && month == (unsigned int) calendar->tm_mon) {
push_to_fields_arr(&reset_fields, CRON_CF_DAY_OF_MONTH);
} else {
continue;
}
month = calendar->tm_mon; /*day already adds one if no day in same month is found*/
update_month = find_next(expr->months, CRON_MAX_MONTHS - 1, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR,
&reset_fields,
&res); // max-1 because month bits are only set from 0 to 11
if (0 != res) goto return_result;
if (month != update_month) {
continue;
}
goto return_result;
}
return_result:
return res;
}
static int to_upper(char *str) {
if (!str) return 1;
int i;
for (i = 0; '\0' != str[i]; i++) {
str[i] = (char) toupper(str[i]);
}
return 0;
}
static char *to_string(int num) {
if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL;
char *str = (char *) cronMalloc(CRON_NUM_OF_DIGITS(num) + 1);
if (!str) return NULL;
int res = sprintf(str, "%d", num);
if (res < 0) return NULL;
return str;
}
static char *str_replace(char *orig, const char *rep, const char *with) {
char *result; /* the return string */
char *ins; /* the next insert point */
char *tmp; /* varies */
size_t len_rep; /* length of rep */
size_t len_with; /* length of with */
size_t len_front; /* distance between rep and end of last rep */
int count; /* number of replacements */
if (!orig) return NULL;
if (!rep) rep = "";
if (!with) with = "";
len_rep = strlen(rep);
len_with = strlen(with);
ins = orig;
for (count = 0; NULL != (tmp = strstr(ins, rep)); ++count) {
ins = tmp + len_rep;
}
/* first time through the loop, all the variable are set correctly
from here on,
tmp points to the end of the result string
ins points to the next occurrence of rep in orig
orig points to the remainder of orig after "end of rep"
*/
tmp = result = (char *) cronMalloc(strlen(orig) + (len_with - len_rep) * count + 1);
if (!result) return NULL;
while (count--) {
ins = strstr(orig, rep);
len_front = ins - orig;
tmp = strncpy(tmp, orig, len_front) + len_front;
tmp = strcpy(tmp, with) + len_with;
orig += len_front + len_rep; /* move to next "end of rep" */
}
strcpy(tmp, orig);
return result;
}
static unsigned int parse_uint(const char *str, int *errcode) {
char *endptr;
errno = 0;
long int l = strtol(str, &endptr, 10);
if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) {
*errcode = 1;
return 0;
} else {
*errcode = 0;
return (unsigned int) l;
}
}
static char **split_str(const char *str, char del, size_t *len_out) {
size_t i;
size_t stlen = 0;
size_t len = 0;
int accum = 0;
char *buf = NULL;
char **res = NULL;
size_t bi = 0;
size_t ri = 0;
char *tmp;
if (!str) goto return_error;
for (i = 0; '\0' != str[i]; i++) {
stlen += 1;
if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error;
}
for (i = 0; i < stlen; i++) {
if (del == str[i]) {
if (accum > 0) {
len += 1;
accum = 0;
}
} else if (!isspace(str[i])) {
accum += 1;
}
}
/* tail */
if (accum > 0) {
len += 1;
}
if (0 == len) return NULL;
buf = (char *) cronMalloc(stlen + 1);
if (!buf) goto return_error;
memset(buf, 0, stlen + 1);
res = (char **) cronMalloc(len * sizeof(char *));
if (!res) goto return_error;
for (i = 0; i < stlen; i++) {
if (del == str[i]) {
if (bi > 0) {
tmp = strdupl(buf, bi);
if (!tmp) goto return_error;
res[ri++] = tmp;
memset(buf, 0, stlen + 1);
bi = 0;
}
} else if (!isspace(str[i])) {
buf[bi++] = str[i];
}
}
/* tail */
if (bi > 0) {
tmp = strdupl(buf, bi);
if (!tmp) goto return_error;
res[ri++] = tmp;
}
cronFree(buf);
*len_out = len;
return res;
return_error:
if (buf) {
cronFree(buf);
}
free_splitted(res, len);
*len_out = 0;
return NULL;
}
static char *replace_ordinals(char *value, const char **arr, size_t arr_len) {
size_t i;
char *cur = value;
char *res = NULL;
int first = 1;
for (i = 0; i < arr_len; i++) {
char *strnum = to_string((int) i);
if (!strnum) {
if (!first) {
cronFree(cur);
}
return NULL;
}
res = str_replace(cur, arr[i], strnum);
cronFree(strnum);
if (!first) {
cronFree(cur);
}
if (!res) {
return NULL;
}
cur = res;
if (first) {
first = 0;
}
}
return res;
}
static int has_char(char *str, char ch) {
size_t i;
size_t len = 0;
if (!str) return 0;
len = strlen(str);
for (i = 0; i < len; i++) {
if (str[i] == ch) return 1;
}
return 0;
}
static int hash_seed = 0;
static cron_custom_hash_fn fn = NULL;
void cron_init_hash(int seed) {
hash_seed = seed;
}
void cron_init_custom_hash_fn(cron_custom_hash_fn func) {
fn = func;
}
/**
* Replace H parameter with integer in proper range. If using an iterator field, min/max have to be set to proper values before!
* The input field will always be freed, the returned char* should be used instead.
*
* @param field CRON field which needs a value for its 'H' (in string form)
* @param n Position of the field in the CRON string, from 0 - 5
* @param min Minimum value allowed in field/for replacement
* @param max Maximum value allowed in field/for replacement
* @param hashFn Custom random output function, if provided, will be used instead of rand(). Needs to return an integer and needs to accept the seed and index for deterministic behaviour for each field. Can be NULL.
* @param error Error string in which error descriptions will be stored, if they happen. Just needs to be a const char** pointer. (See usage of get_range)
* @return New char* with replaced H, to be used instead of field.
*/
static char *replace_hashed(char *field, unsigned int n, unsigned int min, unsigned int max, cron_custom_hash_fn hashFn,
const char **error) {
unsigned int i = 0;
unsigned int value;
char *newField = NULL;
// needed when a custom range is detected and removed
char customRemover[8];
char innerString[6];
char *oldField = field;
if (!has_char(field, 'H')) {
*error = "No H to replace in field";
return field;
}
// Generate random value
if (hashFn) {
value = hashFn(hash_seed, n);
} else {
int newSeed = rand();
srand(hash_seed);
while (n >= i++) {
value = rand();
}
srand(newSeed);
}
// ensure value is below max...
value %= max - min;
// and above min