From 7918c19ea2ac405ee54745bca60487e64088a79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radom=C3=ADr=20Pol=C3=A1ch?= Date: Fri, 8 Sep 2023 14:30:01 +0200 Subject: [PATCH] leap seconds handling --- README.md | 4 +++- ccronexpr.c | 20 +++++++++++++++----- ccronexpr_test.c | 7 +++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 102f937..e2c3e12 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ This reference is based on: ``` Field name Mandatory? Allowed values Allowed special characters ---------- ---------- -------------- ------------------------- -Second No 0-59 * / , - +Second No 0-59 * / , - L Minute Yes 0-59 * / , - Hour Yes 0-23 * / , - Day of month Yes 1-31 * / , - L W @@ -77,6 +77,8 @@ The character `L` stands for "last". In the 'Day of week' field, `5L` denotes th - If followed by a negative number in the 'Day of month' field, such as `L-3`, it indicates the third-to-last day of the month. +- If `L` is present in the beginning of 'Second' field, it turns on non standard leap second functionality. Unless timezone specifies leap seconds, it will segfault, because it will not be able to find any leap second! + When using 'L', avoid specifying lists or ranges to prevent ambiguous results. #### `W` diff --git a/ccronexpr.c b/ccronexpr.c index eb58eda..65a76be 100644 --- a/ccronexpr.c +++ b/ccronexpr.c @@ -32,6 +32,7 @@ #include "ccronexpr.h" #define CRON_MAX_SECONDS 60 +#define CRON_MAX_LEAP_SECONDS 2 #define CRON_MAX_MINUTES 60 #define CRON_MAX_HOURS 24 #define CRON_MAX_DAYS_OF_MONTH 32 @@ -595,10 +596,13 @@ static int do_nextprev( } second = calendar->tm_sec; - update_second = find(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); + update_second = find(expr->seconds, CRON_MAX_SECONDS+CRON_MAX_LEAP_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); if (0 != res) goto return_result; if (second == update_second) { push_to_fields_arr(resets, CRON_CF_SECOND); + } else { + res = do_(expr, calendar, dot); + if (0 != res) goto return_result; } minute = calendar->tm_min; @@ -1146,6 +1150,7 @@ void cron_parse_expr(const char* expression, cron_expr* target, const char** err const char* err_local; size_t len = 0; char** fields = NULL; + char* field; int ret; int pos = 0; if (!error) { @@ -1190,14 +1195,19 @@ void cron_parse_expr(const char* expression, cron_expr* target, const char** err } memset(target, 0, sizeof(*target)); if (len > 5) { - set_number_hits(fields[pos++], target->seconds, 0, 60, error); + if (fields[pos][0] == 'L') { + field = fields[pos++]; + set_number_hits(field+1, target->seconds, 0, CRON_MAX_SECONDS+CRON_MAX_LEAP_SECONDS, error); + } else { + set_number_hits(fields[pos++], target->seconds, 0, CRON_MAX_SECONDS, error); + } if (*error) goto return_res; } else { - set_number_hits("0", target->seconds, 0, 60, error); + set_number_hits("0", target->seconds, 0, CRON_MAX_SECONDS, error); } - set_number_hits(fields[pos++], target->minutes, 0, 60, error); + set_number_hits(fields[pos++], target->minutes, 0, CRON_MAX_MINUTES, error); if (*error) goto return_res; - set_number_hits(fields[pos++], target->hours, 0, 24, error); + set_number_hits(fields[pos++], target->hours, 0, CRON_MAX_HOURS, error); if (*error) goto return_res; ret = set_days_of_month(fields[pos++], target->days_of_month, target->days_of_week, target->day_in_month, target->flags, error); if (*error) goto return_res; diff --git a/ccronexpr_test.c b/ccronexpr_test.c index 7e7405b..559630c 100644 --- a/ccronexpr_test.c +++ b/ccronexpr_test.c @@ -228,8 +228,15 @@ void check_expr_invalid(const char* expr) { } void test_expr() { + char* tz = getenv("TZ"); + /*Test leap seconds - nejsou nastavené hodnoty, co se kontrolují */ /*Test leap seconds check_fn(cron_next, "60 0 0 * * *", "2015-01-01_15:12:42", "2015-06-30_00:00:00", __LINE__);*/ + if (tz && !strcmp("right/UTC", tz)) { + check_fn(cron_next, "L59 * * * * *", "2016-12-31_23:50:00", "2016-12-31_23:50:59", __LINE__); + check_fn(cron_next, "L60 * * * * *", "2016-12-31_23:50:00", "2016-12-31_23:59:60", __LINE__); + check_fn(cron_next, "L60 * * * * *", "2016-12-31_23:59:59", "2016-12-31_23:59:60", __LINE__); + } check_fn(cron_next, "* * * 1 1 * *", "1970-01-01_15:12:42", "1970-01-01_15:12:43", __LINE__); check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2199", "1970-01-01_15:12:42", "1970-01-01_15:12:43", __LINE__);