author | Olivier Brunel
<jjk@jjacky.com> 2016-04-06 17:22:50 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2016-04-30 12:30:10 UTC |
parent | 93edd2a136ed10094f4b73445e3fd3be4cfc466d |
doc/slicd-parser.pod | +3 | -0 |
doc/slicd-sched.pod | +17 | -0 |
src/include/slicd/fields.h | +3 | -1 |
src/include/slicd/job.h | +5 | -0 |
src/libslicd/slicd_add_job_from_cronline.c | +6 | -0 |
src/libslicd/slicd_job.c | +17 | -0 |
src/libslicd/slicd_job_next_run.c | +186 | -24 |
diff --git a/doc/slicd-parser.pod b/doc/slicd-parser.pod index 9c84f53..53b3064 100644 --- a/doc/slicd-parser.pod +++ b/doc/slicd-parser.pod @@ -81,6 +81,9 @@ The time-and-date fields are, in order: month 1-12 (or names, see below) day of the week 0-6 (0=Sunday, 1=Monday, etc; or names, see below) +The hour field can have its value prefixed with an exclamation point (!) to +enable the special DST mode for the job; See B<slicd-sched>(1) for more. + Range of values are allowed, separating two values with a hyphen (-). The specified range is inclusive, and must always have the lower value first. diff --git a/doc/slicd-sched.pod b/doc/slicd-sched.pod index 48bd3f7..95cf2f7 100644 --- a/doc/slicd-sched.pod +++ b/doc/slicd-sched.pod @@ -72,6 +72,23 @@ to run "missed" jobs either. For example our job set to run every hour at minute 30, when the clock goes from 01:59 (DST off) to 03:00 (DST on), would simply run at 01:30 (DST off) then an hour later at 03:30 (DST on). +=head2 Special DST mode + +A special DST handling mode can be activated on a per-job basis (see +B<slicd-parser>(1)), in which case time changes due to DST are handled +differently than described above. + +When time jumps backwards, jobs will only run during the "first pass" - or when +DST was activated. When the time "repeats" but with DST deactivated, they will +not run. So using the same example, our job would run at 02:30 (DST on), and +then only two hours later, at 03:30 (DST off). + +When time jumps forward, jobs that would have run at least once during the +"missed" interval will run at the first minute of DST. Again, with our usual +example it would have the job run at 01:30 (DST off), then at 03:00 (DST on) - +because of a "missed run" at the non-existing time of 02:30 - and back as usual +at 03:30 (DST off). + =head1 SIGNALS The following signals can be used : diff --git a/src/include/slicd/fields.h b/src/include/slicd/fields.h index 71b3676..06c18e2 100644 --- a/src/include/slicd/fields.h +++ b/src/include/slicd/fields.h @@ -30,8 +30,9 @@ * 12 months (0-11) * 7 days of week (0-6; 0=sunday, 1=monday, etc) * 1 days combo: set when both days & dow are set, i.e. the Nth dow + * 1 special DST: to enable special handling for DST changes * - * 135 bits total; hence an array of 17 char-s (leaving 1 bit unused) + * 136 bits total; hence an array of 17 char-s * * Both DAYS & DOW set means that DAYS is limited to 0-6 which stands for the * 1st, 2nd, ... till the 5th & then last DOW of the month. @@ -42,6 +43,7 @@ #define _SLICD_BITS_OFFSET_MONTHS _SLICD_BITS_OFFSET_DAYS + 31 #define _SLICD_BITS_OFFSET_DOW _SLICD_BITS_OFFSET_MONTHS + 12 #define _SLICD_BIT_DAYS_COMBO _SLICD_BITS_OFFSET_DOW + 7 +#define _SLICD_BIT_DST_SPECIAL _SLICD_BIT_DAYS_COMBO + 1 static struct { diff --git a/src/include/slicd/job.h b/src/include/slicd/job.h index c15fed5..1984cf4 100644 --- a/src/include/slicd/job.h +++ b/src/include/slicd/job.h @@ -51,6 +51,11 @@ extern int slicd_job_has_days_combo (slicd_job_t *job); extern int slicd_job_set_days_combo (slicd_job_t *job, int what); +extern int slicd_job_has_dst_special(slicd_job_t *job); + +extern int slicd_job_set_dst_special(slicd_job_t *job, + int what); + extern int slicd_job_clearset (slicd_job_t *job, slicd_field_t field, int from, diff --git a/src/libslicd/slicd_add_job_from_cronline.c b/src/libslicd/slicd_add_job_from_cronline.c index 4e61cd3..e2c7d66 100644 --- a/src/libslicd/slicd_add_job_from_cronline.c +++ b/src/libslicd/slicd_add_job_from_cronline.c @@ -86,6 +86,12 @@ parse_interval (slicd_job_t *job, slicd_field_t field, const char **s) int max = min + _slicd_fields[field].max; int from, to, step; + if (field == SLICD_HOURS && **s == '!') + { + slicd_job_set_dst_special (job, 1); + ++*s; + } + again: if (**s == '*') { diff --git a/src/libslicd/slicd_job.c b/src/libslicd/slicd_job.c index 6d10685..380bd27 100644 --- a/src/libslicd/slicd_job.c +++ b/src/libslicd/slicd_job.c @@ -64,6 +64,23 @@ slicd_job_set_days_combo (slicd_job_t *job, return 0; } +int +slicd_job_has_dst_special (slicd_job_t *job) +{ + assert (job != NULL); + return bitarray_isset (job->bits, _SLICD_BIT_DST_SPECIAL); +} + +int +slicd_job_set_dst_special (slicd_job_t *job, + int what) +{ + assert (job != NULL); + + bitarray_clearsetn (job->bits, _SLICD_BIT_DST_SPECIAL, 1, what); + return 0; +} + int slicd_job_clearset (slicd_job_t *job, slicd_field_t field, diff --git a/src/libslicd/slicd_job_next_run.c b/src/libslicd/slicd_job_next_run.c index 66421b3..9c1031a 100644 --- a/src/libslicd/slicd_job_next_run.c +++ b/src/libslicd/slicd_job_next_run.c @@ -74,12 +74,127 @@ first_minute_of_dst (time_t time, struct tm *time_tm) } } +static int +days_combo_match (slicd_job_t *job, struct tm *time) +{ + int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + struct tm first; + int i; + + days[1] = is_leap (time->tm_year) ? 29 : 28; + + byte_copy (&first, sizeof (struct tm), time); + first.tm_mday = 1; + /* get tm_wday */ + if (mktime (&first) == (time_t) -1) + return -1; + + /* let's get first.tm_mday to be the first next->tm_wday of the month */ + first.tm_mday = time->tm_wday; + if (first.tm_mday < first.tm_wday) + first.tm_mday += 7; + first.tm_mday += 1 - first.tm_wday; + + for (i = 1; i < 6; ++i, first.tm_mday += 7) + if (first.tm_mday == time->tm_mday) + { + if (slicd_job_has (job, SLICD_DAYS, i) + || (first.tm_mday + 7 > days[time->tm_mon] + && slicd_job_has (job, SLICD_DAYS, 6))) + return 1; + else + return 0; + } + + /* silence warning -- the "if" in the "for" loop above always matches */ + return -1; +} + +static int +would_have_ran (slicd_job_t *job, struct tm *first) +{ + int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + struct tm tm; + int n; + + memcpy (&tm, first, sizeof (struct tm)); + + --tm.tm_min; + if (mktime (&tm) == (time_t) -1) + return -1; + + days[1] = is_leap (tm.tm_year) ? 29 : 28; + if (++tm.tm_min == 60) + { + tm.tm_min = 0; + if (++tm.tm_hour == 24) + { + tm.tm_hour = 0; + if (++tm.tm_mday > days[tm.tm_mon]) + { + tm.tm_mday = 1; + if (++tm.tm_mon > 11) + { + tm.tm_mon = 0; + ++tm.tm_year; + days[1] = is_leap (tm.tm_year) ? 29 : 28; + } + } + } + } + + n = slicd_job_first (job, SLICD_MONTHS, tm.tm_mon + 1, 12, 1); + if (n > tm.tm_mon + 1) + return 0; + + if (!slicd_job_has (job, SLICD_DOW, tm.tm_wday)) + return 0; + + if (slicd_job_has_days_combo (job)) + { + n = days_combo_match (job, first); + if (n <= 0) + return n; + } + else + { + n = slicd_job_first (job, SLICD_DAYS, tm.tm_mday, days[tm.tm_mon], 1); + if (n > tm.tm_mday) + return 0; + } + +again: + n = slicd_job_first (job, SLICD_HOURS, tm.tm_hour, 23, 1); + if (n > first->tm_hour) + return 0; + + if (n == first->tm_hour) + { + n = slicd_job_first (job, SLICD_MINUTES, 0, first->tm_min, 1); + if (n >= first->tm_min) + return 0; + } + else + { + n = slicd_job_first (job, SLICD_MINUTES, tm.tm_min, 59, 1); + if (n > 59) + { + if (++tm.tm_hour > 23) + return 0; + tm.tm_min = 0; + goto again; + } + } + + return 1; +} + time_t slicd_job_next_run (slicd_job_t *job, struct tm *next) { int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; time_t time; - int dst = -1; + int dst = -2; int n; again: @@ -87,8 +202,31 @@ again: time = mktime (next); if (time == (time_t) -1) return time; - else if (dst == -1) + else if (dst < 0) + { + /* DST special: if DST is on for the reference time, check if the minute + * before was off, in which case we're right after the "skipped period" + * and need to see if the job would have been ran then, to run it now + * instead ("catching up") */ + if (dst == -2 && slicd_job_has_dst_special (job) && next->tm_isdst == 1) + { + time_t t = time - 60; + struct tm *tm; + + tm = localtime (&t); + if (!tm) + return (time_t) -1; + if (tm->tm_isdst == 0) + { + n = would_have_ran (job, next); + if (n < 0) + return (time_t) -1; + else if (n) + return time; + } + } dst = next->tm_isdst; + } else if (next->tm_isdst != dst) { /* we changed DST state, set next to the first minute with this new DST @@ -100,6 +238,17 @@ again: if (time == (time_t) -1) return time; dst = next->tm_isdst; + + /* DST special: if DST is on, check if it would have ran during the + * "skipped period" and if so, match this first minute of new DST */ + if (dst == 1 && slicd_job_has_dst_special (job)) + { + n = would_have_ran (job, next); + if (n < 0) + return (time_t) -1; + else if (n) + return time; + } } days[1] = is_leap (next->tm_year) ? 29 : 28; @@ -190,31 +339,44 @@ bump_day: if (slicd_job_has_days_combo (job)) { - struct tm first; - int i; - - byte_copy (&first, sizeof (first), next); - first.tm_mday = 1; - /* get tm_wday */ - if (mktime (&first) == (time_t) -1) + n = days_combo_match (job, next); + if (n < 0) return (time_t) -1; + else if (!n) + goto bump_day; + } - /* let's get first.tm_mday to be the first next->tm_wday of the month */ - first.tm_mday = next->tm_wday; - if (first.tm_mday < first.tm_wday) - first.tm_mday += 7; - first.tm_mday += 1 - first.tm_wday; + /* DST special: if DST is off, get the first minute since it's been off, and + * make sure this job isn't in the "repeating time period" and if so, start + * again after said period */ + if (slicd_job_has_dst_special (job) && next->tm_isdst == 0) + { + time_t t; + struct tm tm; - for (i = 1; i < 6; ++i, first.tm_mday += 7) - if (first.tm_mday == next->tm_mday) - { - if (slicd_job_has (job, SLICD_DAYS, i) - || (first.tm_mday + 7 > days[next->tm_mon] - && slicd_job_has (job, SLICD_DAYS, 6))) - break; - else - goto bump_day; - } + memcpy (&tm, next, sizeof (struct tm)); + if (first_minute_of_dst (time, &tm) == (time_t) -1) + return (time_t) -1; + /* move to last minute w/ DST on */ + --tm.tm_min; + if (mktime (&tm) == (time_t) -1) + return (time_t) -1; + /* and get to the same minute but w/ DST off, i.e. last minute to be + * "repeated" */ + tm.tm_isdst = 0; + t = mktime (&tm); + if (t == (time_t) -1) + return t; + + if (time <= t) + { + /* update next.. */ + memcpy (next, &tm, sizeof (struct tm)); + /* ..to the first minute after the "repeating period" */ + ++next->tm_min; + dst = 0; + goto again; + } } return time;