From eb8d82460d259b580f242fd3a5ae93c2f84e645a Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 7 Dec 2022 10:05:27 +0100 Subject: [PATCH 1/8] [Cron lib] Update code to latest version --- lib/ccronexpr/ccronexpr.c | 27 ++++++++++++--------------- lib/ccronexpr/ccronexpr.h | 8 -------- lib/ccronexpr/ccronexpr_test.c | 7 +------ 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/lib/ccronexpr/ccronexpr.c b/lib/ccronexpr/ccronexpr.c index 1933eb1442..b60c4b5406 100644 --- a/lib/ccronexpr/ccronexpr.c +++ b/lib/ccronexpr/ccronexpr.c @@ -49,6 +49,7 @@ #define CRON_CF_ARR_LEN 7 +#define CRON_INVALID_INSTANT ((time_t) -1) static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; #define CRON_DAYS_ARR_LEN 7 @@ -84,7 +85,7 @@ void cron_free(void* p); /* forward declarations for platforms that may need them */ /* can be hidden in time.h */ -#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ESP_PLATFORM) && !defined(ANDROID) +#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ESP_PLATFORM) && !defined(ANDROID) && !defined(TARGET_LIKE_MBED) struct tm *gmtime_r(const time_t *timep, struct tm *result); time_t timegm(struct tm* __tp); struct tm *localtime_r(const time_t *timep, struct tm *result); @@ -102,16 +103,15 @@ time_t cron_mktime_gm(struct tm* tm) { #elif defined(__AVR__) /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ return mk_gmtime(tm); -#elif defined(ESP8266) || defined(ESP_PLATFORM) +#elif defined(ESP8266) || defined(ESP_PLATFORM) || defined(TARGET_LIKE_MBED) /* https://linux.die.net/man/3/timegm */ /* http://www.catb.org/esr/time-programming/ */ /* portable version of timegm() */ - time_t ret = -1; - char *tz_orig = NULL; - char *tz = NULL; - tz_orig = getenv("TZ"); - if (tz_orig) - tz = strdup(tz_orig); + time_t ret; + char *tz; + tz = getenv("TZ"); + if (tz) + tz = strdup(tz); setenv("TZ", "UTC+0", 1); tzset(); ret = mktime(tm); @@ -282,7 +282,6 @@ static int add_to_field(struct tm* calendar, int field, int val) { calendar->tm_hour = calendar->tm_hour + val; break; case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ - break; case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = calendar->tm_mday + val; break; @@ -522,12 +521,12 @@ static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { if (!resets || !empty_list) { res = -1; } - if (empty_list) { - cron_free(empty_list); - } if (resets) { cron_free(resets); } + if (empty_list) { + cron_free(empty_list); + } return res; } @@ -650,7 +649,6 @@ static char** split_str(const char* str, char del, size_t* len_out) { int c = str[i]; if (del == str[i]) { if (bi > 0) { - if (ri >= len) goto return_error; tmp = strdupl(buf, bi); if (!tmp) goto return_error; res[ri++] = tmp; @@ -663,7 +661,6 @@ static char** split_str(const char* str, char del, size_t* len_out) { } /* tail */ if (bi > 0) { - if (ri >= len) goto return_error; tmp = strdupl(buf, bi); if (!tmp) goto return_error; res[ri++] = tmp; @@ -673,10 +670,10 @@ static char** split_str(const char* str, char del, size_t* len_out) { return res; return_error: - free_splitted(res, len); if (buf) { cron_free(buf); } + free_splitted(res, len); *len_out = 0; return NULL; } diff --git a/lib/ccronexpr/ccronexpr.h b/lib/ccronexpr/ccronexpr.h index 450decb5c7..e04a6731db 100644 --- a/lib/ccronexpr/ccronexpr.h +++ b/lib/ccronexpr/ccronexpr.h @@ -24,8 +24,6 @@ #ifndef CCRONEXPR_H #define CCRONEXPR_H -//-V::795 - #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) extern "C" { #endif @@ -38,12 +36,6 @@ extern "C" { #include /*added for use if uint*_t data types*/ - -#define CRON_INVALID_INSTANT ((time_t) -1) - -// Define to use local time -#define CRON_USE_LOCAL_TIME - /** * Parsed cron expression */ diff --git a/lib/ccronexpr/ccronexpr_test.c b/lib/ccronexpr/ccronexpr_test.c index 7fc8aba670..53d32b2d66 100644 --- a/lib/ccronexpr/ccronexpr_test.c +++ b/lib/ccronexpr/ccronexpr_test.c @@ -225,9 +225,7 @@ void check_same(const char* expr1, const char* expr2) { cron_parse_expr(expr1, &parsed1, NULL); cron_expr parsed2; cron_parse_expr(expr2, &parsed2, NULL); - int res = crons_equal(&parsed1, &parsed2); - res = res; // to prevent warnings - assert(res); + assert(crons_equal(&parsed1, &parsed2)); } void check_calc_invalid() { @@ -236,7 +234,6 @@ void check_calc_invalid() { struct tm * calinit = poors_mans_strptime("2012-07-01_09:53:50"); time_t dateinit = timegm(calinit); time_t res = cron_next(&parsed, dateinit); - res = res;; // to prevent warnings assert(INVALID_INSTANT == res); free(calinit); } @@ -352,8 +349,6 @@ void test_bits() { uint8_t testbyte[8]; memset(testbyte, 0, 8); int err = 0; - err = err; // to prevent warnings - int i; for (i = 0; i <= 63; i++) { From 40dcb426fe12cc1ebd1c6790a18c6ccbbcf32457 Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 7 Dec 2022 10:10:28 +0100 Subject: [PATCH 2/8] [Cron] Fix unintended month rollovers, ported from staticlibs#35 Applying patch suggested here: https://github.com/staticlibs/ccronexpr/issues/35 --- lib/ccronexpr/ccronexpr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ccronexpr/ccronexpr.c b/lib/ccronexpr/ccronexpr.c index b60c4b5406..c5c5b3c8f1 100644 --- a/lib/ccronexpr/ccronexpr.c +++ b/lib/ccronexpr/ccronexpr.c @@ -409,10 +409,10 @@ static unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int valu next_value = next_set_bit(bits, max, 0, ¬found); } if (notfound || next_value != value) { - err = set_field(calendar, field, next_value); - if (err) goto return_error; err = reset_all_min(calendar, lower_orders); if (err) goto return_error; + err = set_field(calendar, field, next_value); + if (err) goto return_error; } return next_value; From c1b63dc779a6ffaccb31f86b5ee3c57de9350d5c Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 7 Dec 2022 10:20:43 +0100 Subject: [PATCH 3/8] [Cron] Prevent warnings in ccronexpr_test.c --- lib/ccronexpr/ccronexpr_test.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/ccronexpr/ccronexpr_test.c b/lib/ccronexpr/ccronexpr_test.c index 53d32b2d66..cf4489ef33 100644 --- a/lib/ccronexpr/ccronexpr_test.c +++ b/lib/ccronexpr/ccronexpr_test.c @@ -225,7 +225,9 @@ void check_same(const char* expr1, const char* expr2) { cron_parse_expr(expr1, &parsed1, NULL); cron_expr parsed2; cron_parse_expr(expr2, &parsed2, NULL); - assert(crons_equal(&parsed1, &parsed2)); + const int res = crons_equal(&parsed1, &parsed2); + (void)res; // prevent warnings + assert(res); } void check_calc_invalid() { @@ -235,6 +237,7 @@ void check_calc_invalid() { time_t dateinit = timegm(calinit); time_t res = cron_next(&parsed, dateinit); assert(INVALID_INSTANT == res); + (void)res; // suppress warning free(calinit); } @@ -363,6 +366,7 @@ void test_bits() { err = 1; } assert(!err); + (void)err; // suppress "not used" warning } for (i = 0; i < 12; i++) { From 181ed71e5c30d6f14dd7dda1638ed9f9c3b530cb Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 7 Dec 2022 10:37:37 +0100 Subject: [PATCH 4/8] [Cron] Fix build error (define CRON_INVALID_INSTANT in header) --- lib/ccronexpr/ccronexpr.c | 1 - lib/ccronexpr/ccronexpr.h | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ccronexpr/ccronexpr.c b/lib/ccronexpr/ccronexpr.c index c5c5b3c8f1..552343ab48 100644 --- a/lib/ccronexpr/ccronexpr.c +++ b/lib/ccronexpr/ccronexpr.c @@ -49,7 +49,6 @@ #define CRON_CF_ARR_LEN 7 -#define CRON_INVALID_INSTANT ((time_t) -1) static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; #define CRON_DAYS_ARR_LEN 7 diff --git a/lib/ccronexpr/ccronexpr.h b/lib/ccronexpr/ccronexpr.h index e04a6731db..f35819cfc9 100644 --- a/lib/ccronexpr/ccronexpr.h +++ b/lib/ccronexpr/ccronexpr.h @@ -36,6 +36,8 @@ extern "C" { #include /*added for use if uint*_t data types*/ +#define CRON_INVALID_INSTANT ((time_t) -1) + /** * Parsed cron expression */ From 6cbb8a2765b0bec1ba0d529b95ed415e0e7a88dc Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 7 Dec 2022 10:55:03 +0100 Subject: [PATCH 5/8] Revert "[Cron lib] Update code to latest version" Updating the lib code did revert changes we made ourselves --- lib/ccronexpr/ccronexpr.c | 26 +++++++++++++++----------- lib/ccronexpr/ccronexpr.h | 6 ++++++ lib/ccronexpr/ccronexpr_test.c | 7 +++++-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lib/ccronexpr/ccronexpr.c b/lib/ccronexpr/ccronexpr.c index 552343ab48..2a3e764355 100644 --- a/lib/ccronexpr/ccronexpr.c +++ b/lib/ccronexpr/ccronexpr.c @@ -84,7 +84,7 @@ void cron_free(void* p); /* forward declarations for platforms that may need them */ /* can be hidden in time.h */ -#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ESP_PLATFORM) && !defined(ANDROID) && !defined(TARGET_LIKE_MBED) +#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ESP_PLATFORM) && !defined(ANDROID) struct tm *gmtime_r(const time_t *timep, struct tm *result); time_t timegm(struct tm* __tp); struct tm *localtime_r(const time_t *timep, struct tm *result); @@ -102,15 +102,16 @@ time_t cron_mktime_gm(struct tm* tm) { #elif defined(__AVR__) /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ return mk_gmtime(tm); -#elif defined(ESP8266) || defined(ESP_PLATFORM) || defined(TARGET_LIKE_MBED) +#elif defined(ESP8266) || defined(ESP_PLATFORM) /* https://linux.die.net/man/3/timegm */ /* http://www.catb.org/esr/time-programming/ */ /* portable version of timegm() */ - time_t ret; - char *tz; - tz = getenv("TZ"); - if (tz) - tz = strdup(tz); + time_t ret = -1; + char *tz_orig = NULL; + char *tz = NULL; + tz_orig = getenv("TZ"); + if (tz_orig) + tz = strdup(tz_orig); setenv("TZ", "UTC+0", 1); tzset(); ret = mktime(tm); @@ -281,6 +282,7 @@ static int add_to_field(struct tm* calendar, int field, int val) { calendar->tm_hour = calendar->tm_hour + val; break; case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ + break; case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = calendar->tm_mday + val; break; @@ -520,12 +522,12 @@ static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { if (!resets || !empty_list) { res = -1; } - if (resets) { - cron_free(resets); - } if (empty_list) { cron_free(empty_list); } + if (resets) { + cron_free(resets); + } return res; } @@ -648,6 +650,7 @@ static char** split_str(const char* str, char del, size_t* len_out) { int c = str[i]; if (del == str[i]) { if (bi > 0) { + if (ri >= len) goto return_error; tmp = strdupl(buf, bi); if (!tmp) goto return_error; res[ri++] = tmp; @@ -660,6 +663,7 @@ static char** split_str(const char* str, char del, size_t* len_out) { } /* tail */ if (bi > 0) { + if (ri >= len) goto return_error; tmp = strdupl(buf, bi); if (!tmp) goto return_error; res[ri++] = tmp; @@ -669,10 +673,10 @@ static char** split_str(const char* str, char del, size_t* len_out) { return res; return_error: + free_splitted(res, len); if (buf) { cron_free(buf); } - free_splitted(res, len); *len_out = 0; return NULL; } diff --git a/lib/ccronexpr/ccronexpr.h b/lib/ccronexpr/ccronexpr.h index f35819cfc9..450decb5c7 100644 --- a/lib/ccronexpr/ccronexpr.h +++ b/lib/ccronexpr/ccronexpr.h @@ -24,6 +24,8 @@ #ifndef CCRONEXPR_H #define CCRONEXPR_H +//-V::795 + #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) extern "C" { #endif @@ -36,8 +38,12 @@ extern "C" { #include /*added for use if uint*_t data types*/ + #define CRON_INVALID_INSTANT ((time_t) -1) +// Define to use local time +#define CRON_USE_LOCAL_TIME + /** * Parsed cron expression */ diff --git a/lib/ccronexpr/ccronexpr_test.c b/lib/ccronexpr/ccronexpr_test.c index cf4489ef33..c9ebf5f401 100644 --- a/lib/ccronexpr/ccronexpr_test.c +++ b/lib/ccronexpr/ccronexpr_test.c @@ -225,8 +225,8 @@ void check_same(const char* expr1, const char* expr2) { cron_parse_expr(expr1, &parsed1, NULL); cron_expr parsed2; cron_parse_expr(expr2, &parsed2, NULL); - const int res = crons_equal(&parsed1, &parsed2); - (void)res; // prevent warnings + int res = crons_equal(&parsed1, &parsed2); + res = res; // to prevent warnings assert(res); } @@ -236,6 +236,7 @@ void check_calc_invalid() { struct tm * calinit = poors_mans_strptime("2012-07-01_09:53:50"); time_t dateinit = timegm(calinit); time_t res = cron_next(&parsed, dateinit); + res = res;; // to prevent warnings assert(INVALID_INSTANT == res); (void)res; // suppress warning free(calinit); @@ -352,6 +353,8 @@ void test_bits() { uint8_t testbyte[8]; memset(testbyte, 0, 8); int err = 0; + err = err; // to prevent warnings + int i; for (i = 0; i <= 63; i++) { From 43cdada083b795b75a9a409c19eb3a9fafefc622 Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 4 Dec 2023 09:58:22 +0100 Subject: [PATCH 6/8] [Cron] Switch ccronexpr lib to supertinycron fork. --- lib/ccronexpr/.gitignore | 3 - lib/ccronexpr/README.md | 97 -- lib/ccronexpr/ccronexpr.c | 1275 ----------------- lib/ccronexpr/ccronexpr_test.c | 416 ------ lib/ccronexpr/library.json | 14 - lib/ccronexpr/library.properties | 8 - lib/supertinycron/.github/workflows/build.yml | 60 + lib/supertinycron/.gitignore | 8 + lib/{ccronexpr => supertinycron}/.travis.yml | 18 +- lib/supertinycron/CMakeLists.txt | 39 + lib/supertinycron/ESP-IDF.md | 22 + lib/{ccronexpr => supertinycron}/LICENSE.txt | 0 lib/supertinycron/Makefile | 35 + lib/supertinycron/README.md | 262 ++++ lib/{ccronexpr => supertinycron}/appveyor.yml | 4 + lib/supertinycron/ccronexpr.c | 883 ++++++++++++ lib/{ccronexpr => supertinycron}/ccronexpr.h | 50 +- lib/supertinycron/ccronexpr_test.c | 884 ++++++++++++ lib/supertinycron/supertinycron.c | 234 +++ lib/supertinycron/test/CMakeLists.txt | 16 + platformio_core_defs.ini | 3 + 21 files changed, 2497 insertions(+), 1834 deletions(-) delete mode 100644 lib/ccronexpr/.gitignore delete mode 100644 lib/ccronexpr/README.md delete mode 100644 lib/ccronexpr/ccronexpr.c delete mode 100644 lib/ccronexpr/ccronexpr_test.c delete mode 100644 lib/ccronexpr/library.json delete mode 100644 lib/ccronexpr/library.properties create mode 100644 lib/supertinycron/.github/workflows/build.yml create mode 100644 lib/supertinycron/.gitignore rename lib/{ccronexpr => supertinycron}/.travis.yml (52%) create mode 100644 lib/supertinycron/CMakeLists.txt create mode 100644 lib/supertinycron/ESP-IDF.md rename lib/{ccronexpr => supertinycron}/LICENSE.txt (100%) create mode 100644 lib/supertinycron/Makefile create mode 100644 lib/supertinycron/README.md rename lib/{ccronexpr => supertinycron}/appveyor.yml (73%) create mode 100644 lib/supertinycron/ccronexpr.c rename lib/{ccronexpr => supertinycron}/ccronexpr.h (77%) create mode 100644 lib/supertinycron/ccronexpr_test.c create mode 100644 lib/supertinycron/supertinycron.c create mode 100644 lib/supertinycron/test/CMakeLists.txt diff --git a/lib/ccronexpr/.gitignore b/lib/ccronexpr/.gitignore deleted file mode 100644 index c9e21d91ed..0000000000 --- a/lib/ccronexpr/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build/ -/nbproject -/Makefile diff --git a/lib/ccronexpr/README.md b/lib/ccronexpr/README.md deleted file mode 100644 index 788ec61d16..0000000000 --- a/lib/ccronexpr/README.md +++ /dev/null @@ -1,97 +0,0 @@ -Cron expression parsing in ANSI C -================================= - -[![travis](https://travis-ci.org/staticlibs/ccronexpr.svg?branch=master)](https://travis-ci.org/staticlibs/ccronexpr) -[![appveyor](https://ci.appveyor.com/api/projects/status/github/staticlibs/ccronexpr?svg=true)](https://ci.appveyor.com/project/staticlibs/ccronexpr) - -Given a cron expression and a date, you can get the next date which satisfies the cron expression. - -Supports cron expressions with `seconds` field. Based on implementation of [CronSequenceGenerator](https://github.com/spring-projects/spring-framework/blob/babbf6e8710ab937cd05ece20270f51490299270/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java) from Spring Framework. - -Compiles and should work on Linux (GCC/Clang), Mac OS (Clang), Windows (MSVC), Android NDK, iOS and possibly on other platforms with `time.h` support. - -Supports compilation in C (89) and in C++ modes. - -Usage example -------------- - - #include "ccronexpr.h" - - cron_expr expr; - const char* err = NULL; - memset(&expr, 0, sizeof(expr)); - cron_parse_expr("0 */2 1-4 * * *", &expr, &err); - if (err) ... /* invalid expression */ - time_t cur = time(NULL); - time_t next = cron_next(&expr, cur); - - -Compilation and tests run examples ----------------------------------- - - gcc ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out - g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out - g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out - - clang ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out - clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out - clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out - - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS && ccronexpr.exe - -Examples of supported expressions ---------------------------------- - -Expression, input date, next date: - - "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00" - "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00" - "0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00" - "0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00" - -See more examples in [tests](https://github.com/staticlibs/ccronexpr/blob/a1343bc5a546b13430bd4ac72f3b047ac08f8192/ccronexpr_test.c#L251). - -Timezones ---------- - -This implementation does not support explicit timezones handling. By default all dates are -processed as UTC (GMT) dates without timezone infomation. - -To use local dates (current system timezone) instead of GMT compile with `-DCRON_USE_LOCAL_TIME`, example: - - gcc -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && TZ="America/Toronto" ./a.out - -License information -------------------- - -This project is released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). - -Changelog ---------- - -**2019-03-27** - - * `CRON_USE_LOCAL_TIME` usage fixes - -**2018-05-23** - - * merged [#8](https://github.com/staticlibs/ccronexpr/pull/8) - * merged [#9](https://github.com/staticlibs/ccronexpr/pull/9) - * minor cleanups - -**2018-01-27** - - * merged [#6](https://github.com/staticlibs/ccronexpr/pull/6) - * updated license file (to the one parse-able by github) - -**2017-09-24** - - * merged [#4](https://github.com/staticlibs/ccronexpr/pull/4) - -**2016-06-17** - - * use thread-safe versions of `gmtime` and `localtime` - -**2015-02-28** - - * initial public version diff --git a/lib/ccronexpr/ccronexpr.c b/lib/ccronexpr/ccronexpr.c deleted file mode 100644 index 2a3e764355..0000000000 --- a/lib/ccronexpr/ccronexpr.c +++ /dev/null @@ -1,1275 +0,0 @@ -/* - * 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: ccronexpr.c - * Author: alex - * - * Created on February 24, 2015, 9:35 AM - */ - -#include -#include -#include -#include -#include -#include -#include - -#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 12 -#define CRON_MAX_YEARS_DIFF 4 - -#define CRON_CF_SECOND 0 -#define CRON_CF_MINUTE 1 -#define CRON_CF_HOUR_OF_DAY 2 -#define CRON_CF_DAY_OF_WEEK 3 -#define CRON_CF_DAY_OF_MONTH 4 -#define CRON_CF_MONTH 5 -#define CRON_CF_YEAR 6 - -#define CRON_CF_ARR_LEN 7 - - -static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; -#define CRON_DAYS_ARR_LEN 7 -static const char* const 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 CRON_TEST_MALLOC -#define cron_malloc(x) malloc(x); -#define cron_free(x) free(x); -#else /* CRON_TEST_MALLOC */ -void* cron_malloc(size_t n); -void cron_free(void* p); -#endif /* CRON_TEST_MALLOC */ - -/** - * Time functions from standard library. - * This part defines: cron_mktime: create time_t from tm - * cron_time: create tm from time_t - */ - -/* forward declarations for platforms that may need them */ -/* can be hidden in time.h */ -#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ESP_PLATFORM) && !defined(ANDROID) -struct tm *gmtime_r(const time_t *timep, struct tm *result); -time_t timegm(struct tm* __tp); -struct tm *localtime_r(const time_t *timep, struct tm *result); -#endif /* PLEASE CHECK _WIN32 AND ANDROID NEEDS FOR THESE DECLARATIONS */ -#ifdef __MINGW32__ -/* To avoid warning when building with mingw */ -time_t _mkgmtime(struct tm* tm); -#endif /* __MINGW32__ */ - -/* function definitions */ -time_t cron_mktime_gm(struct tm* tm) { -#if defined(_WIN32) -/* http://stackoverflow.com/a/22557778 */ - return _mkgmtime(tm); -#elif defined(__AVR__) -/* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ - return mk_gmtime(tm); -#elif defined(ESP8266) || defined(ESP_PLATFORM) - /* https://linux.die.net/man/3/timegm */ - /* http://www.catb.org/esr/time-programming/ */ - /* portable version of timegm() */ - time_t ret = -1; - char *tz_orig = NULL; - char *tz = NULL; - tz_orig = getenv("TZ"); - if (tz_orig) - tz = strdup(tz_orig); - setenv("TZ", "UTC+0", 1); - tzset(); - ret = mktime(tm); - if (tz) { - setenv("TZ", tz, 1); - free(tz); - } else - unsetenv("TZ"); - tzset(); - return ret; -#elif defined(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; -#else - return timegm(tm); -#endif -} - -struct tm* cron_time_gm(time_t* date, struct tm* out) { -#if defined(__MINGW32__) - (void)(out); /* To avoid unused warning */ - return gmtime(date); -#elif defined(_WIN32) - errno_t err = gmtime_s(out, date); - return 0 == err ? out : NULL; -#elif defined(__AVR__) - /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ - gmtime_r(date, out); - return out; -#else - return gmtime_r(date, out); -#endif -} - -time_t cron_mktime_local(struct tm* tm) { - tm->tm_isdst = -1; - return mktime(tm); -} - -struct tm* cron_time_local(time_t* date, struct tm* out) { -#if defined(_WIN32) - errno_t err = localtime_s(out, date); - return 0 == err ? out : NULL; -#elif defined(__AVR__) - /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ - localtime_r(date, out); - return out; -#else - return localtime_r(date, out); -#endif -} - -/* Defining 'cron_' time functions to use use UTC (default) or local time */ -#ifndef CRON_USE_LOCAL_TIME -time_t cron_mktime(struct tm* tm) { - return cron_mktime_gm(tm); -} - -struct tm* cron_time(time_t* date, struct tm* out) { - return cron_time_gm(date, out); -} - -#else /* CRON_USE_LOCAL_TIME */ -time_t cron_mktime(struct tm* tm) { - return cron_mktime_local(tm); -} - -struct tm* cron_time(time_t* date, struct tm* out) { - return cron_time_local(date, out); -} - -#endif /* CRON_USE_LOCAL_TIME */ - -/** - * Functions. - */ - -void cron_set_bit(uint8_t* rbyte, int idx) { - uint8_t j = (uint8_t) (idx / 8); - uint8_t k = (uint8_t) (idx % 8); - - rbyte[j] |= (1 << k); -} - -void cron_del_bit(uint8_t* rbyte, int idx) { - uint8_t j = (uint8_t) (idx / 8); - uint8_t k = (uint8_t) (idx % 8); - - rbyte[j] &= ~(1 << k); -} - -uint8_t cron_get_bit(uint8_t* rbyte, int idx) { - uint8_t j = (uint8_t) (idx / 8); - uint8_t k = (uint8_t) (idx % 8); - - if (rbyte[j] & (1 << k)) { - return 1; - } else { - return 0; - } -} - -static void free_splitted(char** splitted, size_t len) { - size_t i; - if (!splitted) return; - for (i = 0; i < len; i++) { - if (splitted[i]) { - cron_free(splitted[i]); - } - } - cron_free(splitted); -} - -static char* strdupl(const char* str, size_t len) { - if (!str) return NULL; - char* res = (char*) cron_malloc(len + 1); - if (!res) return NULL; - memset(res, 0, len + 1); - memcpy(res, str, len); - return res; -} - -static unsigned int next_set_bit(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_get_bit(bits, i)) return i; - } - *notfound = 1; - return 0; -} - -static void push_to_fields_arr(int* arr, int fi) { - int i; - if (!arr || -1 == fi) { - return; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (arr[i] == fi) return; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (-1 == arr[i]) { - arr[i] = fi; - return; - } - } -} - -static int add_to_field(struct tm* calendar, int field, int val) { - if (!calendar || -1 == field) { - 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 */ - break; - 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 setting all the fields provided to zero. - */ -static int reset_min(struct tm* calendar, int field) { - if (!calendar || -1 == field) { - 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; -} - -static int reset_all_min(struct tm* calendar, int* fields) { - int i; - int res = 0; - if (!calendar || !fields) { - return 1; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (-1 != fields[i]) { - res = reset_min(calendar, fields[i]); - if (0 != res) return res; - } - } - return 0; -} - -static int set_field(struct tm* calendar, int field, int val) { - if (!calendar || -1 == field) { - return 1; - } - switch (field) { - case CRON_CF_SECOND: - calendar->tm_sec = val; - break; - case CRON_CF_MINUTE: - calendar->tm_min = val; - break; - case CRON_CF_HOUR_OF_DAY: - calendar->tm_hour = val; - break; - case CRON_CF_DAY_OF_WEEK: - calendar->tm_wday = val; - break; - case CRON_CF_DAY_OF_MONTH: - calendar->tm_mday = val; - break; - case CRON_CF_MONTH: - calendar->tm_mon = val; - break; - case CRON_CF_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; -} - -/** - * Search the bits provided for the next set bit after the value provided, - * and reset the calendar. - */ -static unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, 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_min(calendar, field); - if (err) goto return_error; - notfound = 0; - next_value = next_set_bit(bits, max, 0, ¬found); - } - if (notfound || next_value != value) { - err = reset_all_min(calendar, lower_orders); - 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; -} - -static unsigned int find_next_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { - int err; - unsigned int count = 0; - unsigned int max = 366; - while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { - 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_min(calendar, resets); - } - return day_of_month; - - return_error: - *res_out = 1; - return 0; -} - -static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { - int i; - int res = 0; - int* resets = NULL; - int* empty_list = NULL; - 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; - - resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!resets) goto return_result; - empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!empty_list) goto return_result; - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - resets[i] = -1; - empty_list[i] = -1; - } - - second = calendar->tm_sec; - update_second = find_next(expr->seconds, CRON_MAX_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); - } - - minute = calendar->tm_min; - update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); - if (0 != res) goto return_result; - if (minute == update_minute) { - push_to_fields_arr(resets, CRON_CF_MINUTE); - } else { - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - - 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, resets, &res); - if (0 != res) goto return_result; - if (hour == update_hour) { - push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); - } else { - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - - day_of_week = calendar->tm_wday; - day_of_month = calendar->tm_mday; - update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); - if (0 != res) goto return_result; - if (day_of_month == update_day_of_month) { - push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); - } else { - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - - 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, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); - if (0 != res) goto return_result; - if (month != update_month) { - if (calendar->tm_year - dot > 4) { - res = -1; - goto return_result; - } - res = do_next(expr, calendar, dot); - if (0 != res) goto return_result; - } - goto return_result; - - return_result: - if (!resets || !empty_list) { - res = -1; - } - if (empty_list) { - cron_free(empty_list); - } - if (resets) { - cron_free(resets); - } - return res; -} - -static int to_upper(char* str) { - if (!str) return 1; - int i; - for (i = 0; '\0' != str[i]; i++) { - int c = (int)str[i]; - str[i] = (char) toupper(c); - } - return 0; -} - -static char* to_string(int num) { - if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; - char* str = (char*) cron_malloc(CRON_NUM_OF_DIGITS(num) + 1); - if (!str) return NULL; - int res = sprintf(str, "%d", num); - if (res < 0) { - cron_free(str); - 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*) cron_malloc(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++) { - int c = str[i]; - if (del == str[i]) { - if (accum > 0) { - len += 1; - accum = 0; - } - } else if (!isspace(c)) { - accum += 1; - } - } - /* tail */ - if (accum > 0) { - len += 1; - } - if (0 == len) return NULL; - - buf = (char*) cron_malloc(stlen + 1); - if (!buf) goto return_error; - memset(buf, 0, stlen + 1); - res = (char**) cron_malloc(len * sizeof(char*)); - if (!res) goto return_error; - memset(res, 0, len * sizeof(char*)); - - for (i = 0; i < stlen; i++) { - int c = str[i]; - if (del == str[i]) { - if (bi > 0) { - if (ri >= len) goto return_error; - tmp = strdupl(buf, bi); - if (!tmp) goto return_error; - res[ri++] = tmp; - memset(buf, 0, stlen + 1); - bi = 0; - } - } else if (!isspace(c)) { - buf[bi++] = str[i]; - } - } - /* tail */ - if (bi > 0) { - if (ri >= len) goto return_error; - tmp = strdupl(buf, bi); - if (!tmp) goto return_error; - res[ri++] = tmp; - } - cron_free(buf); - *len_out = len; - return res; - - return_error: - free_splitted(res, len); - if (buf) { - cron_free(buf); - } - *len_out = 0; - return NULL; -} - -static char* replace_ordinals(char* value, const char* const * 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) { - cron_free(cur); - } - return NULL; - } - res = str_replace(cur, arr[i], strnum); - cron_free(strnum); - if (!first) { - cron_free(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 unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { - - char** parts = NULL; - size_t len = 0; - unsigned int* res = (unsigned int*) cron_malloc(2 * sizeof(unsigned int)); - if (!res) goto return_error; - - res[0] = 0; - res[1] = 0; - if (1 == strlen(field) && '*' == field[0]) { - res[0] = min; - res[1] = max - 1; - } else if (!has_char(field, '-')) { - int err = 0; - unsigned int val = parse_uint(field, &err); - if (err) { - *error = "Unsigned integer parse error 1"; - goto return_error; - } - - res[0] = val; - res[1] = val; - } else { - parts = split_str(field, '-', &len); - if (2 != len) { - *error = "Specified range requires two fields"; - goto return_error; - } - int err = 0; - res[0] = parse_uint(parts[0], &err); - if (err) { - *error = "Unsigned integer parse error 2"; - goto return_error; - } - res[1] = parse_uint(parts[1], &err); - if (err) { - *error = "Unsigned integer parse error 3"; - goto return_error; - } - } - if (res[0] >= max || res[1] >= max) { - *error = "Specified range exceeds maximum"; - goto return_error; - } - if (res[0] < min || res[1] < min) { - *error = "Specified range is less than minimum"; - goto return_error; - } - if (res[0] > res[1]) { - *error = "Specified range start exceeds range end"; - goto return_error; - } - - free_splitted(parts, len); - *error = NULL; - return res; - - return_error: - free_splitted(parts, len); - if (res) { - cron_free(res); - } - - return NULL; -} - -static void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { - size_t i; - unsigned int i1; - size_t len = 0; - - char** fields = split_str(value, ',', &len); - if (!fields) { - *error = "Comma split error"; - goto return_result; - } - - for (i = 0; i < len; i++) { - if (!has_char(fields[i], '/')) { - /* Not an incrementer so it must be a range (possibly empty) */ - - unsigned int* range = get_range(fields[i], min, max, error); - - if (*error) { - if (range) { - cron_free(range); - } - goto return_result; - - } - - for (i1 = range[0]; i1 <= range[1]; i1++) { - cron_set_bit(target, i1); - - } - cron_free(range); - - } else { - size_t len2 = 0; - char** split = split_str(fields[i], '/', &len2); - if (2 != len2) { - *error = "Incrementer must have two fields"; - free_splitted(split, len2); - goto return_result; - } - unsigned int* range = get_range(split[0], min, max, error); - if (*error) { - if (range) { - cron_free(range); - } - free_splitted(split, len2); - goto return_result; - } - if (!has_char(split[0], '-')) { - range[1] = max - 1; - } - int err = 0; - unsigned int delta = parse_uint(split[1], &err); - if (err) { - *error = "Unsigned integer parse error 4"; - cron_free(range); - free_splitted(split, len2); - goto return_result; - } - if (0 == delta) { - *error = "Incrementer may not be zero"; - cron_free(range); - free_splitted(split, len2); - goto return_result; - } - for (i1 = range[0]; i1 <= range[1]; i1 += delta) { - cron_set_bit(target, i1); - } - free_splitted(split, len2); - cron_free(range); - - } - } - goto return_result; - - return_result: - free_splitted(fields, len); - -} - -static void set_months(char* value, uint8_t* targ, const char** error) { - unsigned int i; - unsigned int max = 12; - - char* replaced = NULL; - - to_upper(value); - replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); - if (!replaced) { - *error = "Invalid month format"; - return; - } - set_number_hits(replaced, targ, 1, max + 1, error); - cron_free(replaced); - - /* ... and then rotate it to the front of the months */ - for (i = 1; i <= max; i++) { - if (cron_get_bit(targ, i)) { - cron_set_bit(targ, i - 1); - cron_del_bit(targ, i); - } - } -} - -static void set_days_of_week(char* field, uint8_t* targ, const char** error) { - unsigned int max = 7; - char* replaced = NULL; - - if (1 == strlen(field) && '?' == field[0]) { - field[0] = '*'; - } - to_upper(field); - replaced = replace_ordinals(field, DAYS_ARR, CRON_DAYS_ARR_LEN); - if (!replaced) { - *error = "Invalid day format"; - return; - } - set_number_hits(replaced, targ, 0, max + 1, error); - cron_free(replaced); - if (cron_get_bit(targ, 7)) { - /* Sunday can be represented as 0 or 7*/ - cron_set_bit(targ, 0); - cron_del_bit(targ, 7); - } -} - -static void set_days_of_month(char* field, uint8_t* targ, const char** error) { - /* Days of month start with 1 (in Cron and Calendar) so add one */ - if (1 == strlen(field) && '?' == field[0]) { - field[0] = '*'; - } - set_number_hits(field, targ, 1, CRON_MAX_DAYS_OF_MONTH, error); -} - -void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { - const char* err_local; - size_t len = 0; - char** fields = NULL; - if (!error) { - error = &err_local; - } - *error = NULL; - if (!expression) { - *error = "Invalid NULL expression"; - goto return_res; - } - if (!target) { - *error = "Invalid NULL target"; - goto return_res; - } - - fields = split_str(expression, ' ', &len); - if (len != 6) { - *error = "Invalid number of fields, expression must consist of 6 fields"; - goto return_res; - } - memset(target, 0, sizeof(*target)); - set_number_hits(fields[0], target->seconds, 0, 60, error); - if (*error) goto return_res; - set_number_hits(fields[1], target->minutes, 0, 60, error); - if (*error) goto return_res; - set_number_hits(fields[2], target->hours, 0, 24, error); - if (*error) goto return_res; - set_days_of_month(fields[3], target->days_of_month, error); - if (*error) goto return_res; - set_months(fields[4], target->months, error); - if (*error) goto return_res; - set_days_of_week(fields[5], target->days_of_week, error); - if (*error) goto return_res; - - goto return_res; - - return_res: - free_splitted(fields, len); -} - -time_t cron_next(cron_expr* expr, time_t date) { - /* - The plan: - - 1 Round up to the next whole second - - 2 If seconds match move on, otherwise find the next match: - 2.1 If next match is in the next minute then roll forwards - - 3 If minute matches move on, otherwise find the next match - 3.1 If next match is in the next hour then roll forwards - 3.2 Reset the seconds and go to 2 - - 4 If hour matches move on, otherwise find the next match - 4.1 If next match is in the next day then roll forwards, - 4.2 Reset the minutes and seconds and go to 2 - - ... - */ - if (!expr) return CRON_INVALID_INSTANT; - struct tm calval; - memset(&calval, 0, sizeof(struct tm)); - struct tm* calendar = cron_time(&date, &calval); - if (!calendar) return CRON_INVALID_INSTANT; - time_t original = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; - - int res = do_next(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - - time_t calculated = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; - if (calculated == original) { - /* We arrived at the original timestamp - round up to the next whole second and try again... */ - res = add_to_field(calendar, CRON_CF_SECOND, 1); - if (0 != res) return CRON_INVALID_INSTANT; - res = do_next(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - } - - return cron_mktime(calendar); -} - - -/* https://github.com/staticlibs/ccronexpr/pull/8 */ - -static unsigned int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) { - int i; - if (!bits) { - *notfound = 1; - return 0; - } - for (i = from_index; i >= to_index; i--) { - if (cron_get_bit(bits, i)) return i; - } - *notfound = 1; - return 0; -} - -static int last_day_of_month(int month, int year) { - struct tm cal; - time_t t; - memset(&cal,0,sizeof(cal)); - cal.tm_sec=0; - cal.tm_min=0; - cal.tm_hour=0; - cal.tm_mon = month+1; - cal.tm_mday = 0; - cal.tm_year=year; - t=mktime(&cal); - return gmtime(&t)->tm_mday; -} - -/** - * Reset the calendar setting all the fields provided to zero. - */ -static int reset_max(struct tm* calendar, int field) { - if (!calendar || -1 == field) { - return 1; - } - switch (field) { - case CRON_CF_SECOND: - calendar->tm_sec = 59; - break; - case CRON_CF_MINUTE: - calendar->tm_min = 59; - break; - case CRON_CF_HOUR_OF_DAY: - calendar->tm_hour = 23; - break; - case CRON_CF_DAY_OF_WEEK: - calendar->tm_wday = 6; - break; - case CRON_CF_DAY_OF_MONTH: - calendar->tm_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year); - break; - case CRON_CF_MONTH: - calendar->tm_mon = 11; - break; - case CRON_CF_YEAR: - /* I don't think this is supposed to happen ... */ - fprintf(stderr, "reset CRON_CF_YEAR\n"); - break; - default: - return 1; /* unknown field */ - } - time_t res = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == res) { - return 1; - } - return 0; -} - -static int reset_all_max(struct tm* calendar, int* fields) { - int i; - int res = 0; - if (!calendar || !fields) { - return 1; - } - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - if (-1 != fields[i]) { - res = reset_max(calendar, fields[i]); - if (0 != res) return res; - } - } - return 0; -} - -/** - * Search the bits provided for the next set bit after the value provided, - * and reset the calendar. - */ -static unsigned int find_prev(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { - int notfound = 0; - int err = 0; - unsigned int next_value = prev_set_bit(bits, value, 0, ¬found); - /* roll under if needed */ - if (notfound) { - err = add_to_field(calendar, nextField, -1); - if (err) goto return_error; - err = reset_max(calendar, field); - if (err) goto return_error; - notfound = 0; - next_value = prev_set_bit(bits, max - 1, value, ¬found); - } - if (notfound || next_value != value) { - err = set_field(calendar, field, next_value); - if (err) goto return_error; - err = reset_all_max(calendar, lower_orders); - if (err) goto return_error; - } - return next_value; - - return_error: - *res_out = 1; - return 0; -} - -static unsigned int find_prev_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { - int err; - unsigned int count = 0; - unsigned int max = 366; - while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { - 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_max(calendar, resets); - } - return day_of_month; - - return_error: - *res_out = 1; - return 0; -} - -static int do_prev(cron_expr* expr, struct tm* calendar, unsigned int dot) { - int i; - int res = 0; - int* resets = NULL; - int* empty_list = NULL; - 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; - - resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!resets) goto return_result; - empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); - if (!empty_list) goto return_result; - for (i = 0; i < CRON_CF_ARR_LEN; i++) { - resets[i] = -1; - empty_list[i] = -1; - } - - second = calendar->tm_sec; - update_second = find_prev(expr->seconds, CRON_MAX_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); - } - - minute = calendar->tm_min; - update_minute = find_prev(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); - if (0 != res) goto return_result; - if (minute == update_minute) { - push_to_fields_arr(resets, CRON_CF_MINUTE); - } else { - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - - hour = calendar->tm_hour; - update_hour = find_prev(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); - if (0 != res) goto return_result; - if (hour == update_hour) { - push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); - } else { - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - - day_of_week = calendar->tm_wday; - day_of_month = calendar->tm_mday; - update_day_of_month = find_prev_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); - if (0 != res) goto return_result; - if (day_of_month == update_day_of_month) { - push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); - } else { - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - - month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ - update_month = find_prev(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); - if (0 != res) goto return_result; - if (month != update_month) { - if (dot - calendar->tm_year > CRON_MAX_YEARS_DIFF) { - res = -1; - goto return_result; - } - res = do_prev(expr, calendar, dot); - if (0 != res) goto return_result; - } - goto return_result; - - return_result: - if (!resets || !empty_list) { - res = -1; - } - if (resets) { - cron_free(resets); - } - if (empty_list) { - cron_free(empty_list); - } - return res; -} - -time_t cron_prev(cron_expr* expr, time_t date) { - /* - The plan: - - 1 Round down to a whole second - - 2 If seconds match move on, otherwise find the next match: - 2.1 If next match is in the next minute then roll forwards - - 3 If minute matches move on, otherwise find the next match - 3.1 If next match is in the next hour then roll forwards - 3.2 Reset the seconds and go to 2 - - 4 If hour matches move on, otherwise find the next match - 4.1 If next match is in the next day then roll forwards, - 4.2 Reset the minutes and seconds and go to 2 - - ... - */ - if (!expr) return CRON_INVALID_INSTANT; - struct tm calval; - memset(&calval, 0, sizeof(struct tm)); - struct tm* calendar = cron_time(&date, &calval); - if (!calendar) return CRON_INVALID_INSTANT; - time_t original = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; - - /* calculate the previous occurrence */ - int res = do_prev(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - - /* check for a match, try from the next second if one wasn't found */ - time_t calculated = cron_mktime(calendar); - if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; - if (calculated == original) { - /* We arrived at the original timestamp - round up to the next whole second and try again... */ - res = add_to_field(calendar, CRON_CF_SECOND, -1); - if (0 != res) return CRON_INVALID_INSTANT; - res = do_prev(expr, calendar, calendar->tm_year); - if (0 != res) return CRON_INVALID_INSTANT; - } - - return cron_mktime(calendar); -} diff --git a/lib/ccronexpr/ccronexpr_test.c b/lib/ccronexpr/ccronexpr_test.c deleted file mode 100644 index c9ebf5f401..0000000000 --- a/lib/ccronexpr/ccronexpr_test.c +++ /dev/null @@ -1,416 +0,0 @@ -/* - * 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_test.cpp - * Author: alex - * - * Created on February 24, 2015, 9:36 AM - */ - -#include -#include -#include -#include -#include - -#include "ccronexpr.h" - -#define 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 12 - -#define INVALID_INSTANT ((time_t) -1) - -#define DATE_FORMAT "%Y-%m-%d_%H:%M:%S" - -#ifndef ARRAY_LEN -#define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) -#endif - -#ifdef CRON_TEST_MALLOC -static int cronAllocations = 0; -static int cronTotalAllocations = 0; -static int maxAlloc = 0; -void* cron_malloc(size_t n) { - cronAllocations++; - cronTotalAllocations++; - if (cronAllocations > maxAlloc) { - maxAlloc = cronAllocations; - } - return malloc(n); -} - -void cron_free(void* p) { - cronAllocations--; - free(p); -} -#endif - -#ifndef ANDROID -#ifndef _WIN32 -time_t timegm(struct tm* __tp); -#else /* _WIN32 */ -static time_t timegm(struct tm* tm) { - return _mkgmtime(tm); -} -#endif /* _WIN32 */ -#else /* ANDROID */ -static time_t timegm(struct tm * const t) { - /* time_t is signed on Android. */ - 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(t); - if (result < kTimeMin || result > kTimeMax) - return -1; - return result; -} -#endif - -/** - * uint8_t* replace char* for storing hit dates, set_bit and get_bit are used as handlers - */ -uint8_t cron_get_bit(uint8_t* rbyte, int idx); -void cron_set_bit(uint8_t* rbyte, int idx); -void cron_del_bit(uint8_t* rbyte, int idx); - -static int crons_equal(cron_expr* cr1, cron_expr* cr2) { - unsigned int i; - for (i = 0; i < ARRAY_LEN(cr1->seconds); i++) { - if (cr1->seconds[i] != cr2->seconds[i]) { - printf("seconds not equal @%d %02x != %02x", i, cr1->seconds[i], cr2->seconds[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->minutes); i++) { - if (cr1->minutes[i] != cr2->minutes[i]) { - printf("minutes not equal @%d %02x != %02x", i, cr1->minutes[i], cr2->minutes[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->hours); i++) { - if (cr1->hours[i] != cr2->hours[i]) { - printf("hours not equal @%d %02x != %02x", i, cr1->hours[i], cr2->hours[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->days_of_week); i++) { - if (cr1->days_of_week[i] != cr2->days_of_week[i]) { - printf("days_of_week not equal @%d %02x != %02x", i, cr1->days_of_week[i], cr2->days_of_week[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->days_of_month); i++) { - if (cr1->days_of_month[i] != cr2->days_of_month[i]) { - printf("days_of_month not equal @%d %02x != %02x", i, cr1->days_of_month[i], cr2->days_of_month[i]); - return 0; - } - } - for (i = 0; i < ARRAY_LEN(cr1->months); i++) { - if (cr1->months[i] != cr2->months[i]) { - printf("months not equal @%d %02x != %02x", i, cr1->months[i], cr2->months[i]); - return 0; - } - } - return 1; -} - -int one_dec_num(const char ch) { - switch (ch) { - case '0': - return 0; - case '1': - return 1; - case '2': - return 2; - case '3': - return 3; - case '4': - return 4; - case '5': - return 5; - case '6': - return 6; - case '7': - return 7; - case '8': - return 8; - case '9': - return 9; - default: - return -1; - } -} - -int two_dec_num(const char* first) { - return one_dec_num(first[0]) * 10 + one_dec_num(first[1]); -} - -int four_dec_num(const char *first) { - return ((one_dec_num(first[0]) * 1000) - + (one_dec_num(first[1]) * 100) - + (one_dec_num(first[2]) * 10) - + (one_dec_num(first[3]) * 1)); -} - -/* strptime is not available in msvc */ -/* 2012-07-01_09:53:50 */ -/* 0123456789012345678 */ -struct tm* poors_mans_strptime(const char* str) { - struct tm* cal = (struct tm*) malloc(sizeof(struct tm)); - assert(cal != NULL); - memset(cal, 0, sizeof(struct tm)); - cal->tm_year = four_dec_num(str) - 1900; - cal->tm_mon = two_dec_num(str + 5) - 1; - cal->tm_mday = two_dec_num(str + 8); - cal->tm_wday = 0; - cal->tm_yday = 0; - cal->tm_hour = two_dec_num(str + 11); - cal->tm_min = two_dec_num(str + 14); - cal->tm_sec = two_dec_num(str + 17); - return cal; -} - -void check_next(const char* pattern, const char* initial, const char* expected) { - const char* err = NULL; - cron_expr parsed; - cron_parse_expr(pattern, &parsed, &err); - - struct tm* calinit = poors_mans_strptime(initial); -#ifdef CRON_USE_LOCAL_TIME - time_t dateinit = mktime(calinit); -#else - time_t dateinit = timegm(calinit); -#endif - assert(-1 != dateinit); - time_t datenext = cron_next(&parsed, dateinit); -#ifdef CRON_USE_LOCAL_TIME - struct tm* calnext = localtime(&datenext); -#else - struct tm* calnext = gmtime(&datenext); -#endif - assert(calnext); - char* buffer = (char*) malloc(21); - memset(buffer, 0, 21); - strftime(buffer, 20, DATE_FORMAT, calnext); - if (0 != strcmp(expected, buffer)) { - printf("Pattern: %s\n", pattern); - printf("Initial: %s\n", initial); - printf("Expected: %s\n", expected); - printf("Actual: %s\n", buffer); - assert(0); - } - free(buffer); - free(calinit); -} - -void check_same(const char* expr1, const char* expr2) { - cron_expr parsed1; - cron_parse_expr(expr1, &parsed1, NULL); - cron_expr parsed2; - cron_parse_expr(expr2, &parsed2, NULL); - int res = crons_equal(&parsed1, &parsed2); - res = res; // to prevent warnings - assert(res); -} - -void check_calc_invalid() { - cron_expr parsed; - cron_parse_expr("0 0 0 31 6 *", &parsed, NULL); - struct tm * calinit = poors_mans_strptime("2012-07-01_09:53:50"); - time_t dateinit = timegm(calinit); - time_t res = cron_next(&parsed, dateinit); - res = res;; // to prevent warnings - assert(INVALID_INSTANT == res); - (void)res; // suppress warning - free(calinit); -} - -void check_expr_invalid(const char* expr) { - const char* err = NULL; - cron_expr test; - cron_parse_expr(expr, &test, &err); - assert(err); -} - -void test_expr() { -#ifdef CRON_USE_LOCAL_TIME - check_next("* 15 11 * * *", "2019-03-09_11:43:00", "2019-03-10_11:15:00"); -#else - check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00"); - check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); - check_next("0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00"); - check_next("0 */2 * * * *", "2012-07-01_09:00:00", "2012-07-01_09:02:00"); - check_next("0 */2 * * * *", "2013-07-01_09:00:00", "2013-07-01_09:02:00"); - check_next("0 */2 * * * *", "2018-09-14_14:24:00", "2018-09-14_14:26:00"); - check_next("0 */2 * * * *", "2018-09-14_14:25:00", "2018-09-14_14:26:00"); - check_next("0 */20 * * * *", "2018-09-14_14:24:00", "2018-09-14_14:40:00"); - check_next("* * * * * *", "2012-07-01_09:00:00", "2012-07-01_09:00:01"); - check_next("* * * * * *", "2012-12-01_09:00:58", "2012-12-01_09:00:59"); - check_next("10 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); - check_next("11 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:42:11"); - check_next("10 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:43:10"); - check_next("10-15 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); - check_next("10-15 * * * * *", "2012-12-01_21:42:14", "2012-12-01_21:42:15"); - check_next("0 * * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); - check_next("0 * * * * *", "2012-12-01_21:11:00", "2012-12-01_21:12:00"); - check_next("0 11 * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); - check_next("0 10 * * * *", "2012-12-01_21:11:00", "2012-12-01_22:10:00"); - check_next("0 0 * * * *", "2012-09-30_11:01:00", "2012-09-30_12:00:00"); - check_next("0 0 * * * *", "2012-09-30_12:00:00", "2012-09-30_13:00:00"); - check_next("0 0 * * * *", "2012-09-10_23:01:00", "2012-09-11_00:00:00"); - check_next("0 0 * * * *", "2012-09-11_00:00:00", "2012-09-11_01:00:00"); - check_next("0 0 0 * * *", "2012-09-01_14:42:43", "2012-09-02_00:00:00"); - check_next("0 0 0 * * *", "2012-09-02_00:00:00", "2012-09-03_00:00:00"); - check_next("* * * 10 * *", "2012-10-09_15:12:42", "2012-10-10_00:00:00"); - check_next("* * * 10 * *", "2012-10-11_15:12:42", "2012-11-10_00:00:00"); - check_next("0 0 0 * * *", "2012-09-30_15:12:42", "2012-10-01_00:00:00"); - check_next("0 0 0 * * *", "2012-10-01_00:00:00", "2012-10-02_00:00:00"); - check_next("0 0 0 * * *", "2012-08-30_15:12:42", "2012-08-31_00:00:00"); - check_next("0 0 0 * * *", "2012-08-31_00:00:00", "2012-09-01_00:00:00"); - check_next("0 0 0 * * *", "2012-10-30_15:12:42", "2012-10-31_00:00:00"); - check_next("0 0 0 * * *", "2012-10-31_00:00:00", "2012-11-01_00:00:00"); - check_next("0 0 0 1 * *", "2012-10-30_15:12:42", "2012-11-01_00:00:00"); - check_next("0 0 0 1 * *", "2012-11-01_00:00:00", "2012-12-01_00:00:00"); - check_next("0 0 0 1 * *", "2010-12-31_15:12:42", "2011-01-01_00:00:00"); - check_next("0 0 0 1 * *", "2011-01-01_00:00:00", "2011-02-01_00:00:00"); - check_next("0 0 0 31 * *", "2011-10-30_15:12:42", "2011-10-31_00:00:00"); - check_next("0 0 0 1 * *", "2011-10-30_15:12:42", "2011-11-01_00:00:00"); - check_next("* * * * * 2", "2010-10-25_15:12:42", "2010-10-26_00:00:00"); - check_next("* * * * * 2", "2010-10-20_15:12:42", "2010-10-26_00:00:00"); - check_next("* * * * * 2", "2010-10-27_15:12:42", "2010-11-02_00:00:00"); - check_next("55 5 * * * *", "2010-10-27_15:04:54", "2010-10-27_15:05:55"); - check_next("55 5 * * * *", "2010-10-27_15:05:55", "2010-10-27_16:05:55"); - check_next("55 * 10 * * *", "2010-10-27_09:04:54", "2010-10-27_10:00:55"); - check_next("55 * 10 * * *", "2010-10-27_10:00:55", "2010-10-27_10:01:55"); - check_next("* 5 10 * * *", "2010-10-27_09:04:55", "2010-10-27_10:05:00"); - check_next("* 5 10 * * *", "2010-10-27_10:05:00", "2010-10-27_10:05:01"); - check_next("55 * * 3 * *", "2010-10-02_10:05:54", "2010-10-03_00:00:55"); - check_next("55 * * 3 * *", "2010-10-03_00:00:55", "2010-10-03_00:01:55"); - check_next("* * * 3 11 *", "2010-10-02_14:42:55", "2010-11-03_00:00:00"); - check_next("* * * 3 11 *", "2010-11-03_00:00:00", "2010-11-03_00:00:01"); - check_next("0 0 0 29 2 *", "2007-02-10_14:42:55", "2008-02-29_00:00:00"); - check_next("0 0 0 29 2 *", "2008-02-29_00:00:00", "2012-02-29_00:00:00"); - check_next("0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); - check_next("0 0 7 ? * MON-FRI", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); - check_next("0 30 23 30 1/3 ?", "2010-12-30_00:00:00", "2011-01-30_23:30:00"); - check_next("0 30 23 30 1/3 ?", "2011-01-30_23:30:00", "2011-04-30_23:30:00"); - check_next("0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00"); -#endif -} - -void test_parse() { - - check_same("* * * 2 * *", "* * * 2 * ?"); - check_same("57,59 * * * * *", "57/2 * * * * *"); - check_same("1,3,5 * * * * *", "1-6/2 * * * * *"); - check_same("* * 4,8,12,16,20 * * *", "* * 4/4 * * *"); - check_same("* * * * * 0-6", "* * * * * TUE,WED,THU,FRI,SAT,SUN,MON"); - check_same("* * * * * 0", "* * * * * SUN"); - check_same("* * * * * 0", "* * * * * 7"); - check_same("* * * * 1-12 *", "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *"); - check_same("* * * * 2 *", "* * * * Feb *"); - check_same("* * * * 1 *", "* * * * 1 *"); - - check_expr_invalid("77 * * * * *"); - check_expr_invalid("44-77 * * * * *"); - check_expr_invalid("* 77 * * * *"); - check_expr_invalid("* 44-77 * * * *"); - check_expr_invalid("* * 27 * * *"); - check_expr_invalid("* * 23-28 * * *"); - check_expr_invalid("* * * 45 * *"); - check_expr_invalid("* * * 28-45 * *"); - check_expr_invalid("0 0 0 25 13 ?"); - check_expr_invalid("0 0 0 25 0 ?"); - check_expr_invalid("0 0 0 32 12 ?"); - check_expr_invalid("* * * * 11-13 *"); - check_expr_invalid("-5 * * * * *"); - check_expr_invalid("3-2 */5 * * * *"); - check_expr_invalid("/5 * * * * *"); - check_expr_invalid("*/0 * * * * *"); - check_expr_invalid("*/-0 * * * * *"); - check_expr_invalid("* 1 1 0 * *"); -} - -void test_bits() { - - uint8_t testbyte[8]; - memset(testbyte, 0, 8); - int err = 0; - err = err; // to prevent warnings - - int i; - - for (i = 0; i <= 63; i++) { - cron_set_bit(testbyte, i); - if (!cron_get_bit(testbyte, i)) { - printf("Bit set error! Bit: %d!\n", i); - err = 1; - } - cron_del_bit(testbyte, i); - if (cron_get_bit(testbyte, i)) { - printf("Bit clear error! Bit: %d!\n", i); - err = 1; - } - assert(!err); - (void)err; // suppress "not used" warning - } - - for (i = 0; i < 12; i++) { - cron_set_bit(testbyte, i); - } - if (testbyte[0] != 0xff) { - err = 1; - } - if (testbyte[1] != 0x0f) { - err = 1; - } - - assert(!err); -} - -/* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ -#ifdef CRON_TEST_MALLOC -void test_memory() { - cron_expr cron; - const char* err; - - cron_parse_expr("* * * * * *", &cron, &err); - if (cronAllocations != 0) { - printf("Allocations != 0 but %d", cronAllocations); - assert(0); - } - printf("Allocations: total: %d, max: %d", cronTotalAllocations, maxAlloc); -} -#endif - -int main() { - - test_bits(); - - test_expr(); - test_parse(); - check_calc_invalid(); - #ifdef CRON_TEST_MALLOC - test_memory(); /* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ - #endif - printf("\nAll OK!"); - return 0; -} - diff --git a/lib/ccronexpr/library.json b/lib/ccronexpr/library.json deleted file mode 100644 index 8828bcbe5f..0000000000 --- a/lib/ccronexpr/library.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "ccronexpr", - "version": "1.0.0", - "description": "Cron expression parsing in ANSI C", - "repository": - { - "type": "git", - "url": "https://github.com/staticlibs/ccronexpr" - }, - "frameworks": "arduino", - "platforms": [ - "espressif8266","espressif32","espressif32s2" - ] -} diff --git a/lib/ccronexpr/library.properties b/lib/ccronexpr/library.properties deleted file mode 100644 index ac3a250a49..0000000000 --- a/lib/ccronexpr/library.properties +++ /dev/null @@ -1,8 +0,0 @@ -name=ccronexpr -version=1.0.0 -sentence=Cron expression parsing in ANSI C -paragraph= -url=https://github.com/staticlibs/ccronexpr -architectures=esp8266,esp32,esp32s2 -author= -maintainer= diff --git a/lib/supertinycron/.github/workflows/build.yml b/lib/supertinycron/.github/workflows/build.yml new file mode 100644 index 0000000000..9f90690a7a --- /dev/null +++ b/lib/supertinycron/.github/workflows/build.yml @@ -0,0 +1,60 @@ +name: Build + +on: + push: + paths-ignore: [ "*.md" ] + pull_request: + paths-ignore: [ "*.md" ] + +env: + BUILD_TYPE: Debug + +jobs: + build: + name: Build + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + cron_use_local_time: + - "" + - "-DCRON_USE_LOCAL_TIME=1" + char_c_flag: + - "" + - "-fsigned-char" + - "-funsigned-char" + + exclude: + - os: ubuntu-latest + char_c_flag: "" + - os: windows-latest + char_c_flag: "-fsigned-char" + - os: windows-latest + char_c_flag: "-funsigned-char" + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE ${{ matrix.cron_use_local_time }} + env: + CFLAGS: ${{ matrix.char_c_flag }} + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE + + - name: Test + working-directory: ${{github.workspace}}/build + shell: bash + run: ctest --extra-verbose -C $BUILD_TYPE diff --git a/lib/supertinycron/.gitignore b/lib/supertinycron/.gitignore new file mode 100644 index 0000000000..4bc0b34b3d --- /dev/null +++ b/lib/supertinycron/.gitignore @@ -0,0 +1,8 @@ +build/ +cmake-build*/ +/nbproject +.idea/ +*.o +supertinycron +ccronexpr_test +libccronexpr.so diff --git a/lib/ccronexpr/.travis.yml b/lib/supertinycron/.travis.yml similarity index 52% rename from lib/ccronexpr/.travis.yml rename to lib/supertinycron/.travis.yml index f5843a53d0..9b1630dbd2 100644 --- a/lib/ccronexpr/.travis.yml +++ b/lib/supertinycron/.travis.yml @@ -25,12 +25,18 @@ compiler: - clang script: - - $CC ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out - - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out - - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out - - $CC -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && TZ="America/Toronto" ./a.out - - $CXX -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && TZ="America/Toronto" ./a.out - - $CXX -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && TZ="America/Toronto" ./a.out + - $CC ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -o a.out && ./a.out + - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -o a.out && ./a.out + - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && ./a.out + - $CC -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -o a.out && TZ="America/Toronto" ./a.out + - $CXX -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -o a.out && TZ="America/Toronto" ./a.out + - $CXX -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && TZ="America/Toronto" ./a.out + - $CC ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c89 -o a.out && ./a.out + - $CXX ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -o a.out && ./a.out + - $CXX ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && ./a.out + - $CC -DCRON_USE_LOCAL_TIME ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c89 -o a.out && TZ="America/Toronto" ./a.out + - $CXX -DCRON_USE_LOCAL_TIME ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -o a.out && TZ="America/Toronto" ./a.out + - $CXX -DCRON_USE_LOCAL_TIME ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && TZ="America/Toronto" ./a.out notifications: email: diff --git a/lib/supertinycron/CMakeLists.txt b/lib/supertinycron/CMakeLists.txt new file mode 100644 index 0000000000..64bad38f56 --- /dev/null +++ b/lib/supertinycron/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.0) +project(ccronexpr) + +# Library +add_library(ccronexpr STATIC ccronexpr.c) +target_include_directories(ccronexpr PUBLIC .) + +# Supertinycron binary +#add_executable(supertinycron supertinycron.c) +#target_link_libraries(supertinycron ccronexpr) + +if (ESP_PLATFORM) + target_compile_definitions(ccronexpr PRIVATE ESP_PLATFORM=1) + set(CRON_DISABLE_TESTING ON) # disable tests automatically +endif () + +if (CRON_USE_LOCAL_TIME) + target_compile_definitions(ccronexpr PUBLIC CRON_USE_LOCAL_TIME=1) +endif () + +if (CRON_COMPILE_AS_CXX) + target_compile_definitions(ccronexpr PUBLIC CRON_COMPILE_AS_CXX=1) +endif () + +if (MSVC) + # Strict compilation + target_compile_options(ccronexpr PRIVATE /W4 /WX) + # But ignore _s functions + target_compile_definitions(ccronexpr PRIVATE _CRT_SECURE_NO_WARNINGS) +else () + # Strict compilation + target_compile_options(ccronexpr PRIVATE -ansi -Wall -Wextra -Werror -Wshadow -Wpointer-arith -Wcast-qual -Wconversion -Wno-unused-parameter -pedantic-errors) +endif () + +# Tests +if (NOT CRON_DISABLE_TESTING) + enable_testing() + add_subdirectory(test) +endif () diff --git a/lib/supertinycron/ESP-IDF.md b/lib/supertinycron/ESP-IDF.md new file mode 100644 index 0000000000..9bcccda047 --- /dev/null +++ b/lib/supertinycron/ESP-IDF.md @@ -0,0 +1,22 @@ +Using library from ESP-IDF +========================== + +Note that only local time mode is currently supported on ESP. + +Add library as a submodule, but outside components directory: + +```shell +git submodule add https://github.com/mdvorak/ccronexpr.git libs/ccronexpr +``` + +Reference it in the project CMakeLists.txt: +```cmake +set(CRON_USE_LOCAL_TIME ON) +add_subdirectory(libs/ccronexpr) +``` + +Finally, link it to your component: +```cmake +idf_component_register(...) +target_link_libraries(${COMPONENT_LIB} PUBLIC ccronexpr) +``` diff --git a/lib/ccronexpr/LICENSE.txt b/lib/supertinycron/LICENSE.txt similarity index 100% rename from lib/ccronexpr/LICENSE.txt rename to lib/supertinycron/LICENSE.txt diff --git a/lib/supertinycron/Makefile b/lib/supertinycron/Makefile new file mode 100644 index 0000000000..84fe6b4633 --- /dev/null +++ b/lib/supertinycron/Makefile @@ -0,0 +1,35 @@ +CC = musl-gcc +VERSION := $(shell git rev-parse --short HEAD)-$(shell date +%Y%m%d%H%M%S) +UNCOMMITTED_CHANGES := $(shell git status --porcelain) +ifeq ($(strip $(UNCOMMITTED_CHANGES)),) + VERSION := $(VERSION) +else + VERSION := $(VERSION)-dev-build +endif +CFLAGS = -Wextra -std=c89 -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,-z,norelro -Wno-unused-parameter -static -DVERSION=\"$(VERSION)\" -DCRON_USE_LOCAL_TIME +CFLAGS = -Wextra -std=c89 -s -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,-z,norelro -static -DVERSION=\"$(VERSION)\" -DCRON_USE_LOCAL_TIME -fPIC +SOURCES = supertinycron.c ccronexpr.c ccronexpr_test.c +OBJECTS = supertinycron.o ccronexpr.o +OBJECTS_TEST = ccronexpr_test.o ccronexpr.o +OBJECTS_SHARED = ccronexpr.o +EXECUTABLE = supertinycron +EXECUTABLE_TEST = ccronexpr_test +SHARED = libccronexpr.so +LDFLAGS = -shared -Wl,-soname,$(SHARED) -fPIC + +all: $(EXECUTABLE) $(EXECUTABLE_TEST) + +shared: $(SHARED) + +$(SHARED): $(OBJECTS_SHARED) + $(CC) $(LDFLAGS) $^ -o $@ + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(CFLAGS) $(OBJECTS) -o $@ + upx -9 $(EXECUTABLE) + +$(EXECUTABLE_TEST): $(OBJECTS_TEST) + $(CC) $(CFLAGS) $(OBJECTS_TEST) -o $@ + +clean: + rm -f $(OBJECTS) $(OBJECTS_TEST) $(EXECUTABLE) $(EXECUTABLE_TEST) $(SHARED) diff --git a/lib/supertinycron/README.md b/lib/supertinycron/README.md new file mode 100644 index 0000000000..e0a351e691 --- /dev/null +++ b/lib/supertinycron/README.md @@ -0,0 +1,262 @@ + +supertinycron +============= + +A very small replacement for cron. Particularly useful within containers and for distributing cron tasks alongside a project as a standalone file. + +Installing +---------- + +```bash +make +sudo mv supertinycron /usr/local/bin/ +``` + +Usage +----- + +``` +supertinycron [expression] [command...] +``` + +Supertinycron can be conveniently used in your scripts interpreter line: +```bash +#!/usr/local/bin/supertinycron */5 * * * * * * /bin/sh +echo "Current time: $(date)" +``` + +Or invoked directly via commandline: +```bash +$ supertinycron '*/5 * * * * * *' /bin/echo hello +``` + +Implementation +-------------- + +This reference is based on: +- [1] [gorhill's cronexpr on GitHub](https://github.com/gorhill/cronexpr/blob/master/README.md) +- [2] [Quartz Scheduler's CronExpression](https://www.javadoc.io/doc/org.quartz-scheduler/quartz/latest/org/quartz/CronExpression.html) +- [3] [ccronexpr by staticlibs on GitHub](https://github.com/staticlibs/ccronexpr/blob/master/README.md) +- [4] [ccronexpr by mdvorak on GitHub](https://github.com/mdvorak/ccronexpr/blob/main/README.md) +- [5] [Wikipedia on CRON Expression](https://en.wikipedia.org/wiki/Cron#CRON_expression) + +``` +Field name Mandatory? Allowed values Allowed special characters +---------- ---------- -------------- ------------------------- +Second No 0-59 * / , - L +Minute Yes 0-59 * / , - +Hour Yes 0-23 * / , - +Day of month Yes 1-31 * / , - L W +Month Yes 1-12 or JAN-DEC * / , - +Day of week Yes 0-6 or SUN-SAT * / , - L # +Year No 1970–2199 * / , - +``` + +**Note:** In the 'Day of week' field, both 0 and 7 represent SAT, as referenced by [crontab's man page](http://linux.die.net/man/5/crontab#). The 'Year' field spans to 2199 as per [2], which differs from [1] where it's up to 2099. When `*` is used for year it should function well above year 2199. Depending on libc, it may function well above year 10000. + +### Special Characters + +#### Asterisk `*` +The asterisk indicates that the cron expression matches all values of the field. For instance, an asterisk in the 'Month' field matches every month. + +#### Hyphen `-` +Hyphens define ranges. For instance, `2000-2010` in the 'Year' field matches every year from 2000 to 2010, inclusive. + +#### Slash `/` +Slashes specify increments within ranges. For example, `3-59/15` in the 'Minute' field matches the third minute of the hour and every 15 minutes thereafter. The form `*/...` is equivalent to `first-last/...`, representing an increment over the full range of the field. + +#### Comma `,` +Commas separate items in a list. For instance, `MON,WED,FRI` in the 'Day of week' field matches Mondays, Wednesdays, and Fridays. + +#### `L` +The character `L` stands for "last". In the 'Day of week' field, `5L` denotes the last Friday of a given month. In the 'Day of month' field, it represents the last day of the month. + +- Using `L` alone in the 'Day of week' field is equivalent to `0` or `SAT`. Hence, expressions `* * * * * L *` and `* * * * * 0 *` are the same. + +- When followed by another value in the 'Day of week' field, like `6L`, it signifies the last Friday of the month. + +- 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 cycle indefinitely, because it will not be able to find any leap second! + +When using 'L', avoid specifying lists or ranges to prevent ambiguous results. + +#### `W` +The `W` character is exclusive to the 'Day of month' field. It indicates the closest business day (Monday-Friday) to the given day. For example, `15W` means the nearest business day to the 15th of the month. If you set 1W for the day-of-month and the 1st falls on a Saturday, the trigger activates on Monday the 3rd, since it respects the month's day boundaries and won't skip over them. Similarly, at the end of the month, the behavior ensures it doesn't "jump" over the boundary to the following month. + +The `W` character can also pair with `L` (as `LW`), signifying the last business day of the month. Alone, it's equivalent to the range `1-5`, making the expressions `* * * W * * *` and `* * * * * 1-5 *` identical. This interpretation differs from [1,2]. + +#### Hash `#` +The `#` character is only for the 'Day of week' field and should be followed by a number between one and five, or their negative values. It lets you specify constructs like "the second Friday" of a month. + +For example, `6#3` means the third Friday of the month. Note that if you use `#5` and there isn't a fifth occurrence of that weekday in the month, no firing occurs for that month. Using the '#' character requires a single expression in the 'Day of week' field. + +Negative nth values are also valid. For instance, `6#-1` is equivalent to `6L`. + +#### Known limitation + +1. Leap seconds can be prefixed with multiple `L` symbols: `LLLL60` without issuing errors. +2. Ordinals `JAN`...`DEC` and `SUN`...`SAT` are processed in all fields without issuing errors. +3. Errors from lexical analyzers are masked by parser errors. +4. Multiple `#` segments: `1#1,3#3,5#5` are not allowed. + +Predefined cron expressions +--------------------------- +(Copied from , with text modified according to this implementation) + + Entry Description Equivalent to + @annually Run once a year at midnight in the morning of January 1 0 0 0 1 1 * + @yearly Run once a year at midnight in the morning of January 1 0 0 0 1 1 * + @monthly Run once a month at midnight in the morning of the first of the month 0 0 0 1 * * + @weekly Run once a week at midnight in the morning of Sunday 0 0 0 * * 0 + @daily Run once a day at midnight 0 0 0 * * * + @hourly Run once an hour at the beginning of the hour 0 0 * * * * + @minutely Run once a minute at the beginning of minute 0 * * * * * + @secondly Run once every second * * * * * * * + @reboot Not supported + +Note that `@minutely` and `@secondly` are not standard. + +Other details +------------- +* If only five fields are present, the Year and Second fields are omitted. The omitted Year and Second are `*` and `0` respectively. +* If only six fields are present, the Year field is omitted. The omitted Year is set to `*`. Note that this is different from [1] which has Second field omitted in this case and [2] which doesn't allow five fields. +* Only proper expressions are guaranteed to work. +* Cron doesn't decide calendar, it follows it. It doesn't and it should not disallow combinations like 31st April or 30rd February. Not only that these dates hisctorically happened, but they may very well happen based on timezone configuration. Within reasonable constrains, it should work under changed conditions. + +Config +------ + +TinyCron can be configured by setting the below environmental variables to a non-empty value: + +Variable | Description +--- | --- +TINYCRON_VERBOSE | Enable verbose output + + +Cron expression parsing in ANSI C +================================= + +[![Build](https://github.com/mdvorak/ccronexpr/actions/workflows/build.yml/badge.svg)](https://github.com/mdvorak/ccronexpr/actions/workflows/build.yml) + +Given a cron expression and a date, you can get the next date which satisfies the cron expression. + +Supports cron expressions with `seconds` field. Based on implementation of [CronSequenceGenerator](https://github.com/spring-projects/spring-framework/blob/babbf6e8710ab937cd05ece20270f51490299270/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java) from Spring Framework. + +Compiles and should work on Linux (GCC/Clang), Mac OS (Clang), Windows (MSVC), Android NDK, iOS and possibly on other platforms with `time.h` support. + +Supports compilation in C (89) and in C++ modes. + +Usage example +------------- + + #include "ccronexpr.h" + + cron_expr expr; + const char* err = NULL; + memset(&expr, 0, sizeof(expr)); + cron_parse_expr("0 */2 1-4 * * *", &expr, &err); + if (err) ... /* invalid expression */ + time_t cur = time(NULL); + time_t next = cron_next(&expr, cur); + + +Compilation and tests run examples +---------------------------------- + + gcc ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -o a.out && ./a.out + g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -o a.out && ./a.out + g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && ./a.out + + clang ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -o a.out && ./a.out + clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -o a.out && ./a.out + clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && ./a.out + + cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS && ccronexpr.exe + + + gcc ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c89 -o a.out && ./a.out + g++ ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -o a.out && ./a.out + g++ ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && ./a.out + + clang ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c89 -o a.out && ./a.out + clang++ ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -o a.out && ./a.out + clang++ ccronexpr.c supertinycron.c -I. -Wall -Wextra -std=c++11 -DCRON_COMPILE_AS_CXX -o a.out && ./a.out + + cl ccronexpr.c supertinycron.c /W4 /D_CRT_SECURE_NO_WARNINGS && supertinycron.exe + +Examples of supported expressions +--------------------------------- + +Expression, input date, next date: + + "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00" + "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00" + "0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00" + "0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00" + +See more examples in [tests](https://github.com/staticlibs/ccronexpr/blob/a1343bc5a546b13430bd4ac72f3b047ac08f8192/ccronexpr_test.c#L251). + +Timezones +--------- + +This implementation does not support explicit timezones handling. By default, all dates are +processed as UTC (GMT) dates without timezone information. + +To use local dates (current system timezone) instead of GMT compile with `-DCRON_USE_LOCAL_TIME`, example: + + gcc -DCRON_USE_LOCAL_TIME ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -o a.out && TZ="America/Toronto" ./a.out + +Years +----- + +To disable Year field use `-DCRON_DISABLE_YEARS`. This will lower memory footriprint by 29 bytes for `cron_expr`. It will still accept year field, but field will not be validated and it will be triggered every year. + +License information +------------------- + +This project is released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +Changelog +--------- + +**2023** +* major extension of supported expressions +* command line tool + +**2022** + +* added CMake build +* added GitHub Workflow for continuous testing +* fixed type casts to support `-Wconvert` +* added tests for cron_prev and leap years +* fixed tests to work with `CRON_USE_LOCAL_TIME` +* added [ESP-IDF](./ESP-IDF.md) usage guide + +**2019-03-27** + + * `CRON_USE_LOCAL_TIME` usage fixes + +**2018-05-23** + + * merged [#8](https://github.com/staticlibs/ccronexpr/pull/8) + * merged [#9](https://github.com/staticlibs/ccronexpr/pull/9) + * minor cleanups + +**2018-01-27** + + * merged [#6](https://github.com/staticlibs/ccronexpr/pull/6) + * updated license file (to the one parse-able by github) + +**2017-09-24** + + * merged [#4](https://github.com/staticlibs/ccronexpr/pull/4) + +**2016-06-17** + + * use thread-safe versions of `gmtime` and `localtime` + +**2015-02-28** + + * initial public version + diff --git a/lib/ccronexpr/appveyor.yml b/lib/supertinycron/appveyor.yml similarity index 73% rename from lib/ccronexpr/appveyor.yml rename to lib/supertinycron/appveyor.yml index 379b32fa74..42fed44b5e 100644 --- a/lib/ccronexpr/appveyor.yml +++ b/lib/supertinycron/appveyor.yml @@ -23,6 +23,10 @@ build_script: - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS /Feccronexpr_32.exe - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS /Feccronexpr_64.exe +# - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" +# - cl ccronexpr.c supertinycron.c /W4 /D_CRT_SECURE_NO_WARNINGS /Fesupertinycron_32.exe +# - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" +# - cl ccronexpr.c supertinycron.c /W4 /D_CRT_SECURE_NO_WARNINGS /Fesupertinycron_64.exe test_script: - ccronexpr_32.exe diff --git a/lib/supertinycron/ccronexpr.c b/lib/supertinycron/ccronexpr.c new file mode 100644 index 0000000000..6ae027d1a5 --- /dev/null +++ b/lib/supertinycron/ccronexpr.c @@ -0,0 +1,883 @@ +/* + * 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: ccronexpr.c + * Author: alex + * + * Created on February 24, 2015, 9:35 AM + */ + +#include +#include +#include +#include +#include + +#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 +#define CRON_MAX_DAYS_OF_WEEK 7 +#define CRON_MAX_MONTHS 12 +#define CRON_MIN_YEARS 1970 +#define CRON_MAX_YEARS 2200 +#define CRON_MAX_YEARS_DIFF 4 + +#define YEAR_OFFSET 1900 +#define DAY_SECONDS 24 * 60 * 60 +#define WEEK_DAYS 7 + +#define CRON_CF_SECOND 0 +#define CRON_CF_MINUTE 1 +#define CRON_CF_HOUR_OF_DAY 2 +#define CRON_CF_DAY_OF_WEEK 3 +#define CRON_CF_DAY_OF_MONTH 4 +#define CRON_CF_MONTH 5 +#define CRON_CF_YEAR 6 +#define CRON_CF_NEXT 7 + +#define CRON_CF_ARR_LEN 7 + +static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; +#define CRON_DAYS_ARR_LEN 7 +static const char* const MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#define CRON_MONTHS_ARR_LEN 13 + +/** + * Time functions from standard library. + * This part defines: cron_mktime: create time_t from tm + * cron_time: create tm from time_t + */ + +/* forward declarations for platforms that may need them */ +/* can be hidden in time.h */ +#if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ESP_PLATFORM) && !defined(ANDROID) && !defined(TARGET_LIKE_MBED) +struct tm *gmtime_r(const time_t *timep, struct tm *result); +time_t timegm(struct tm* __tp); +struct tm *localtime_r(const time_t *timep, struct tm *result); +#endif /* PLEASE CHECK _WIN32 AND ANDROID NEEDS FOR THESE DECLARATIONS */ +#ifdef __MINGW32__ +/* To avoid warning when building with mingw */ +time_t _mkgmtime(struct tm* tm); +#endif /* __MINGW32__ */ + +/* function definitions */ +#ifndef CRON_USE_LOCAL_TIME + +static time_t cron_mktime_gm(struct tm* tm) { +#if defined(_WIN32) +/* http://stackoverflow.com/a/22557778 */ + return _mkgmtime(tm); +#elif defined(__AVR__) +/* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ + return mk_gmtime(tm); +#elif defined(ESP8266) || defined(ESP_PLATFORM) || defined(TARGET_LIKE_MBED) + +#error "timegm() is not supported on the ESP platform, please use this library with CRON_USE_LOCAL_TIME" + +#elif defined(ANDROID) && !defined(__LP64__) + /* 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; +#else + return timegm(tm); +#endif +} + +static struct tm* cron_time_gm(time_t* date, struct tm* out) { +#if defined(__MINGW32__) + (void)(out); /* To avoid unused warning */ + return gmtime(date); +#elif defined(_WIN32) + errno_t err = gmtime_s(out, date); + return 0 == err ? out : NULL; +#elif defined(__AVR__) + /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ + gmtime_r(date, out); + return out; +#else + return gmtime_r(date, out); +#endif +} + +#else + +static time_t cron_mktime_local(struct tm* tm) { + tm->tm_isdst = -1; + return mktime(tm); +} + +static struct tm* cron_time_local(time_t* date, struct tm* out) { +#if defined(_WIN32) + errno_t err = localtime_s(out, date); + return 0 == err ? out : NULL; +#elif defined(__AVR__) + /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ + localtime_r(date, out); + return out; +#else + return localtime_r(date, out); +#endif +} + +#endif + +/* Defining 'cron_' time functions to use use UTC (default) or local time */ +#ifndef CRON_USE_LOCAL_TIME +time_t cron_mktime(struct tm* tm) { + return cron_mktime_gm(tm); +} + +struct tm* cron_time(time_t* date, struct tm* out) { + return cron_time_gm(date, out); +} + +#else /* CRON_USE_LOCAL_TIME */ +time_t cron_mktime(struct tm* tm) { + return cron_mktime_local(tm); +} + +struct tm* cron_time(time_t* date, struct tm* out) { + return cron_time_local(date, out); +} + +#endif /* CRON_USE_LOCAL_TIME */ + +#define reset_all_min(calendar, fields) reset_all(reset_min, calendar, fields); +#define reset_all_max(calendar, fields) reset_all(reset_max, calendar, fields); +#define PARSE_ERROR(message) { context->err = message; goto error; } +#define CRON_ERROR(message) { *error = message; goto error; } +#define TOKEN_COMPARE(context, token) if (context->err) goto error; if (token == context->type) token_next(context); else goto compare_error; +#define GET_BYTE(idx) (uint8_t) (idx / 8) +#define GET_BIT(idx) (uint8_t) (idx % 8) + +void cron_set_bit(uint8_t* rbyte, int idx) { + rbyte[GET_BYTE(idx)] |= (uint8_t)(1 << GET_BIT(idx)); +} + +void cron_del_bit(uint8_t* rbyte, int idx) { + rbyte[GET_BYTE(idx)] &= (uint8_t)~(1 << GET_BIT(idx)); +} + +uint8_t cron_get_bit(const uint8_t* rbyte, int idx) { + return (rbyte[GET_BYTE(idx)] & (1 << GET_BIT(idx))) ? 1 : 0; +} + +static int next_set_bit(uint8_t* bits, int max, int from_index, int* notfound) { + int i; + if (!bits) goto error; + for (i = from_index; i < max; i++) if (cron_get_bit(bits, i)) return i; + error: *notfound = 1; return 0; +} + +static int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) { + int i; + if (!bits) goto error; + for (i = from_index; i >= to_index; i--) if (cron_get_bit(bits, i)) return i; + error: *notfound = 1; return 0; +} + +static int* get_field_ptr(struct tm* calendar, int field) { + switch (field) { + case CRON_CF_SECOND: return &calendar->tm_sec; + case CRON_CF_MINUTE: return &calendar->tm_min; + case CRON_CF_HOUR_OF_DAY: return &calendar->tm_hour; + case CRON_CF_DAY_OF_WEEK: return &calendar->tm_wday; + case CRON_CF_DAY_OF_MONTH: return &calendar->tm_mday; + case CRON_CF_MONTH: return &calendar->tm_mon; + case CRON_CF_YEAR: return &calendar->tm_year; + default: return NULL; /* unknown field */ + } +} + +static int last_day_of_month(int month, int year) { + struct tm cal; + time_t t; + memset(&cal, 0, sizeof(struct tm)); + cal.tm_mon = month + 1; + cal.tm_year = year; + t = cron_mktime(&cal); + return cron_time(&t, &cal)->tm_mday; +} + +static int set_field(struct tm* calendar, int field, int val) { + int* field_ptr = get_field_ptr(calendar, field); + int last_mday; + if (!field_ptr || !calendar) return 1; + *field_ptr = val; + if (field == CRON_CF_MONTH) { + last_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year); + if (calendar->tm_mday > last_mday) calendar->tm_mday = last_mday; + } + return CRON_INVALID_INSTANT == cron_mktime(calendar) ? 1 : 0; +} + +static int add_to_field(struct tm* calendar, int field, int val) { + int* field_ptr; + if (CRON_CF_DAY_OF_WEEK == field) field = CRON_CF_DAY_OF_MONTH; + field_ptr = get_field_ptr(calendar, field); + if (!field_ptr || !calendar) return 1; + *field_ptr += val; + return CRON_INVALID_INSTANT == cron_mktime(calendar) ? 1 : 0; +} + +/** + * Reset the calendar setting all the fields provided to zero. + */ +static int reset_min(struct tm* calendar, int field) { + return set_field(calendar, field, field == CRON_CF_DAY_OF_MONTH); +} + +/** + * Reset the calendar setting all the fields provided to zero. + */ +static int reset_max(struct tm* calendar, int field) { + int value; + if (!calendar) return 1; + switch (field) { + case CRON_CF_SECOND: value = CRON_MAX_SECONDS-1; break; + case CRON_CF_MINUTE: value = CRON_MAX_MINUTES-1; break; + case CRON_CF_HOUR_OF_DAY: value = CRON_MAX_HOURS-1; break; + case CRON_CF_DAY_OF_WEEK: value = CRON_MAX_DAYS_OF_WEEK-1; break; + case CRON_CF_DAY_OF_MONTH: value = last_day_of_month(calendar->tm_mon, calendar->tm_year); break; + case CRON_CF_MONTH: value = CRON_MAX_MONTHS-1; break; + /* I don't think this is supposed to happen ... */ + /* fprintf(stderr, "reset CRON_CF_YEAR\n"); */ + case CRON_CF_YEAR: return 1; + default: return 1; /* unknown field */ + } + return set_field(calendar, field, value); +} + +static int last_weekday_of_month(int month, int year) { + struct tm cal; + time_t t; + memset(&cal, 0, sizeof(struct tm)); + cal.tm_mon = month + 1; /* next month */ + cal.tm_year = year; /* years since 1900 */ + t = cron_mktime(&cal); + + /* If the last day of the month is a Saturday (6) or Sunday (0), decrement the day. + * But it is shifted to (5) and (6). */ + while (cron_time(&t, &cal)->tm_wday == 6 || cron_time(&t, &cal)->tm_wday == 0) t -= DAY_SECONDS; /* subtract a day */ + return cron_time(&t, &cal)->tm_mday; +} + +static int closest_weekday(int day_of_month, int month, int year) { + struct tm cal; + time_t t; + int wday; + memset(&cal, 0, sizeof(struct tm)); + cal.tm_mon = month; /* given month */ + cal.tm_mday = day_of_month + 1; + cal.tm_year = year; /* years since 1900 */ + t = cron_mktime(&cal); + + wday = cron_time(&t, &cal)->tm_wday; + + /* If it's a Sunday */ + if (wday == 0) { + /* If it's the last day of the month, go to the previous Friday */ + if (day_of_month + 1 == last_day_of_month(month, year)) t -= 2 * DAY_SECONDS; + else t += DAY_SECONDS; /* go to the next Monday */ + /* If it's a Saturday */ + } else if (wday == 6) { + /* If it's the first day of the month, go to the next Monday */ + if (day_of_month == 0) t += 2 * DAY_SECONDS; + else t -= DAY_SECONDS; /* go to the previous Friday */ + } + + /* If it's a weekday */ + return cron_time(&t, &cal)->tm_mday; +} + +static int reset_all(int (*fn)(struct tm* calendar, int field), struct tm* calendar, uint8_t* fields) { + int i; + int res = 0; + if (!calendar || !fields) return 1; + for (i = 0; i < CRON_CF_ARR_LEN; i++) if (cron_get_bit(fields, i)) { + res = fn(calendar, i); + if (0 != res) return res; + } + return 0; +} + +typedef enum { T_ASTERISK, T_QUESTION, T_NUMBER, T_COMMA, T_SLASH, T_L, T_W, T_HASH, T_MINUS, T_WS, T_EOF, T_INVALID } TokenType; +typedef struct { const char* input; TokenType type; cron_expr* target; int field_type, value, min, max, offset, fix_dow; uint8_t* field; char* err; } ParserContext; + +static int compare_strings(const char* str1, const char* str2, size_t len) { + size_t i; + for (i = 0; i < len; i++) if (toupper(str1[i]) != str2[i]) return str1[i] - str2[i]; + return 0; +} + +static int match_ordinals(const char* str, const char* const* arr, size_t arr_len) { + size_t i; + for (i = 0; i < arr_len; i++) if (!compare_strings(str, arr[i], strlen(arr[i]))) return (int)i; + return -1; +} + +static int count_fields(const char* str, char del) { + size_t count = 0; + if (!str) return -1; + while ((str = strchr(str, del)) != NULL) { + count++; + do str++; while (del == *str); + } + return (int)count + 1; +} + +static void token_next(ParserContext* context) { + const char *input = context->input; + context->type = T_INVALID; + context->value = 0; + if (*context->input == '\0') context->type = T_EOF; + else if (isspace(*context->input)) { + do ++context->input; while (isspace(*context->input)); + context->type = T_WS; + } else if (isdigit(*context->input)) { + do { + context->value = context->value * 10 + (*context->input - '0'); + ++context->input; + } while (isdigit(*context->input)); + context->type = T_NUMBER; + } else if (isalpha(*input)) { + do ++input; while (isalpha(*input)); + context->value = match_ordinals(context->input, DAYS_ARR, CRON_DAYS_ARR_LEN); + if (context->value < 0) context->value = match_ordinals(context->input, MONTHS_ARR, CRON_MONTHS_ARR_LEN); + if (context->value < 0) goto rest; + context->input = input; + context->type = T_NUMBER; + } else { + rest: switch (*context->input) { + case '*': context->type = T_ASTERISK; break; + case '?': context->type = T_QUESTION; break; + case ',': context->type = T_COMMA; break; + case '/': context->type = T_SLASH; break; + case 'L': context->type = T_L; break; + case 'W': context->type = T_W; break; + case '#': context->type = T_HASH; break; + case '-': context->type = T_MINUS; break; + } + ++context->input; + } + if (T_INVALID == context->type) context->err = "Invalid token"; +} + +static int Number(ParserContext* context) { + int value = 0; + switch (context->type) { + case T_MINUS: + token_next(context); + if (T_NUMBER == context->type) { + value = -context->value; + token_next(context); + } else PARSE_ERROR("Number '-' follows with number"); + break; + case T_NUMBER: value = context->value; token_next(context); break; + default: PARSE_ERROR("Number - error"); + } + error: return value; +} + +static int Frequency(ParserContext* context, int delta, int* to, int range) { + switch (context->type) { + case T_SLASH: + token_next(context); + if (T_NUMBER == context->type) { + delta = context->value; + if (delta < 1) PARSE_ERROR("Frequency - needs to be at least 1"); + if (!range) *to = context->max - 1; + token_next(context); + } else PARSE_ERROR("Frequency - '/' follows with number"); + break; + case T_COMMA: case T_WS: case T_EOF: break; + default: PARSE_ERROR("Frequency - error"); + } + error: return delta; +} + +static int Range(ParserContext* context, int* from, int to) { + int i; + switch (context->type) { + case T_HASH: + if (CRON_CF_DAY_OF_WEEK == context->field_type) { + token_next(context); + if (*context->target->day_in_month) + PARSE_ERROR("Nth-day - support for specifying multiple '#' segments is not implemented"); + *context->target->day_in_month = (int8_t)Number(context); + if (*context->target->day_in_month > 5 || *context->target->day_in_month < -5) + PARSE_ERROR("Nth-day - '#' can follow only with -5..5"); + } else PARSE_ERROR("Nth-day - '#' allowed only for day of week"); + break; + case T_MINUS: + token_next(context); + if (T_NUMBER == context->type) { + to = context->value; + token_next(context); + } else PARSE_ERROR("Range '-' follows with number"); + break; + case T_W: + *context->target->day_in_month = (int8_t)to; + *from = context->min; + for (i = 1; i <= 5; i++) cron_set_bit(context->target->days_of_week, i); + cron_set_bit(context->target->flags, 2); + to = context->max - 1; + token_next(context); + break; + case T_L: + if (CRON_CF_DAY_OF_WEEK == context->field_type) { + *context->target->day_in_month = -1; + token_next(context); + } else PARSE_ERROR("Range - 'L' allowed only for day of week"); + break; + case T_WS: case T_SLASH: case T_COMMA: case T_EOF: break; + default: PARSE_ERROR("Range - error"); + } + error: return to; +} + +static void Segment(ParserContext* context) { + int i, from = context->min, to = context->max - 1, delta = 1, leap = 0; + start: switch (context->type) { + case T_ASTERISK: token_next(context); delta = Frequency(context, delta, &to, 0); break; + case T_NUMBER: + from = context->value; + token_next(context); + to = Range(context, &from, from); + if (context->err) goto error; + delta = Frequency(context, delta, &to, from != to); + break; + case T_L: + token_next(context); + switch (context->field_type) { + case CRON_CF_SECOND: if (!leap) context->max += CRON_MAX_LEAP_SECONDS; leap = 1; goto start; + case CRON_CF_DAY_OF_MONTH: + *context->target->day_in_month = -1; + switch (context->type) { + case T_MINUS: case T_NUMBER: *context->target->day_in_month += (int8_t)Number(context); break; + case T_W: + if (CRON_CF_DAY_OF_MONTH == context->field_type) { + token_next(context); + for (i = 1; i <= 5; i++) cron_set_bit(context->target->days_of_week, i); + cron_set_bit(context->target->flags, 1); + context->fix_dow = 1; + goto done; + } else PARSE_ERROR("Offset - 'W' allowed only for day of month"); + case T_COMMA: case T_WS: case T_EOF: break; + default: PARSE_ERROR("Offset - error"); + } + /* Note 0..6 and not 1..7, see end of set_days_of_week. */ + for (i = 0; i <= 6; i++) cron_set_bit(context->target->days_of_week, i); + cron_set_bit(context->target->flags, 0); + context->fix_dow = 1; + break; + case CRON_CF_DAY_OF_WEEK: from = to = 0; break; + default: PARSE_ERROR("Segment 'L' allowed only for day of month and leap seconds") + } + break; + case T_W: + for (i = 1; i <= 5; i++) cron_set_bit(context->target->days_of_week, i); + token_next(context); + context->fix_dow = 1; + break; + case T_QUESTION: token_next(context); break; + default: PARSE_ERROR("Segment - error"); + } + done: if (context->err) goto error; + if (CRON_CF_DAY_OF_WEEK == context->field_type && context->fix_dow) return; + if (from < context->min || to < context->min) PARSE_ERROR("Range - specified range is less than minimum"); + if (from >= context->max || to >= context->max) PARSE_ERROR("Range - specified range exceeds maximum"); + if (from > to) PARSE_ERROR("Range - specified range start exceeds range end"); + for (; from <= to; from+=delta) cron_set_bit(context->field, from+context->offset); + if (CRON_CF_DAY_OF_WEEK == context->field_type) { + if (cron_get_bit(context->field, 7)) { + /* Sunday can be represented as 0 or 7*/ + cron_set_bit(context->field, 0); + cron_del_bit(context->field, 7); + } + } + error: return; +} + +static void Field(ParserContext* context) { + Segment(context); + if (context->err) goto error; + switch (context->type) { + case T_COMMA: token_next(context); Field(context); break; + case T_WS: case T_EOF: break; + default: PARSE_ERROR("FieldRest - error"); + } + error: return; +} + +static void FieldWrapper(ParserContext* context, int field_type, int min, int max, int offset, uint8_t* field) { + context->field_type = field_type; + context->min = min; + context->max = max; + context->offset = offset; + context->field = field; + Field(context); +} + +static void Fields(ParserContext* context, int len) { + token_next(context); + if (len < 6) cron_set_bit(context->target->seconds, 0); + else { + FieldWrapper(context, CRON_CF_SECOND, 0, CRON_MAX_SECONDS, 0, context->target->seconds); + TOKEN_COMPARE(context, T_WS); + } + FieldWrapper(context, CRON_CF_MINUTE, 0, CRON_MAX_MINUTES, 0, context->target->minutes); + TOKEN_COMPARE(context, T_WS); + FieldWrapper(context, CRON_CF_HOUR_OF_DAY, 0, CRON_MAX_HOURS, 0, context->target->hours); + TOKEN_COMPARE(context, T_WS); + FieldWrapper(context, CRON_CF_DAY_OF_MONTH, 1, CRON_MAX_DAYS_OF_MONTH, 0, context->target->days_of_month); + TOKEN_COMPARE(context, T_WS); + FieldWrapper(context, CRON_CF_MONTH, 1, CRON_MAX_MONTHS + 1, -1, context->target->months); + TOKEN_COMPARE(context, T_WS); + FieldWrapper(context, CRON_CF_DAY_OF_WEEK, 0, CRON_MAX_DAYS_OF_WEEK + 1, 0, context->target->days_of_week); +#ifndef CRON_DISABLE_YEARS + if (len < 7) cron_set_bit(context->target->years, EXPR_YEARS_LENGTH*8-1); + else { + TOKEN_COMPARE(context, T_WS); + FieldWrapper(context, CRON_CF_YEAR, CRON_MIN_YEARS, CRON_MAX_YEARS, -CRON_MIN_YEARS, context->target->years); + } +#endif + return; + compare_error: PARSE_ERROR("Fields - expected whitespace separator"); + error: return; +} + +/** + * Search the bits provided for the next set bit after the value provided, + * and reset the calendar. + */ +static int find_next(uint8_t* bits, int max, int value, int offset, struct tm* calendar, int field, int nextField, uint8_t* lower_orders, int* res_out) { + int notfound = 0, err = 0, next_value = next_set_bit(bits, max, value+offset, ¬found)-offset; + /* roll over if needed */ + if (notfound) { + err = add_to_field(calendar, nextField, 1); + if (err) goto return_error; + err = reset_min(calendar, field); + if (err) goto return_error; + notfound = 0; + next_value = next_set_bit(bits, max, 0, ¬found); + } + if (notfound || next_value != value) { + err = reset_all_min(calendar, lower_orders); + 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; +} + +/** + * Search the bits provided for the next set bit after the value provided, + * and reset the calendar. + */ +static int find_prev(uint8_t* bits, int max, int value, int offset, struct tm* calendar, int field, int nextField, uint8_t* lower_orders, int* res_out) { + int notfound = 0, err = 0, next_value = prev_set_bit(bits, value+offset, 0, ¬found)-offset; + /* roll under if needed */ + if (notfound) { + err = add_to_field(calendar, nextField, -1); + if (err) goto return_error; + err = reset_max(calendar, field); + if (err) goto return_error; + notfound = 0; + next_value = prev_set_bit(bits, max - 1, value, ¬found); + } + if (notfound || next_value != value) { + err = reset_all_max(calendar, lower_orders); + 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; +} + +static int find_day_condition(struct tm* calendar, uint8_t* days_of_month, int8_t* dim, int dom, uint8_t* days_of_week, int dow, uint8_t* flags, int* day) { + int tmp_day = *day; + if (tmp_day < 0) { + if ((!*flags && *dim < 0) || *flags & 1) tmp_day = last_day_of_month(calendar->tm_mon, calendar->tm_year); + else if (*flags & 2) tmp_day = last_weekday_of_month(calendar->tm_mon, calendar->tm_year); + else if (*flags & 4) tmp_day = closest_weekday(*dim-1, calendar->tm_mon, calendar->tm_year); + *day = tmp_day; + } + if (!cron_get_bit(days_of_month, dom)) return 1; + if (!cron_get_bit(days_of_week, dow)) return 1; + if (*flags) { + if ((*flags & 3) && dom != tmp_day+1+*dim) return 1; + if ((*flags & 4) && dom != tmp_day) return 1; + } else { + if (*dim < 0 && (dom < tmp_day+WEEK_DAYS**dim+1 || dom >= tmp_day+WEEK_DAYS*(*dim+1)+1)) return 1; + if (*dim > 0 && (dom < WEEK_DAYS*(*dim-1)+1 || dom >= WEEK_DAYS**dim+1)) return 1; + } + return 0; +} + +static int find_day(struct tm* calendar, uint8_t* days_of_month, int8_t* dim, int dom, uint8_t* days_of_week, int dow, uint8_t* flags, uint8_t* resets, int* res_out, int offset) { + int err, day = -1, year = calendar->tm_year, month = calendar->tm_mon; + unsigned int count = 0, max = 366; + while (find_day_condition(calendar, days_of_month, dim, dom, days_of_week, dow, flags, &day) && count++ < max) { + err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, offset); + if (err) goto return_error; + dom = calendar->tm_mday; + dow = calendar->tm_wday; + if (year != calendar->tm_year) { + year = calendar->tm_year; + /* This should not be needed unless there is as single day month in libc. */ + day = -1; + } + if (month != calendar->tm_mon) { + month = calendar->tm_mon; + day = -1; + } + if (offset > 0) reset_all_min(calendar, resets) else reset_all_max(calendar, resets); + } + return dom; + return_error: *res_out = 1; return 0; +} + +static int do_nextprev( + int (*find)(uint8_t* bits, int max, int value, int offset, struct tm* calendar, int field, int nextField, uint8_t* lower_orders, int* res_out), + cron_expr* expr, struct tm* calendar, int dot, int offset) { + int res = 0, value = 0, update_value = 0; + uint8_t resets[1], empty_list[1] = {0}; + + for(;;) { + *resets = 0; + + value = calendar->tm_sec; + update_value = find(expr->seconds, CRON_MAX_SECONDS+CRON_MAX_LEAP_SECONDS, value, 0, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); + if (0 != res) break; + if (value == update_value) cron_set_bit(resets, CRON_CF_SECOND); + else if (update_value >= CRON_MAX_SECONDS) continue; + + value = calendar->tm_min; + update_value = find(expr->minutes, CRON_MAX_MINUTES, value, 0, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); + if (0 != res) break; + if (value == update_value) cron_set_bit(resets, CRON_CF_MINUTE); else continue; + + value = calendar->tm_hour; + update_value = find(expr->hours, CRON_MAX_HOURS, value, 0, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); + if (0 != res) break; + if (value == update_value) cron_set_bit(resets, CRON_CF_HOUR_OF_DAY); else continue; + + value = calendar->tm_mday; + update_value = find_day(calendar, expr->days_of_month, expr->day_in_month, value, expr->days_of_week, calendar->tm_wday, expr->flags, resets, &res, offset); + if (0 != res) break; + if (value == update_value) cron_set_bit(resets, CRON_CF_DAY_OF_MONTH); else continue; + + value = calendar->tm_mon; /*day already adds one if no day in same value is found*/ + update_value = find(expr->months, CRON_MAX_MONTHS, value, 0, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); + if (0 != res) break; + if (value != update_value) { + if (abs(calendar->tm_year - dot) > CRON_MAX_YEARS_DIFF) { + res = -1; + break; + } + continue; + } +#ifdef CRON_DISABLE_YEARS + else break; +#else + if (cron_get_bit(expr->years, EXPR_YEARS_LENGTH*8-1)) break; + value = calendar->tm_year; + update_value = find(expr->years, CRON_MAX_YEARS-CRON_MIN_YEARS, value, YEAR_OFFSET-CRON_MIN_YEARS, calendar, CRON_CF_YEAR, CRON_CF_NEXT, resets, &res); + if (0 != res || value == update_value) break; +#endif + } + + return res; +} + +#define STRCATC(dest, buf, inc_len) do { len += inc_len; if (len > buffer_len) return -1; strcat(dest, buf); } while (0) + +static int generate_field(char *dest, uint8_t *bits, int min, int max, int offset, int buffer_len) { + char buf[32]; + int first = 1, from = -1, i, bit, len = 0; + + for (i = min; i < max; i++) { + bit = cron_get_bit(bits, i + offset); + if (bit) { + if (from == -1) from = i; + } else if (from != -1) { + if (first) first = 0; else STRCATC(dest, ",", 1); + if (from == i - 1) len += sprintf(buf, "%d", from); + else len += sprintf(buf, "%d-%d", from, i - 1); + STRCATC(dest, buf, 0); + from = -1; + } + } + if (from == i - 1) { + if (first) first = 0; else STRCATC(dest, ",", 1); + STRCATC(dest, buf, sprintf(buf, "%d", from)); + } else if (from == min && i == max) STRCATC(dest, "*", 1); + else if (from != -1) { + if (first) first = 0; else STRCATC(dest, ",", 1); + STRCATC(dest, buf, sprintf(buf, "%d-%d", from, i - 1)); + } + return len; +} + +#define GFC(dest, bits, min, max, offset, buffer_len) { tmp = generate_field(dest, bits, min, max, offset, buffer_len); if (tmp < 0) return tmp; else len += tmp; } + +int cron_generate_expr(cron_expr *source, char *buffer, int buffer_len, int cron_len, const char **error) { + const char* err_local; + char buf[32]; + int i, leap = 0, tmp, len = 1; + uint8_t days_of_week = *source->days_of_week; + *buffer = '\0'; + if (!error) error = &err_local; + + if (cron_len > 5) { + for (i = CRON_MAX_SECONDS; i < CRON_MAX_SECONDS+CRON_MAX_LEAP_SECONDS; i++) { + if (cron_get_bit(source->seconds, i)) { + leap = 1; + STRCATC(buffer, "L", 1); + break; + } + } + GFC(buffer, source->seconds, 0, CRON_MAX_SECONDS + (leap ? CRON_MAX_LEAP_SECONDS : 0), 0, buffer_len - len); + STRCATC(buffer, " ", 1); + } + GFC(buffer, source->minutes, 0, CRON_MAX_MINUTES, 0, buffer_len - len); + STRCATC(buffer, " ", 1); + GFC(buffer, source->hours, 0, CRON_MAX_HOURS, 0, buffer_len - len); + STRCATC(buffer, " ", 1); + if (cron_get_bit(source->flags, 0)) { + STRCATC(buffer, "L", 1); + if (*source->day_in_month < -1) STRCATC(buffer, buf, sprintf(buf, "%d", *source->day_in_month + 1)); + } else if (cron_get_bit(source->flags, 1)) STRCATC(buffer, "LW", 2); + else if (cron_get_bit(source->flags, 2)) STRCATC(buffer, buf, sprintf(buf, "%dW", *source->day_in_month)); + else if (*source->day_in_month != 0) STRCATC(buffer, "?", 1); + else GFC(buffer, source->days_of_month, 1, CRON_MAX_DAYS_OF_MONTH, 0, buffer_len - len); + STRCATC(buffer, " ", 1); + GFC(buffer, source->months, 1, CRON_MAX_MONTHS + 1, -1, buffer_len - len); + STRCATC(buffer, " ", 1); + if (cron_get_bit(&days_of_week, 0)) { + cron_set_bit(&days_of_week, 7); + cron_del_bit(&days_of_week, 0); + } + if (*source->flags) STRCATC(buffer, "?", 1); + else { + if (*source->day_in_month != 0) { + GFC(buffer, &days_of_week, 1, CRON_MAX_DAYS_OF_WEEK+1, 0, buffer_len - len); + if (*source->day_in_month == -1) STRCATC(buffer, "L", 1); + else STRCATC(buffer, buf, sprintf(buf, "#%d", *source->day_in_month)); + } else GFC(buffer, &days_of_week, 1, CRON_MAX_DAYS_OF_WEEK+1, 0, buffer_len - len); + } +#ifndef CRON_DISABLE_YEARS + if (cron_len > 6) { + if (cron_get_bit(source->years, EXPR_YEARS_LENGTH*8-1)) STRCATC(buffer, " *", 2); + else { + STRCATC(buffer, " ", 1); + GFC(buffer, source->years, CRON_MIN_YEARS, CRON_MAX_YEARS, -CRON_MIN_YEARS, buffer_len - len); + } + } +#endif + return len; +} + +static time_t cron( + int (*find)(uint8_t* bits, int max, int value, int offset, struct tm* calendar, int field, int nextField, uint8_t* lower_orders, int* res_out), + cron_expr* expr, time_t date, int offset) { + /* + The plan: + + 1 Round up to the next whole second + + 2 If seconds match move on, otherwise find the next match: + 2.1 If next match is in the next minute then roll forwards + + 3 If minute matches move on, otherwise find the next match + 3.1 If next match is in the next hour then roll forwards + 3.2 Reset the seconds and go to 2 + + 4 If hour matches move on, otherwise find the next match + 4.1 If next match is in the next day then roll forwards, + 4.2 Reset the minutes and seconds and go to 2 + + ... + */ + struct tm calval, *calendar; + int res; + time_t original, calculated; + if (!expr) return CRON_INVALID_INSTANT; + memset(&calval, 0, sizeof(struct tm)); + calendar = cron_time(&date, &calval); + if (!calendar) return CRON_INVALID_INSTANT; + original = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; + + res = do_nextprev(find, expr, calendar, calendar->tm_year, offset); + if (0 != res) return CRON_INVALID_INSTANT; + + calculated = cron_mktime(calendar); + if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; + if (calculated == original) { + /* We arrived at the original timestamp - round up to the next whole second and try again... */ + res = add_to_field(calendar, CRON_CF_SECOND, offset); + if (0 != res) return CRON_INVALID_INSTANT; + res = do_nextprev(find, expr, calendar, calendar->tm_year, offset); + if (0 != res) return CRON_INVALID_INSTANT; + } + + return cron_mktime(calendar); +} + +void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { + const char* err_local; + int len = 0; + ParserContext context; + if (!error) error = &err_local; + *error = NULL; + if (!expression) CRON_ERROR("Invalid NULL expression"); + if (!target) CRON_ERROR("Invalid NULL target"); + if ('@' == *expression) { + expression++; + if (!strcmp("annually", expression) || !strcmp("yearly", expression)) expression = "0 0 0 1 1 *"; + else if (!strcmp("monthly", expression)) expression = "0 0 0 1 * *"; + else if (!strcmp("weekly", expression)) expression = "0 0 0 * * 0"; + else if (!strcmp("daily", expression) || !strcmp("midnight", expression)) expression = "0 0 0 * * *"; + else if (!strcmp("hourly", expression)) expression = "0 0 * * * *"; + else if (!strcmp("minutely", expression)) expression = "0 * * * * *"; + else if (!strcmp("secondly", expression)) expression = "* * * * * * *"; + else if (!strcmp("reboot", expression)) CRON_ERROR("@reboot not implemented"); + } + len = count_fields(expression, ' '); + if (len < 5 || len > 7) CRON_ERROR("Invalid number of fields, expression must consist of 5-7 fields"); + memset(target, 0, sizeof(*target)); + memset(&context, 0, sizeof(context)); + context.input = expression; + context.target = target; + Fields(&context, len); + *error = context.err; + error: return; +} + +time_t cron_next(cron_expr* expr, time_t date) { return cron(find_next, expr, date, 1); } +time_t cron_prev(cron_expr* expr, time_t date) { return cron(find_prev, expr, date, -1); } diff --git a/lib/ccronexpr/ccronexpr.h b/lib/supertinycron/ccronexpr.h similarity index 77% rename from lib/ccronexpr/ccronexpr.h rename to lib/supertinycron/ccronexpr.h index 450decb5c7..66600f8ead 100644 --- a/lib/ccronexpr/ccronexpr.h +++ b/lib/supertinycron/ccronexpr.h @@ -14,18 +14,18 @@ * limitations under the License. */ -/* +/* * File: ccronexpr.h * Author: alex * * Created on February 24, 2015, 9:35 AM + * + * Cron doesn't decide calendar, it follows it. */ #ifndef CCRONEXPR_H #define CCRONEXPR_H -//-V::795 - #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) extern "C" { #endif @@ -41,12 +41,12 @@ extern "C" { #define CRON_INVALID_INSTANT ((time_t) -1) -// Define to use local time -#define CRON_USE_LOCAL_TIME - /** * Parsed cron expression */ + +#define EXPR_YEARS_LENGTH 29 + typedef struct { uint8_t seconds[8]; uint8_t minutes[8]; @@ -54,11 +54,22 @@ typedef struct { uint8_t days_of_week[1]; uint8_t days_of_month[4]; uint8_t months[2]; + int8_t day_in_month[1]; + /** + * Flags: + * 0 last day of the month + * 1 last weekday of the month + * 2 closest weekday to day in month + */ + uint8_t flags[1]; +#ifndef CRON_DISABLE_YEARS + uint8_t years[EXPR_YEARS_LENGTH]; +#endif } cron_expr; /** * Parses specified cron expression. - * + * * @param expression cron expression as nul-terminated string, * should be no longer that 256 bytes * @param pointer to cron expression structure, it's client code responsibility @@ -71,10 +82,10 @@ void cron_parse_expr(const char* expression, cron_expr* target, const char** err /** * Uses the specified expression to calculate the next 'fire' date after - * the specified date. All dates are processed as UTC (GMT) dates - * without timezones information. To use local dates (current system timezone) + * the specified date. All dates are processed as UTC (GMT) dates + * without timezones information. To use local dates (current system timezone) * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' - * + * * @param expr parsed cron expression to use in next date calculation * @param date start date to start calculation from * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. @@ -83,21 +94,30 @@ time_t cron_next(cron_expr* expr, time_t date); /** * Uses the specified expression to calculate the previous 'fire' date after - * the specified date. All dates are processed as UTC (GMT) dates - * without timezones information. To use local dates (current system timezone) + * the specified date. All dates are processed as UTC (GMT) dates + * without timezones information. To use local dates (current system timezone) * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' - * + * * @param expr parsed cron expression to use in previous date calculation * @param date start date to start calculation from * @return previous 'fire' date in case of success, '((time_t) -1)' in case of error. */ time_t cron_prev(cron_expr* expr, time_t date); +/** + * Generate cron expression from cron_expr structure + * + * @param expr parsed cron expression to use + * @param buffer buffer for the result + * @param buffer_len maximum length of the buffer + * @param expr_len number of cron fields produced + * @param error output error message, will be set to string literal + * @return used length of the buffer or -1 on error + */ +int cron_generate_expr(cron_expr *source, char *buffer, int buffer_len, int expr_len, const char **error); #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) } /* extern "C"*/ #endif #endif /* CCRONEXPR_H */ - - diff --git a/lib/supertinycron/ccronexpr_test.c b/lib/supertinycron/ccronexpr_test.c new file mode 100644 index 0000000000..23e129bdaa --- /dev/null +++ b/lib/supertinycron/ccronexpr_test.c @@ -0,0 +1,884 @@ +/* + * 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_test.cpp + * Author: alex + * + * Created on February 24, 2015, 9:36 AM + */ + +#include +#include +#include +#include +#include + +#include "ccronexpr.h" + +#define MAX_SECONDS 62 +#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 12 + +#define DATE_FORMAT "%Y-%m-%d_%H:%M:%S" + +#ifndef ARRAY_LEN +#define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) +#endif + +/* declared in cronexpr.c */ +time_t cron_mktime(struct tm* tm); + +/** + * uint8_t* replace char* for storing hit dates, set_bit and get_bit are used as handlers + */ +uint8_t cron_get_bit(const uint8_t* rbyte, int idx); +void cron_set_bit(uint8_t* rbyte, int idx); +void cron_del_bit(uint8_t* rbyte, int idx); + +static int crons_equal(cron_expr* cr1, cron_expr* cr2) { + unsigned int i; + for (i = 0; i < ARRAY_LEN(cr1->seconds); i++) { + if (cr1->seconds[i] != cr2->seconds[i]) { + printf("seconds not equal @%d %02x != %02x", i, cr1->seconds[i], cr2->seconds[i]); + return 0; + } + } + for (i = 0; i < ARRAY_LEN(cr1->minutes); i++) { + if (cr1->minutes[i] != cr2->minutes[i]) { + printf("minutes not equal @%d %02x != %02x", i, cr1->minutes[i], cr2->minutes[i]); + return 0; + } + } + for (i = 0; i < ARRAY_LEN(cr1->hours); i++) { + if (cr1->hours[i] != cr2->hours[i]) { + printf("hours not equal @%d %02x != %02x", i, cr1->hours[i], cr2->hours[i]); + return 0; + } + } + for (i = 0; i < ARRAY_LEN(cr1->days_of_week); i++) { + if (cr1->days_of_week[i] != cr2->days_of_week[i]) { + printf("days_of_week not equal @%d %02x != %02x", i, cr1->days_of_week[i], cr2->days_of_week[i]); + return 0; + } + } + for (i = 0; i < ARRAY_LEN(cr1->days_of_month); i++) { + if (cr1->days_of_month[i] != cr2->days_of_month[i]) { + printf("days_of_month not equal @%d %02x != %02x", i, cr1->days_of_month[i], cr2->days_of_month[i]); + return 0; + } + } + for (i = 0; i < ARRAY_LEN(cr1->months); i++) { + if (cr1->months[i] != cr2->months[i]) { + printf("months not equal @%d %02x != %02x", i, cr1->months[i], cr2->months[i]); + return 0; + } + } + return 1; +} + +int one_dec_num(const char ch) { + switch (ch) { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + default: + return -1; + } +} + +int extract_digits(const char *str, int *digit_count) { + int num = 0; + if (!str || !digit_count) return 0; + while (*str) { + (*digit_count)++; + if (*str >= '0' && *str <= '9') { + num = num * 10 + (*str - '0'); + } else break; + str++; + } + return num; +} + +/* strptime is not available in msvc */ +/* 2012-07-01_09:53:50 */ +/* 0123456789012345678 */ +void poors_mans_strptime(const char* str, struct tm* cal) { + int count = 0; + assert(cal != NULL); + memset(cal, 0, sizeof(struct tm)); + cal->tm_year = extract_digits(str, &count) - 1900; + cal->tm_mon = extract_digits(str + count, &count) - 1; + cal->tm_mday = extract_digits(str + count, &count); + cal->tm_wday = 0; + cal->tm_yday = 0; + cal->tm_hour = extract_digits(str + count, &count); + cal->tm_min = extract_digits(str + count, &count); + cal->tm_sec = extract_digits(str + count, &count); + cal->tm_isdst = -1; +} + +typedef time_t (*cron_find_fn)(cron_expr*, time_t); + +int count_fields(const char* str, char del) { + size_t count = 0; + if (!str) return -1; + while ((str = strchr(str, del)) != NULL) { + count++; + do str++; while (del == *str); + } + return (int)count + 1; +} + +#define check_fn(fn_fn,pattern,initial,expected) check_fn_line(fn_fn, pattern, initial, expected, __LINE__) +void check_fn_line(cron_find_fn fn, const char* pattern, const char* initial, const char* expected, int line) { + const char* err = NULL; + const int len = count_fields(pattern, ' '); + cron_expr parsed1, parsed2; + /*printf("Pattern: %s\n", pattern);**/ + cron_parse_expr(pattern, &parsed1, &err); + + struct tm calinit; + poors_mans_strptime(initial, &calinit); + time_t dateinit = cron_mktime(&calinit); + assert(-1 != dateinit); + time_t datenext = fn(&parsed1, dateinit); +#ifdef CRON_USE_LOCAL_TIME + struct tm* calnext = localtime(&datenext); +#else + struct tm* calnext = gmtime(&datenext); +#endif + assert(calnext); + char* buffer = (char*) malloc(512); + memset(buffer, 0, 512); + strftime(buffer, 512, DATE_FORMAT, calnext); + printf("parsed: %s\n", pattern); + if (0 != strcmp(expected, buffer+(buffer[0] == '+' ? 1 : 0))) { + printf("Line: %d\n", line); + printf("Pattern: %s\n", pattern); + printf("Initial: %s\n", initial); + printf("Expected: %s\n", expected); + printf("Actual: %s\n", buffer); + assert(0); + } + assert(cron_generate_expr(&parsed1, buffer, 512, len, &err) > 0); + if (0 != strcmp(pattern, buffer)) { + printf("Line: %d\n", line); + printf("Pattern: %s\n", pattern); + printf("Actual: %s\n", buffer); + } + cron_parse_expr(buffer, &parsed2, &err); + assert(crons_equal(&parsed1, &parsed2)); + free(buffer); +} + +void check_same(const char* expr1, const char* expr2) { + cron_expr parsed1; + cron_parse_expr(expr1, &parsed1, NULL); + cron_expr parsed2; + cron_parse_expr(expr2, &parsed2, NULL); + printf("parsed1: %s\n", expr1); + printf("parsed2: %s\n", expr2); + assert(crons_equal(&parsed1, &parsed2)); +} + +void check_calc_invalid() { + cron_expr parsed; + cron_parse_expr("0 0 0 31 6 *", &parsed, NULL); + struct tm calinit; + poors_mans_strptime("2012-07-01_09:53:50", &calinit); + time_t dateinit = cron_mktime(&calinit); + time_t res = cron_next(&parsed, dateinit); + assert(CRON_INVALID_INSTANT == res); +} + +void check_expr_invalid(const char* pattern) { + const char* err = NULL; + const int len = count_fields(pattern, ' '); + cron_expr parsed1, parsed2; + cron_parse_expr(pattern, &parsed1, &err); + printf("parsed1: %s\n", pattern); + if (err) { + printf("check_expr_invalid: %s\n", err); + } + assert(err); +} + +#define check_expr_valid(pattern) check_expr_valid_line(pattern, __LINE__) +void check_expr_valid_line(const char* pattern, int line) { + const char* err = NULL; + const int len = count_fields(pattern, ' '); + cron_expr parsed1, parsed2; + cron_parse_expr(pattern, &parsed1, &err); + printf("parsed1: %s\n", pattern); + if (err) { + printf("check_expr_invalid: %s\n", err); + } + char* buffer = (char*) malloc(512); + memset(buffer, 0, 512); + assert(cron_generate_expr(&parsed1, buffer, 512, len, &err) > 0); + if (0 != strcmp(pattern, buffer)) { + printf("Line: %d\n", line); + printf("Pattern: %s\n", pattern); + printf("Actual: %s\n", buffer); + } + cron_parse_expr(buffer, &parsed2, &err); + assert(crons_equal(&parsed1, &parsed2)); + + assert(!err); +} + +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");*/ + check_fn(cron_next, "* * * * * *", "2100-01-01_15:12:42", "2100-01-01_15:12:43"); + check_fn(cron_next, "* * * * * *", "2198-01-01_15:12:42", "2198-01-01_15:12:43"); + check_fn(cron_next, "* * * * * *", "2199-01-01_15:12:42", "2199-01-01_15:12:43"); + if (tz && !strcmp("right/UTC", tz)) { + check_fn(cron_next, "L59 * * * * *", "2016-12-31_23:50:00", "2016-12-31_23:50:59"); + check_fn(cron_next, "L60 * * * * *", "2016-12-31_23:50:00", "2016-12-31_23:59:60"); + check_fn(cron_next, "L60 * * * * *", "2016-12-31_23:59:59", "2016-12-31_23:59:60"); + } + + check_fn(cron_next, "* * * 1 1 * *", "1970-01-01_15:12:42", "1970-01-01_15:12:43"); + +#ifndef CRON_DISABLE_YEARS + check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2199", "1970-01-01_15:12:42", "1970-01-01_15:12:43"); + /*check_fn(cron_next, "* * * 1 1 * 1969,2100,2193,2199", "1969-01-01_15:12:42", "1969-01-01_15:12:43");*/ + check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2199", "1971-01-01_15:12:42", "2100-01-01_00:00:00"); + check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2199", "2195-01-01_15:12:42", "2199-01-01_00:00:00"); + /*check_fn(cron_next, "* * * 1 1 * 1970,2100,2193,2200", "2195-01-01_15:12:42", "2200-01-01_00:00:00");*/ + check_fn(cron_next, "* * * 1 1 * 2020", "2011-01-01_15:12:42", "2020-01-01_00:00:00"); +#endif + + check_fn(cron_next, "* * * 1W * *", "2011-01-01_15:12:42", "2011-01-03_00:00:00"); + check_fn(cron_next, "* * * 31W * *", "2010-01-01_15:12:42", "2010-01-29_00:00:00"); + + check_fn(cron_next, "* * * LW * *", "2010-01-01_15:12:42", "2010-01-29_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-02-03_15:12:42", "2010-02-26_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-03-06_15:12:42", "2010-03-31_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-04-09_15:12:42", "2010-04-30_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-05-12_15:12:42", "2010-05-31_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-06-15_15:12:42", "2010-06-30_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-07-18_15:12:42", "2010-07-30_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-08-21_15:12:42", "2010-08-31_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-09-24_15:12:42", "2010-09-30_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-10-27_15:12:42", "2010-10-29_00:00:00"); + check_fn(cron_next, "* * * LW * *", "2010-11-30_15:12:42", "2010-11-30_15:12:43"); + check_fn(cron_next, "* * * LW * *", "2010-12-31_15:12:42", "2010-12-31_15:12:43"); + + check_fn(cron_next, "* * * 15W * *", "2010-01-01_15:12:42", "2010-01-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-02-03_15:12:42", "2010-02-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-03-06_15:12:42", "2010-03-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-04-09_15:12:42", "2010-04-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-05-12_15:12:42", "2010-05-14_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-06-15_15:12:42", "2010-06-15_15:12:43"); + check_fn(cron_next, "* * * 15W * *", "2010-07-18_15:12:42", "2010-08-16_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-08-21_15:12:42", "2010-09-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-09-24_15:12:42", "2010-10-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-10-27_15:12:42", "2010-11-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-11-30_15:12:42", "2010-12-15_00:00:00"); + check_fn(cron_next, "* * * 15W * *", "2010-12-31_15:12:42", "2011-01-14_00:00:00"); + + check_fn(cron_next, "* * * L-1 * *", "2010-01-01_15:12:42", "2010-01-30_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-02-03_15:12:42", "2010-02-27_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-03-06_15:12:42", "2010-03-30_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-04-09_15:12:42", "2010-04-29_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-05-12_15:12:42", "2010-05-30_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-06-15_15:12:42", "2010-06-29_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-07-18_15:12:42", "2010-07-30_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-08-21_15:12:42", "2010-08-30_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-09-24_15:12:42", "2010-09-29_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-10-27_15:12:42", "2010-10-30_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-11-30_15:12:42", "2010-12-30_00:00:00"); + check_fn(cron_next, "* * * L-1 * *", "2010-12-31_15:12:42", "2011-01-30_00:00:00"); + + check_fn(cron_next, "* * * L * *", "2010-01-01_15:12:42", "2010-01-31_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-02-03_15:12:42", "2010-02-28_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-03-06_15:12:42", "2010-03-31_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-04-09_15:12:42", "2010-04-30_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-05-12_15:12:42", "2010-05-31_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-06-15_15:12:42", "2010-06-30_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-07-18_15:12:42", "2010-07-31_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-08-21_15:12:42", "2010-08-31_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-09-24_15:12:42", "2010-09-30_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-10-27_15:12:42", "2010-10-31_00:00:00"); + check_fn(cron_next, "* * * L * *", "2010-11-30_15:12:42", "2010-11-30_15:12:43"); + check_fn(cron_next, "* * * L * *", "2010-12-31_15:12:42", "2010-12-31_15:12:43"); + + check_fn(cron_next, "* * * * * 1#-5", "2010-09-03_15:12:42", "2010-11-01_00:00:00"); + check_fn(cron_next, "* * * * * 1#-4", "2010-09-03_15:12:42", "2010-09-06_00:00:00"); + check_fn(cron_next, "* * * * * 1#-3", "2010-09-03_15:12:42", "2010-09-13_00:00:00"); + check_fn(cron_next, "* * * * * 1#-2", "2010-09-03_15:12:42", "2010-09-20_00:00:00"); + check_fn(cron_next, "* * * * * 1#1", "2010-09-03_15:12:42", "2010-09-06_00:00:00"); + check_fn(cron_next, "* * * * * 1#2", "2010-09-03_15:12:42", "2010-09-13_00:00:00"); + check_fn(cron_next, "* * * * * 1#3", "2010-09-03_15:12:42", "2010-09-20_00:00:00"); + check_fn(cron_next, "* * * * * 1#4", "2010-09-03_15:12:42", "2010-09-27_00:00:00"); + check_fn(cron_next, "* * * * * 1#5", "2010-09-03_15:12:42", "2010-11-29_00:00:00"); + + check_fn(cron_next, "* * * * * 2#-5", "2010-09-03_15:12:42", "2010-11-02_00:00:00"); + check_fn(cron_next, "* * * * * 2#-4", "2010-09-03_15:12:42", "2010-09-07_00:00:00"); + check_fn(cron_next, "* * * * * 2#-3", "2010-09-03_15:12:42", "2010-09-14_00:00:00"); + check_fn(cron_next, "* * * * * 2#-2", "2010-09-03_15:12:42", "2010-09-21_00:00:00"); + check_fn(cron_next, "* * * * * 2#1", "2010-09-03_15:12:42", "2010-09-07_00:00:00"); + check_fn(cron_next, "* * * * * 2#2", "2010-09-03_15:12:42", "2010-09-14_00:00:00"); + check_fn(cron_next, "* * * * * 2#3", "2010-09-03_15:12:42", "2010-09-21_00:00:00"); + check_fn(cron_next, "* * * * * 2#4", "2010-09-03_15:12:42", "2010-09-28_00:00:00"); + check_fn(cron_next, "* * * * * 2#5", "2010-09-03_15:12:42", "2010-11-30_00:00:00"); + + check_fn(cron_next, "* * * * * 3#-5", "2010-09-03_15:12:42", "2010-12-01_00:00:00"); + check_fn(cron_next, "* * * * * 3#-4", "2010-09-03_15:12:42", "2010-09-08_00:00:00"); + check_fn(cron_next, "* * * * * 3#-3", "2010-09-03_15:12:42", "2010-09-15_00:00:00"); + check_fn(cron_next, "* * * * * 3#-2", "2010-09-03_15:12:42", "2010-09-22_00:00:00"); + check_fn(cron_next, "* * * * * 3#1", "2010-09-03_15:12:42", "2010-10-06_00:00:00"); + check_fn(cron_next, "* * * * * 3#2", "2010-09-03_15:12:42", "2010-09-08_00:00:00"); + check_fn(cron_next, "* * * * * 3#3", "2010-09-03_15:12:42", "2010-09-15_00:00:00"); + check_fn(cron_next, "* * * * * 3#4", "2010-09-03_15:12:42", "2010-09-22_00:00:00"); + check_fn(cron_next, "* * * * * 3#5", "2010-09-03_15:12:42", "2010-09-29_00:00:00"); + + check_fn(cron_next, "* * * * * 4#-5", "2010-09-03_15:12:42", "2010-12-02_00:00:00"); + check_fn(cron_next, "* * * * * 4#-4", "2010-09-03_15:12:42", "2010-09-09_00:00:00"); + check_fn(cron_next, "* * * * * 4#-3", "2010-09-03_15:12:42", "2010-09-16_00:00:00"); + check_fn(cron_next, "* * * * * 4#-2", "2010-09-03_15:12:42", "2010-09-23_00:00:00"); + check_fn(cron_next, "* * * * * 4#1", "2010-09-03_15:12:42", "2010-10-07_00:00:00"); + check_fn(cron_next, "* * * * * 4#2", "2010-09-03_15:12:42", "2010-09-09_00:00:00"); + check_fn(cron_next, "* * * * * 4#3", "2010-09-03_15:12:42", "2010-09-16_00:00:00"); + check_fn(cron_next, "* * * * * 4#4", "2010-09-03_15:12:42", "2010-09-23_00:00:00"); + check_fn(cron_next, "* * * * * 4#5", "2010-09-03_15:12:42", "2010-09-30_00:00:00"); + + check_fn(cron_next, "* * * * * 5#-5", "2010-09-03_15:12:42", "2010-10-01_00:00:00"); + check_fn(cron_next, "* * * * * 5#-4", "2010-09-03_15:12:42", "2010-09-03_15:12:43"); + check_fn(cron_next, "* * * * * 5#-3", "2010-09-03_15:12:42", "2010-09-10_00:00:00"); + check_fn(cron_next, "* * * * * 5#-2", "2010-09-03_15:12:42", "2010-09-17_00:00:00"); + check_fn(cron_next, "* * * * * 5#1", "2010-09-03_15:12:42", "2010-09-03_15:12:43"); + check_fn(cron_next, "* * * * * 5#2", "2010-09-03_15:12:42", "2010-09-10_00:00:00"); + check_fn(cron_next, "* * * * * 5#3", "2010-09-03_15:12:42", "2010-09-17_00:00:00"); + check_fn(cron_next, "* * * * * 5#4", "2010-09-03_15:12:42", "2010-09-24_00:00:00"); + check_fn(cron_next, "* * * * * 5#5", "2010-09-03_15:12:42", "2010-10-29_00:00:00"); + + check_fn(cron_next, "* * * * * 6#-5", "2010-09-03_15:12:42", "2010-10-02_00:00:00"); + check_fn(cron_next, "* * * * * 6#-4", "2010-09-03_15:12:42", "2010-09-04_00:00:00"); + check_fn(cron_next, "* * * * * 6#-3", "2010-09-03_15:12:42", "2010-09-11_00:00:00"); + check_fn(cron_next, "* * * * * 6#-2", "2010-09-03_15:12:42", "2010-09-18_00:00:00"); + check_fn(cron_next, "* * * * * 6#1", "2010-09-03_15:12:42", "2010-09-04_00:00:00"); + check_fn(cron_next, "* * * * * 6#2", "2010-09-03_15:12:42", "2010-09-11_00:00:00"); + check_fn(cron_next, "* * * * * 6#3", "2010-09-03_15:12:42", "2010-09-18_00:00:00"); + check_fn(cron_next, "* * * * * 6#4", "2010-09-03_15:12:42", "2010-09-25_00:00:00"); + check_fn(cron_next, "* * * * * 6#5", "2010-09-03_15:12:42", "2010-10-30_00:00:00"); + + check_fn(cron_next, "* * * * * 7#-5", "2010-09-03_15:12:42", "2010-10-03_00:00:00"); + check_fn(cron_next, "* * * * * 7#-4", "2010-09-03_15:12:42", "2010-09-05_00:00:00"); + check_fn(cron_next, "* * * * * 7#-3", "2010-09-03_15:12:42", "2010-09-12_00:00:00"); + check_fn(cron_next, "* * * * * 7#-2", "2010-09-03_15:12:42", "2010-09-19_00:00:00"); + check_fn(cron_next, "* * * * * 7#1", "2010-09-03_15:12:42", "2010-09-05_00:00:00"); + check_fn(cron_next, "* * * * * 7#2", "2010-09-03_15:12:42", "2010-09-12_00:00:00"); + check_fn(cron_next, "* * * * * 7#3", "2010-09-03_15:12:42", "2010-09-19_00:00:00"); + check_fn(cron_next, "* * * * * 7#4", "2010-09-03_15:12:42", "2010-09-26_00:00:00"); + check_fn(cron_next, "* * * * * 7#5", "2010-09-03_15:12:42", "2010-10-31_00:00:00"); + + check_fn(cron_next, "* * * * * 1L", "2010-09-30_15:12:42", "2010-10-25_00:00:00"); + check_fn(cron_next, "* * * * * 2L", "2010-09-30_15:12:42", "2010-10-26_00:00:00"); + check_fn(cron_next, "* * * * * 3L", "2010-09-30_15:12:42", "2010-10-27_00:00:00"); + check_fn(cron_next, "* * * * * 4L", "2010-09-30_15:12:42", "2010-09-30_15:12:43"); + check_fn(cron_next, "* * * * * 5L", "2010-09-30_15:12:42", "2010-10-29_00:00:00"); + check_fn(cron_next, "* * * * * 6L", "2010-09-30_15:12:42", "2010-10-30_00:00:00"); + check_fn(cron_next, "* * * * * 7L", "2010-09-30_15:12:42", "2010-10-31_00:00:00"); + check_fn(cron_next, "* * * * * 1L", "2010-10-27_15:12:42", "2010-11-29_00:00:00"); + check_fn(cron_next, "* * * * * 2L", "2010-10-27_15:12:42", "2010-11-30_00:00:00"); + check_fn(cron_next, "* * * * * 3L", "2010-10-27_15:12:42", "2010-10-27_15:12:43"); + check_fn(cron_next, "* * * * * 4L", "2010-10-27_15:12:42", "2010-10-28_00:00:00"); + check_fn(cron_next, "* * * * * 5L", "2010-10-27_15:12:42", "2010-10-29_00:00:00"); + check_fn(cron_next, "* * * * * 6L", "2010-10-27_15:12:42", "2010-10-30_00:00:00"); + check_fn(cron_next, "* * * * * 7L", "2010-10-27_15:12:42", "2010-10-31_00:00:00"); + + check_fn(cron_next, "* * * * * 1L", "2010-10-30_15:12:42", "2010-11-29_00:00:00"); + check_fn(cron_next, "* * * * * 2L", "2010-10-30_15:12:42", "2010-11-30_00:00:00"); + check_fn(cron_next, "* * * * * 3L", "2010-10-30_15:12:42", "2010-11-24_00:00:00"); + check_fn(cron_next, "* * * * * 4L", "2010-10-30_15:12:42", "2010-11-25_00:00:00"); + check_fn(cron_next, "* * * * * 5L", "2010-10-30_15:12:42", "2010-11-26_00:00:00"); + check_fn(cron_next, "* * * * * 6L", "2010-10-30_15:12:42", "2010-10-30_15:12:43"); + check_fn(cron_next, "* * * * * 7L", "2010-10-30_15:12:42", "2010-10-31_00:00:00"); + check_fn(cron_next, "* * * * * 1L", "2010-11-27_15:12:42", "2010-11-29_00:00:00"); + check_fn(cron_next, "* * * * * 2L", "2010-11-27_15:12:42", "2010-11-30_00:00:00"); + check_fn(cron_next, "* * * * * 3L", "2010-11-27_15:12:42", "2010-12-29_00:00:00"); + check_fn(cron_next, "* * * * * 4L", "2010-11-27_15:12:42", "2010-12-30_00:00:00"); + check_fn(cron_next, "* * * * * 5L", "2010-11-27_15:12:42", "2010-12-31_00:00:00"); + check_fn(cron_next, "* * * * * 6L", "2010-11-27_15:12:42", "2010-11-27_15:12:43"); + check_fn(cron_next, "* * * * * 7L", "2010-11-27_15:12:42", "2010-11-28_00:00:00"); + + check_fn(cron_next, "0 0 7 W * *", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); + check_fn(cron_next, "0 0 7 W * *", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); + check_fn(cron_next, "* * * * * L", "2010-10-25_15:12:42", "2010-10-31_00:00:00"); + check_fn(cron_next, "* * * * * L", "2010-10-20_15:12:42", "2010-10-24_00:00:00"); + check_fn(cron_next, "* * * * * L", "2010-10-27_15:12:42", "2010-10-31_00:00:00"); + + check_fn(cron_next, "* 15 11 * * *", "2019-03-09_11:43:00", "2019-03-10_11:15:00"); + check_fn(cron_next, "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00"); + check_fn(cron_next, "*/15 * 1-4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); + check_fn(cron_next, "0,15,30,45 * 1,2,3,4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); + check_fn(cron_next, "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00"); + check_fn(cron_next, "0 */2 * * * *", "2012-07-01_09:00:00", "2012-07-01_09:02:00"); + check_fn(cron_next, "0 */2 * * * *", "2013-07-01_09:00:00", "2013-07-01_09:02:00"); + check_fn(cron_next, "0 */2 * * * *", "2018-09-14_14:24:00", "2018-09-14_14:26:00"); + check_fn(cron_next, "0 */2 * * * *", "2018-09-14_14:25:00", "2018-09-14_14:26:00"); + check_fn(cron_next, "0 */20 * * * *", "2018-09-14_14:24:00", "2018-09-14_14:40:00"); + check_fn(cron_next, "* * * * * *", "2012-07-01_09:00:00", "2012-07-01_09:00:01"); + check_fn(cron_next, "* * * * * *", "2012-12-01_09:00:58", "2012-12-01_09:00:59"); + check_fn(cron_next, "10 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); + check_fn(cron_next, "11 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:42:11"); + check_fn(cron_next, "10 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:43:10"); + check_fn(cron_next, "10-15 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); + check_fn(cron_next, "10-15 * * * * *", "2012-12-01_21:42:14", "2012-12-01_21:42:15"); + check_fn(cron_next, "0 * * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); + check_fn(cron_next, "0 * * * * *", "2012-12-01_21:11:00", "2012-12-01_21:12:00"); + check_fn(cron_next, "0 11 * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); + check_fn(cron_next, "0 10 * * * *", "2012-12-01_21:11:00", "2012-12-01_22:10:00"); + check_fn(cron_next, "0 0 * * * *", "2012-09-30_11:01:00", "2012-09-30_12:00:00"); + check_fn(cron_next, "0 0 * * * *", "2012-09-30_12:00:00", "2012-09-30_13:00:00"); + check_fn(cron_next, "0 0 * * * *", "2012-09-10_23:01:00", "2012-09-11_00:00:00"); + check_fn(cron_next, "0 0 * * * *", "2012-09-11_00:00:00", "2012-09-11_01:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-09-01_14:42:43", "2012-09-02_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-09-02_00:00:00", "2012-09-03_00:00:00"); + check_fn(cron_next, "* * * 10 * *", "2012-10-09_15:12:42", "2012-10-10_00:00:00"); + check_fn(cron_next, "* * * 10 * *", "2012-10-11_15:12:42", "2012-11-10_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-09-30_15:12:42", "2012-10-01_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-10-01_00:00:00", "2012-10-02_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-08-30_15:12:42", "2012-08-31_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-08-31_00:00:00", "2012-09-01_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-10-30_15:12:42", "2012-10-31_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2012-10-31_00:00:00", "2012-11-01_00:00:00"); + check_fn(cron_next, "0 0 0 1 * *", "2012-10-30_15:12:42", "2012-11-01_00:00:00"); + check_fn(cron_next, "0 0 0 1 * *", "2012-11-01_00:00:00", "2012-12-01_00:00:00"); + check_fn(cron_next, "0 0 0 1 * *", "2010-12-31_15:12:42", "2011-01-01_00:00:00"); + check_fn(cron_next, "0 0 0 1 * *", "2011-01-01_00:00:00", "2011-02-01_00:00:00"); + check_fn(cron_next, "0 0 0 31 * *", "2011-10-30_15:12:42", "2011-10-31_00:00:00"); + check_fn(cron_next, "0 0 0 1 * *", "2011-10-30_15:12:42", "2011-11-01_00:00:00"); + check_fn(cron_next, "* * * * * 2", "2010-10-25_15:12:42", "2010-10-26_00:00:00"); + check_fn(cron_next, "* * * * * 2", "2010-10-20_15:12:42", "2010-10-26_00:00:00"); + check_fn(cron_next, "* * * * * 2", "2010-10-27_15:12:42", "2010-11-02_00:00:00"); + check_fn(cron_next, "55 5 * * * *", "2010-10-27_15:04:54", "2010-10-27_15:05:55"); + check_fn(cron_next, "55 5 * * * *", "2010-10-27_15:05:55", "2010-10-27_16:05:55"); + check_fn(cron_next, "55 * 10 * * *", "2010-10-27_09:04:54", "2010-10-27_10:00:55"); + check_fn(cron_next, "55 * 10 * * *", "2010-10-27_10:00:55", "2010-10-27_10:01:55"); + check_fn(cron_next, "* 5 10 * * *", "2010-10-27_09:04:55", "2010-10-27_10:05:00"); + check_fn(cron_next, "* 5 10 * * *", "2010-10-27_10:05:00", "2010-10-27_10:05:01"); + check_fn(cron_next, "55 * * 3 * *", "2010-10-02_10:05:54", "2010-10-03_00:00:55"); + check_fn(cron_next, "55 * * 3 * *", "2010-10-03_00:00:55", "2010-10-03_00:01:55"); + check_fn(cron_next, "* * * 3 11 *", "2010-10-02_14:42:55", "2010-11-03_00:00:00"); + check_fn(cron_next, "* * * 3 11 *", "2010-11-03_00:00:00", "2010-11-03_00:00:01"); + check_fn(cron_next, "0 0 0 29 2 *", "2007-02-10_14:42:55", "2008-02-29_00:00:00"); + check_fn(cron_next, "0 0 0 29 2 *", "2008-02-29_00:00:00", "2012-02-29_00:00:00"); + check_fn(cron_next, "0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); + check_fn(cron_next, "0 0 7 ? * MON-FRI", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); + check_fn(cron_next, "0 30 23 30 1/3 ?", "2010-12-30_00:00:00", "2011-01-30_23:30:00"); + check_fn(cron_next, "0 30 23 30 1/3 ?", "2011-01-30_23:30:00", "2011-04-30_23:30:00"); + check_fn(cron_next, "0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00"); + check_fn(cron_next, "* * * * * *", "2020-12-31_23:59:59", "2021-01-01_00:00:00"); + check_fn(cron_next, "0 0 * * * *", "2020-02-28_23:00:00", "2020-02-29_00:00:00"); + check_fn(cron_next, "0 0 0 * * *", "2020-02-29_01:02:03", "2020-03-01_00:00:00"); + + check_fn(cron_prev, "* 15 11 * * *", "2019-03-09_11:43:00", "2019-03-09_11:15:59"); + check_fn(cron_prev, "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-01_04:59:45"); + check_fn(cron_prev, "*/15 * 1-4 * * *", "2012-07-01_01:00:14", "2012-07-01_01:00:00"); + check_fn(cron_prev, "*/15 * 1-4 * * *", "2012-07-01_01:00:00", "2012-06-30_04:59:45"); + check_fn(cron_prev, "* * * * * *", "2012-07-01_09:00:00", "2012-07-01_08:59:59"); + check_fn(cron_prev, "* * * * * *", "2021-01-01_00:00:00", "2020-12-31_23:59:59"); + check_fn(cron_prev, "0 0 * * * *", "2020-02-29_00:00:00", "2020-02-28_23:00:00"); + check_fn(cron_prev, "0 0 0 * * *", "2020-03-01_00:00:00", "2020-02-29_00:00:00"); + check_fn(cron_prev, "0 0 * * * *", "2020-03-01_00:00:00", "2020-02-29_23:00:00"); + + check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-05-31_00:00:00", "2022-11-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-07-31_00:00:00", "2022-11-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-08-31_00:00:00", "2022-11-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 11-12 *", "2022-10-31_00:00:00", "2022-11-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 6-7 *", "2022-05-31_00:00:00", "2022-06-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 8-9 *", "2022-07-31_00:00:00", "2022-08-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 9-10 *", "2022-08-31_00:00:00", "2022-09-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 2-3 *", "2022-01-31_00:00:00", "2022-02-01_00:00:00"); + check_fn(cron_next, "0 0 0 ? 4-5 *", "2022-03-31_00:00:00", "2022-04-01_00:00:00"); + + check_fn(cron_next, "* * * 29 2 *", "2021-12-07_12:00:00", "2024-02-29_00:00:00"); + check_fn(cron_prev, "* * * 29 2 *", "2021-12-07_12:00:00", "2020-02-29_23:59:59"); + + check_fn(cron_prev, "* * * 1 2 *", "2023-11-01_00:00:00", "2023-02-01_23:59:59"); + check_fn(cron_prev, "* * * 2 2 *", "2023-11-01_00:00:00", "2023-02-02_23:59:59"); + check_fn(cron_prev, "* * * 3 2 *", "2023-11-01_00:00:00", "2023-02-03_23:59:59"); + check_fn(cron_prev, "* * * 4 2 *", "2023-11-01_00:00:00", "2023-02-04_23:59:59"); + + check_fn(cron_prev, "* * * 1 4 *", "2023-10-01_00:00:00", "2023-04-01_23:59:59"); + check_fn(cron_prev, "* * * 2 4 *", "2023-10-01_00:00:00", "2023-04-02_23:59:59"); + check_fn(cron_prev, "* * * 3 4 *", "2023-10-01_00:00:00", "2023-04-03_23:59:59"); + check_fn(cron_prev, "* * * 4 4 *", "2023-10-01_00:00:00", "2023-04-04_23:59:59"); + + check_fn(cron_prev, "0 0 20 1 2 *", "2022-12-30_08:10:23", "2022-02-01_20:00:00"); + check_fn(cron_prev, "0 0 20 2 2 *", "2022-12-30_08:10:23", "2022-02-02_20:00:00"); + check_fn(cron_prev, "0 0 20 3 2 *", "2022-12-30_08:10:23", "2022-02-03_20:00:00"); + check_fn(cron_prev, "0 0 20 1 2 *", "2022-12-31_08:10:23", "2022-02-01_20:00:00"); + check_fn(cron_prev, "0 0 20 2 2 *", "2022-12-31_08:10:23", "2022-02-02_20:00:00"); + check_fn(cron_prev, "0 0 20 3 2 *", "2022-12-31_08:10:23", "2022-02-03_20:00:00"); + check_fn(cron_prev, "0 0 20 1 2 *", "2023-01-01_08:10:23", "2022-02-01_20:00:00"); + check_fn(cron_prev, "0 0 20 2 2 *", "2023-01-01_08:10:23", "2022-02-02_20:00:00"); + check_fn(cron_prev, "0 0 20 3 2 *", "2023-01-01_08:10:23", "2022-02-03_20:00:00"); + + check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-08-31_18:00:00", "2023-02-28_17:00:00"); + check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-01_18:00:00", "2023-02-28_17:00:00"); + check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-02_18:00:00", "2023-02-28_17:00:00"); + check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-03_18:00:00", "2023-02-28_17:00:00"); + check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-04_18:00:00", "2023-02-28_17:00:00"); + check_fn(cron_prev, "0 0 17 * 2 2-4", "2023-09-05_18:00:00", "2023-02-28_17:00:00"); + + check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-02_17:00:00", "2023-03-01_17:00:00"); + check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-02_17:00:01", "2023-03-02_17:00:00"); + check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-02_18:00:00", "2023-03-02_17:00:00"); + check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-03_18:00:00", "2023-03-03_17:00:00"); + check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-04_18:00:00", "2023-03-03_17:00:00"); + check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-05_18:00:00", "2023-03-03_17:00:00"); + check_fn(cron_prev, "0 0 17 * 3 1-5", "2023-03-06_18:00:00", "2023-03-06_17:00:00"); + + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-05_18:00:00", "2023-04-29_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_09:29:59", "2023-04-29_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_09:30:00", "2023-04-29_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_09:30:01", "2024-04-06_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-06_18:00:00", "2024-04-06_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-07_18:00:00", "2024-04-06_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-26_18:00:00", "2024-04-20_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-27_18:00:00", "2024-04-27_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-04-28_18:00:00", "2024-04-27_09:30:00"); + check_fn(cron_prev, "0 30 9 * 4 6", "2024-05-01_18:00:00", "2024-04-27_09:30:00"); + + check_fn(cron_prev, "0 30 11 * * 6", "2020-02-27_10:00:00", "2020-02-22_11:30:00"); + check_fn(cron_prev, "0 30 11 * * 6", "2020-02-28_10:00:00", "2020-02-22_11:30:00"); + check_fn(cron_prev, "0 30 11 * * 6", "2020-02-29_10:00:00", "2020-02-22_11:30:00"); + check_fn(cron_prev, "0 30 11 * * 6", "2020-02-29_11:31:00", "2020-02-29_11:30:00"); + check_fn(cron_prev, "0 30 11 * * 6", "2020-03-01_10:00:00", "2020-02-29_11:30:00"); + check_fn(cron_prev, "0 30 11 * * 6", "2020-03-01_12:00:00", "2020-02-29_11:30:00"); + + check_fn(cron_prev, "0 0 10 * * *", "2020-02-29_09:59:59", "2020-02-28_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2020-02-29_10:00:00", "2020-02-28_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2020-02-29_10:00:01", "2020-02-29_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2022-02-28_09:59:59", "2022-02-27_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2022-02-28_10:00:00", "2022-02-27_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2022-02-28_10:00:01", "2022-02-28_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_09:59:59", "2022-12-30_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_10:00:00", "2022-12-30_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_10:00:01", "2022-12-31_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2022-12-31_23:59:59", "2022-12-31_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_00:00:00", "2022-12-31_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_00:00:01", "2022-12-31_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_09:59:59", "2022-12-31_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_10:00:00", "2022-12-31_10:00:00"); + check_fn(cron_prev, "0 0 10 * * *", "2023-01-01_10:00:01", "2023-01-01_10:00:00"); + + check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-01_12:34:56", "2023-06-22_23:50:30"); + check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-19_12:34:56", "2023-06-22_23:50:30"); + check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-20_12:34:56", "2023-06-22_23:50:30"); + check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-21_12:34:56", "2023-07-20_23:50:30"); + check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-22_12:34:56", "2023-07-21_23:50:30"); + check_fn(cron_prev, "30 50 23 20,21,22 * *", "2023-07-23_12:34:56", "2023-07-22_23:50:30"); +} + +void test_parse() { + check_same("* * * W * *", "* * * * * 1-5"); + check_same("* * * * * L", "* * * * * 0"); + check_same("* * * * * 6#-1", "* * * * * 6L"); + check_same("0 0 0 * * *", "0 0 * * *"); + check_same("0 0 0 * * *", "0 0 0 * * * *"); + check_same("* * * * * *", "* * * * * * *"); + + check_same("@annually", "0 0 0 1 1 * *"); + check_same("@yearly", "0 0 0 1 1 * *"); + check_same("@monthly", "0 0 0 1 * * *"); + check_same("@weekly", "0 0 0 * * 0 *"); + check_same("@daily", "0 0 0 * * * *"); + check_same("@midnight", "0 0 0 * * * *"); + check_same("@hourly", "0 0 * * * * *"); + check_same("@minutely", "0 * * * * * *"); + check_same("@secondly", "* * * * * * *"); + + check_same("* * * 2 * *", "* * * 2 * ?"); + check_same("* * * 2 * *", "* * * 2 * ?"); + check_same("57,59 * * * * *", "57/2 * * * * *"); + check_same("L57,59,61 * * * * *", "L57/2 * * * * *"); + check_same("1,3,5 * * * * *", "1-6/2 * * * * *"); + check_same("* * 4,8,12,16,20 * * *", "* * 4/4 * * *"); + check_same("* * * * * 0-6", "* * * * * TUE,WED,THU,FRI,SAT,SUN,MON"); + check_same("* * * * * 0", "* * * * * SUN"); + check_same("* * * * * 0", "* * * * * 7"); + check_same("* * * * 1-12 *", "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *"); + check_same("* * * * 2 *", "* * * * Feb *"); + check_same("* * * * 1 *", "* * * * 1 *"); + + check_expr_invalid("Z * * * * *"); + check_expr_invalid("* * * * * 1#-6"); + check_expr_invalid("* * * * * 1#6"); + + check_expr_invalid("77 * * * * *"); + check_expr_invalid("44-77 * * * * *"); + check_expr_invalid("* 77 * * * *"); + check_expr_invalid("* 44-77 * * * *"); + check_expr_invalid("* * 27 * * *"); + check_expr_invalid("* * 23-28 * * *"); + check_expr_invalid("* * * 45 * *"); + check_expr_invalid("* * * 28-45 * *"); + check_expr_invalid("0 0 0 25 13 ?"); + check_expr_invalid("0 0 0 25 0 ?"); + check_expr_invalid("0 0 0 32 12 ?"); + check_expr_invalid("* * * * 11-13 *"); + check_expr_invalid("-5 * * * * *"); + check_expr_invalid("3-2 */5 * * * *"); + check_expr_invalid("/5 * * * * *"); + check_expr_invalid("*/0 * * * * *"); + check_expr_invalid("*/-0 * * * * *"); + check_expr_invalid("* 1 1 0 * *"); + + /* Source: https://www.freeformatter.com/cron-expression-generator-quartz.html */ + + check_expr_valid("* * * ? * *"); /* Every second */ + check_expr_valid("0 * * ? * *"); /* Every minute */ + check_expr_valid("0 */2 * ? * *"); /* Every even minute */ + check_expr_valid("0 1/2 * ? * *"); /* Every uneven minute */ + check_expr_valid("0 */2 * ? * *"); /* Every 2 minutes */ + check_expr_valid("0 */3 * ? * *"); /* Every 3 minutes */ + check_expr_valid("0 */4 * ? * *"); /* Every 4 minutes */ + check_expr_valid("0 */5 * ? * *"); /* Every 5 minutes */ + check_expr_valid("0 */10 * ? * *"); /* Every 10 minutes */ + check_expr_valid("0 */15 * ? * *"); /* Every 15 minutes */ + check_expr_valid("0 */30 * ? * *"); /* Every 30 minutes */ + check_expr_valid("0 15,30,45 * ? * *"); /* Every hour at minutes 15, 30 and 45 */ + check_expr_valid("0 0 * ? * *"); /* Every hour */ + check_expr_valid("0 0 */2 ? * *"); /* Every hour */ + check_expr_valid("0 0 0/2 ? * *"); /* Every even hour */ + check_expr_valid("0 0 1/2 ? * *"); /* Every uneven hour */ + check_expr_valid("0 0 */3 ? * *"); /* Every three hours */ + check_expr_valid("0 0 */4 ? * *"); /* Every four hours */ + check_expr_valid("0 0 */6 ? * *"); /* Every six hours */ + check_expr_valid("0 0 */8 ? * *"); /* Every eight hours */ + check_expr_valid("0 0 */12 ? * *"); /* Every twelve hours */ + check_expr_valid("0 0 0 * * ?"); /* Every day at midnight - 12am */ + check_expr_valid("0 0 1 * * ?"); /* Every day at 1am */ + check_expr_valid("0 0 6 * * ?"); /* Every day at 6am */ + check_expr_valid("0 0 12 * * ?"); /* Every day at noon - 12pm */ + check_expr_valid("0 0 12 * * ?"); /* Every day at noon - 12pm */ + check_expr_valid("0 0 12 ? * SUN"); /* Every Sunday at noon */ + check_expr_valid("0 0 12 ? * MON"); /* Every Monday at noon */ + check_expr_valid("0 0 12 ? * TUE"); /* Every Tuesday at noon */ + check_expr_valid("0 0 12 ? * WED"); /* Every Wednesday at noon */ + check_expr_valid("0 0 12 ? * THU"); /* Every Thursday at noon */ + check_expr_valid("0 0 12 ? * FRI"); /* Every Friday at noon */ + check_expr_valid("0 0 12 ? * SAT"); /* Every Saturday at noon */ + check_expr_valid("0 0 12 ? * MON-FRI"); /* Every Weekday at noon */ + check_expr_valid("0 0 12 ? * SUN,SAT"); /* Every Saturday and Sunday at noon */ + check_expr_valid("0 0 12 */7 * ?"); /* Every 7 days at noon */ + check_expr_valid("0 0 12 1 * ?"); /* Every month on the 1st, at noon */ + check_expr_valid("0 0 12 2 * ?"); /* Every month on the 2nd, at noon */ + check_expr_valid("0 0 12 15 * ?"); /* Every month on the 15th, at noon */ + check_expr_valid("0 0 12 1/2 * ?"); /* Every 2 days starting on the 1st of the month, at noon */ + check_expr_valid("0 0 12 1/4 * ?"); /* Every 4 days staring on the 1st of the month, at noon */ + check_expr_valid("0 0 12 L * ?"); /* Every month on the last day of the month, at noon */ + check_expr_valid("0 0 12 L-2 * ?"); /* Every month on the second to last day of the month, at noon */ + check_expr_valid("0 0 12 LW * ?"); /* Every month on the last weekday, at noon */ + + /*check_expr_valid("0 0 12 1L * ?"); */ /* Every month on the last Sunday, at noon */ + /*check_expr_valid("0 0 12 2L * ?"); */ /* Every month on the last Monday, at noon */ + /*check_expr_valid("0 0 12 6L * ?"); */ /* Every month on the last Friday, at noon */ + + check_expr_valid("0 0 12 ? * 1L"); /* Every month on the last Sunday, at noon */ + check_expr_valid("0 0 12 ? * 2L"); /* Every month on the last Monday, at noon */ + check_expr_valid("0 0 12 ? * 6L"); /* Every month on the last Friday, at noon */ + + check_expr_valid("0 0 12 1W * ?"); /* Every month on the nearest Weekday to the 1st of the month, at noon */ + check_expr_valid("0 0 12 15W * ?"); /* Every month on the nearest Weekday to the 15th of the month, at noon */ + check_expr_valid("0 0 12 ? * 2#1"); /* Every month on the first Monday of the Month, at noon */ + check_expr_valid("0 0 12 ? * 6#1"); /* Every month on the first Friday of the Month, at noon */ + check_expr_valid("0 0 12 ? * 2#2"); /* Every month on the second Monday of the Month, at noon */ + check_expr_valid("0 0 12 ? * 5#3"); /* Every month on the third Thursday of the Month, at noon - 12pm */ + check_expr_valid("0 0 12 ? JAN *"); /* Every day at noon in January only */ + check_expr_valid("0 0 12 ? JUN *"); /* Every day at noon in June only */ + check_expr_valid("0 0 12 ? JAN,JUN *"); /* Every day at noon in January and June */ + check_expr_valid("0 0 12 ? DEC *"); /* Every day at noon in December only */ + check_expr_valid("0 0 12 ? JAN,FEB,MAR,APR *"); /* Every day at noon in January, February, March and April */ + check_expr_valid("0 0 12 ? 9-12 *"); /* Every day at noon between September and December */ + + /* ChatGPT generated inputs for further testing. */ + + check_expr_valid("0 0 12 * * ?"); /* Every day at 12 PM (noon). */ + check_expr_valid("0 15 10 ? * *"); /* Every day at 10:15 AM. */ + check_expr_valid("0 15 10 * * ?"); /* Every day at 10:15 AM. */ + check_expr_valid("0 15 10 * * ? *"); /* Every day at 10:15 AM. */ + check_expr_valid("0 15 10 * * ? 2023"); /* Every day at 10:15 AM during the year 2023. */ + check_expr_valid("0 * 14 * * ?"); /* Every minute starting at 2 PM and ending at 2:59 PM, every day. */ + check_expr_valid("0 0/5 14 * * ?"); /* Every 5 minutes starting at 2 PM and ending at 2:55 PM, every day. */ + check_expr_valid("0 0/5 14,18 * * ?"); /* Every 5 minutes starting at 2 PM and ending at 2:55 PM, AND every 5 minutes starting at 6 PM and ending at 6:55 PM, every day. */ + check_expr_valid("0 0-5 14 * * ?"); /* Every minute starting at 2 PM and ending at 2:05 PM, every day. */ + check_expr_valid("0 10,44 14 ? 3 WED"); /* Every Wednesday in March at 2:10 PM and 2:44 PM. */ + check_expr_valid("0 15 10 ? * MON-FRI"); /* Every weekday at 10:15 AM. */ + check_expr_valid("0 15 10 15 * ?"); /* Every 15th day of the month at 10:15 AM. */ + check_expr_valid("0 15 10 L * ?"); /* Last day of every month at 10:15 AM. */ + check_expr_valid("0 15 10 ? * 6L"); /* Last Friday of every month at 10:15 AM. */ + check_expr_valid("0 15 10 ? * 6L 2022-2025"); /* Last Friday of every month during the years 2022 through 2025 at 10:15 AM. */ + check_expr_valid("0 15 10 ? * 6#3"); /* Third Friday of every month at 10:15 AM. */ + check_expr_valid("0 15 10 ? * 2-6"); /* Every weekday (Monday to Friday) at 10:15 AM. */ + check_expr_valid("0 0/5 14-18 * * ?"); /* Every 5 minutes from 2 PM to 6:55 PM every day. */ + check_expr_valid("0 0 12 1/5 * ?"); /* Every 5 days at 12 PM. */ + check_expr_valid("0 11 11 11 11 ?"); /* Every November 11th at 11:11 AM. */ + check_expr_valid("0 0 12 ? * SUN"); /* Every Sunday at noon. */ + check_expr_valid("0 0 12 ? * MON"); /* Every Monday at noon. */ + check_expr_valid("0 0 12 ? * TUE"); /* Every Tuesday at noon. */ + check_expr_valid("0 0 12 ? * WED"); /* Every Wednesday at noon. */ + check_expr_valid("0 0 12 ? * THU"); /* Every Thursday at noon. */ + check_expr_valid("0 0 12 ? * FRI"); /* Every Friday at noon. */ + check_expr_valid("0 0 12 ? * SAT"); /* Every Saturday at noon. */ + check_expr_valid("0 0/30 8-9 1 * ?"); /* Every 30 minutes between 8-9 AM on the 1st of the month. */ + check_expr_valid("0 0 0 1 1 ?"); /* Every New Year's Day at midnight. */ + check_expr_valid("0 0 0 25 12 ?"); /* Every Christmas at midnight. */ + check_expr_valid("0 0 6,18 * * ?"); /* Every day at 6 AM and 6 PM. */ + check_expr_valid("0 0 8-10 ? * 2-6"); /* Every weekday between 8-10 AM. */ + check_expr_valid("0 0/15 * ? * *"); /* Every 15 minutes. */ + check_expr_valid("0 0 12 LW * ?"); /* Last weekday of the month at noon. */ + check_expr_valid("0 0 12 1W * ?"); /* Nearest weekday to the 1st of the month at noon. */ + check_expr_valid("0 0 12 W * ?"); /* Every weekday at noon. */ + check_expr_valid("0 0 12 ? JAN,DEC *"); /* Every day at noon in January and December. */ + check_expr_valid("0 0 12 ? * 1#2"); /* Second Sunday of every month at noon. */ + check_expr_valid("0 0 12 ? * 2#2"); /* Second Monday of every month at noon. */ + check_expr_valid("0 0 12 ? * 6#1"); /* First Friday of every month at noon. */ + check_expr_valid("0 0 8,20 ? * *"); /* Every day at 8 AM and 8 PM. */ + check_expr_valid("0 30 10-13 ? * WED,FRI"); /* Every Wednesday and Friday between 10:30 AM and 1:30 PM. */ + check_expr_valid("0 0/10 * * * ?"); /* Every 10 minutes. */ + check_expr_valid("0 0 12 L-2 * ?"); /* Two days before the end of the month at noon. */ + check_expr_valid("0 0 12 15W * ?"); /* Nearest weekday to the 15th of every month at noon. */ + check_expr_valid("0 0 0 * * ?"); /* Every day at midnight (beginning of the day). */ + check_expr_valid("0 0 23 * * ?"); /* Every day at 11 PM (end of the day). */ + check_expr_valid("0 30 10 ? * 2-6"); /* Every weekday at 10:30 AM. */ + check_expr_valid("0 0/10 8-17 ? * MON-FRI"); /* Every 10 minutes during working hours (8 AM - 5 PM) on weekdays. */ + /*check_expr_valid("0 0 12 1L * ?"); */ /* Last day of the month at noon. */ + check_expr_valid("0 0 12 ? * SUN,MON"); /* Every Sunday and Monday at noon. */ + check_expr_valid("0 0/5 9-16 * * ?"); /* Every 5 minutes from 9 AM to 4:55 PM every day. */ + check_expr_valid("0 0 12 10-15 * ?"); /* Every day from the 10th to the 15th at noon. */ + check_expr_valid("0 0 0 1 JAN-JUN ?"); /* First day of the month from January to June at midnight. */ + check_expr_valid("0 0 0 ? * 2L"); /* Last Monday of the month at midnight. */ + check_expr_valid("0 0 0 ? * 6#4"); /* Fourth Friday of the month at midnight. */ + check_expr_valid("0 0 12 1/2 * ?"); /* Every other day at noon. */ + check_expr_valid("0 0 12 ? * 2/2"); /* Every other Monday at noon. */ + check_expr_valid("0 0 0 29 FEB ?"); /* Every 29th of February at midnight (Leap Year). */ + check_expr_valid("0 0 12 1,15 * ?"); /* 1st and 15th of the month at noon. */ + check_expr_valid("0 0 0 1 * ? 2024"); /* 1st of every month in 2024 at midnight. */ + check_expr_valid("0 30 6 ? * 1-5"); /* Weekdays at 6:30 AM. */ + check_expr_valid("0 0 0/2 * * ?"); /* Every 2 hours. */ + check_expr_valid("0 0 12/3 ? * *"); /* Every 3 hours starting at noon. */ + check_expr_valid("0 15,45 * ? * *"); /* Every hour at 15 and 45 minutes past. */ + check_expr_valid("0 0 0 ? * SAT,SUN"); /* Every weekend at midnight. */ + check_expr_valid("0 0 8-10,14-16 * * ?"); /* Every day from 8-10 AM and 2-4 PM. */ + check_expr_valid("0 0 12 ? 1-6,9-12 *"); /* Every day at noon excluding July and August. */ + check_expr_valid("0 0 12/4 * * ?"); /* Every 4 hours starting at noon. */ + check_expr_valid("0 0 9-17 * * ?"); /* Every hour from 9 AM to 5 PM. */ + check_expr_valid("0 0/20 9-16 * * ?"); /* Every 20 minutes from 9 AM to 4:40 PM. */ + check_expr_valid("0 0 12 ? * 2-6"); /* Every weekday at noon. */ + check_expr_valid("0 0 8-11,13-16 * * ?"); /* Every day from 8-11 AM and 1-4 PM. */ + check_expr_valid("0 0/30 6-18 * * ?"); /* Every 30 minutes from 6 AM to 6:30 PM. */ + check_expr_valid("0 0 12 ? JAN,MAR,MAY,JUL,SEP,NOV *"); /* Every alternate month at noon. */ + check_expr_valid("0 15 9 ? * MON,TUE,WED,THU,FRI"); /* Every weekday at 9:15 AM. */ + check_expr_valid("0 0/40 * ? * *"); /* Every 40 minutes. */ + check_expr_valid("0 30 10-14 ? * ?"); /* Every day from 10:30 AM to 2:30 PM. */ + check_expr_valid("0 0 12 * JAN-DEC ?"); /* Every day of the year at noon. */ + check_expr_valid("0 0 0 25 12 ? *"); /* Every Christmas at midnight. */ + check_expr_valid("0 0 0/3 * * ?"); /* Every 3 hours. */ + check_expr_valid("0 0 12 * * ? 2025"); /* Every day at noon in the year 2025. */ + check_expr_valid("0 0 6 ? * 2-6"); /* Every weekday at 6 AM. */ + check_expr_valid("0 0 18 ? * 1-5"); /* Every weekday at 6 PM. */ + check_expr_valid("0 0 0 ? * * 2026"); /* Every day at midnight in 2026. */ + check_expr_valid("0 0/45 10-14 * * ?"); /* Every 45 minutes between 10 AM and 2:45 PM. */ + check_expr_valid("0 0 12 1/3 * ?"); /* Every 3 days at noon. */ + check_expr_valid("0 0 0 1 JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC ?"); /* Start of every month at midnight. */ + check_expr_valid("0 0 12 10,20,30 * ?"); /* Every 10th, 20th, and 30th of the month at noon. */ + check_expr_valid("0 0/50 8-16 * * ?"); /* Every 50 minutes from 8 AM to 4:50 PM. */ + check_expr_invalid("0 0 12 ? * 1#1,3#3,5#5"); /* First Sunday, third Wednesday, and fifth Friday of every month at noon. */ + check_expr_valid("0 0 0/4 * * ?"); /* Every 4 hours. */ + check_expr_valid("0 15,30,45 * * * ?"); /* Every hour at 15, 30, and 45 minutes past. */ + check_expr_valid("0 0 12 ? * 2L"); /* Last Monday of every month at noon. */ + check_expr_valid("0 0 12 ? * 5L"); /* Last Thursday of every month at noon. */ + check_expr_valid("0 0 0/6 * * ?"); /* Every 6 hours. */ + check_expr_valid("0 0 12/2 ? * *"); /* Every 2 hours starting at noon. */ + check_expr_valid("0 0 12 ? 1,3,5,7,9,11 *"); /* Every odd month at noon. */ + check_expr_valid("0 0 0 ? 2,4,6,8,10,12 *"); /* Every even month at midnight. */ + check_expr_valid("0 0 12 * * ? 2027"); /* Every day at noon in the year 2027. */ +} + +void test_bits() { + + uint8_t testbyte[8]; + memset(testbyte, 0, 8); + int err = 0; + int i; + + for (i = 0; i <= 63; i++) { + cron_set_bit(testbyte, i); + if (!cron_get_bit(testbyte, i)) { + printf("Bit set error! Bit: %d!\n", i); + err = 1; + } + cron_del_bit(testbyte, i); + if (cron_get_bit(testbyte, i)) { + printf("Bit clear error! Bit: %d!\n", i); + err = 1; + } + assert(!err); + } + + for (i = 0; i < 12; i++) { + cron_set_bit(testbyte, i); + } + if (testbyte[0] != 0xff) { + err = 1; + } + if (testbyte[1] != 0x0f) { + err = 1; + } + + assert(!err); +} + +int main() { + test_bits(); + + test_expr(); + test_parse(); + check_calc_invalid(); + printf("\nAll OK!\n"); + return 0; +} + diff --git a/lib/supertinycron/supertinycron.c b/lib/supertinycron/supertinycron.c new file mode 100644 index 0000000000..dca0a258f0 --- /dev/null +++ b/lib/supertinycron/supertinycron.c @@ -0,0 +1,234 @@ +#define _ISOC99_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccronexpr.h" + +#ifndef VERSION + #define VERSION "dev-build" +#endif + +typedef struct { char *shell, *cmd, *schedule; int verbose; } TinyCronJob; + +void output(const char *msg) { + printf("[supertinycron] %s\n", msg); +} + +void sigchld_handler(int signo) { + (void) signo; + /*while (waitpid(-1, NULL, WNOHANG) > 0);*/ +} + +void sig_handler(int signo) { + if (signo == SIGTERM || signo == SIGINT) { + output("terminated"); + _exit(0); + } +} + +int cron_system(const char *shell, const char *command) { + int stdout_pipe[2], stderr_pipe[2]; + pid_t pid; + + if (pipe(stdout_pipe) != 0 || pipe(stderr_pipe) != 0) { + perror("pipe"); + return -1; + } + + pid = fork(); + if (pid == 0) { + close(stdout_pipe[0]); + close(stderr_pipe[0]); + + if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1) { + perror("dup2 stdout"); + return -1; + } + + if (dup2(stderr_pipe[1], STDERR_FILENO) == -1) { + perror("dup2 stderr"); + return -1; + } + + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + execl(shell, shell, "-c", command, NULL); + perror("execl"); + exit(EXIT_FAILURE); + } else if (pid < 0) { + perror("fork"); + return -1; + } else { + char buffer[4096]; + int nbytes; + + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + while ((nbytes = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) { + write(STDOUT_FILENO, buffer, nbytes); + } + + while ((nbytes = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) { + write(STDERR_FILENO, buffer, nbytes); + } + + close(stdout_pipe[0]); + close(stderr_pipe[0]); + + int status; + pid_t wpid = waitpid(pid, &status, 0); + if (wpid == -1) { + perror("waitpid"); + return -1; + } + if (WIFEXITED(status)) return WEXITSTATUS(status); + else if (WIFSIGNALED(status)) return -WTERMSIG(status); + return -1; + } +} + +TinyCronJob optsFromEnv() { + TinyCronJob opts = {0, 0, 0, 0}; + if (getenv("TINYCRON_VERBOSE") != NULL) opts.verbose = 1; + opts.shell = getenv("SHELL"); + if (!opts.shell) opts.shell = (char *)"/bin/sh"; + return opts; +} + +void usage() { + printf("Usage: supertinycron [expression] [command]\n"); + exit(EXIT_FAILURE); +} + +void message(const char *err, const char *msg) { + if (strlen(msg) == 0) output(err); + else { + char errMsg[512]; + snprintf(errMsg, sizeof(errMsg), "%s %s", msg, err); + output(errMsg); + } +} + +void messageInt(int err, const char *msg) { + if (err) message(strerror(err), msg); +} + +void exitOnErr(int err, const char *msg) { + if (err) { + messageInt(err, msg); + exit(EXIT_FAILURE); + } +} + +void run(TinyCronJob *job) { + if (job->verbose) message(job->cmd, "running job:"); + + messageInt(cron_system(job->shell, job->cmd), "job failed:"); +} + +int nap(TinyCronJob *job) { + time_t current_time = time(NULL), next_run; + + cron_expr expr; + const char* err = NULL; + cron_parse_expr(job->schedule, &expr, &err); + + if (err) { + message(err, "error parsing cron expression:"); + return 1; + } + + next_run = cron_next(&expr, current_time); + + if (job->verbose) { + char msg[512]; + struct tm *time_info = localtime(&next_run); + strftime(msg, sizeof(msg), "%Y-%m-%d %H:%M:%S", time_info); + message(msg, "next job scheduled for"); + } + + int sleep_duration = next_run - current_time; + sleep(sleep_duration); + return 0; +} + +char* find_nth(const char* str, char ch, int n) { + int count = 0; + while (*str) { + if (*str == ch && ++count == n) return (char*)str; + str++; + } + return NULL; +} + +void parse_line(char *line, TinyCronJob *job, int count) { + job->schedule = line; + job->cmd = find_nth(line, ' ', line[0] == '@' ? 1 : count); + + if (!job->cmd) { + messageInt(1, "incomplete cron expression"); + exit(EXIT_FAILURE); + } + *job->cmd = '\0'; + ++job->cmd; +} +int main(int argc, char *argv[]) { + /*signal(SIGCHLD, sigchld_handler);*/ + signal(SIGTERM, sig_handler); + signal(SIGINT, sig_handler); + + if (argc < 2 || strcmp(argv[1], "help") == 0) usage(); + + if (strcmp(argv[1], "version") == 0) { + printf("supertinycron version %s\n", VERSION); + return EXIT_SUCCESS; + } + + TinyCronJob job = optsFromEnv(); + + int i, line_len = 0; + for (i = 1; i < argc; i++) { + line_len += strlen(argv[i]); + } + + line_len += argc - 3; + line_len += 1; + + char *line = (char *)malloc(line_len); + if (!line) { + perror("malloc"); + return EXIT_FAILURE; + } + + strcpy(line, argv[1]); + + for (i = 2; i < argc; i++) { + strcat(line, " "); + strcat(line, argv[i]); + } + + if (job.verbose) message(line, "line"); + + parse_line(line, &job, 7); + + while (1) { + if (nap(&job)) { + perror("error creating job"); + break; + } + run(&job); + } + + free(line); + + return EXIT_SUCCESS; +} diff --git a/lib/supertinycron/test/CMakeLists.txt b/lib/supertinycron/test/CMakeLists.txt new file mode 100644 index 0000000000..7a52389389 --- /dev/null +++ b/lib/supertinycron/test/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0) + +project(ccronexpr_test) + +# Test executable +add_executable(ccronexpr_test ../ccronexpr_test.c) +target_compile_features(ccronexpr_test PRIVATE c_std_99) +target_include_directories(ccronexpr_test PRIVATE .) +target_link_libraries(ccronexpr_test ccronexpr) + +if (MSVC) + target_compile_definitions(ccronexpr_test PRIVATE _CRT_SECURE_NO_WARNINGS) +endif () + +# Add tests +add_test(NAME ccronexpr_test COMMAND ccronexpr_test) diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 31a4a23689..cde562f5ce 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -37,6 +37,7 @@ build_flags = -D NDEBUG -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -DVTABLES_IN_FLASH -DPUYA_SUPPORT=1 + -DCRON_USE_LOCAL_TIME -fno-strict-aliasing -I$PROJECT_DIR/src/include -include "ESPEasy_config.h" @@ -61,6 +62,7 @@ build_flags = -DNDEBUG -DPUYA_SUPPORT=1 -DCORE_POST_2_5_0 -DDISABLE_SC16IS752_SPI + -DCRON_USE_LOCAL_TIME -fno-strict-aliasing -DLIBRARIES_NO_LOG=1 -I$PROJECT_DIR/src/include @@ -251,6 +253,7 @@ build_flags = -DESP32_STAGE -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 -DNEOPIXEL_ESP32_RMT_DEFAULT + -DCRON_USE_LOCAL_TIME -I$PROJECT_DIR/src/include -include "sdkconfig.h" -include "ESPEasy_config.h" From 71fc1a7ca7fd194bc210509da959129ce86298aa Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 4 Dec 2023 10:04:47 +0100 Subject: [PATCH 7/8] [Build] Comment out main function test code supertinycron lib --- lib/supertinycron/supertinycron.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/supertinycron/supertinycron.c b/lib/supertinycron/supertinycron.c index dca0a258f0..30e54145f7 100644 --- a/lib/supertinycron/supertinycron.c +++ b/lib/supertinycron/supertinycron.c @@ -1,3 +1,4 @@ + #define _ISOC99_SOURCE #include @@ -181,8 +182,9 @@ void parse_line(char *line, TinyCronJob *job, int count) { *job->cmd = '\0'; ++job->cmd; } +/* int main(int argc, char *argv[]) { - /*signal(SIGCHLD, sigchld_handler);*/ + //signal(SIGCHLD, sigchld_handler); signal(SIGTERM, sig_handler); signal(SIGINT, sig_handler); @@ -232,3 +234,4 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } +*/ \ No newline at end of file From 2f3a61bf9d6369c884346f7f7d2d9c24515aa81f Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 4 Dec 2023 13:45:30 +0100 Subject: [PATCH 8/8] [SuperTinyCron] Fix discard 'const' qualifier warning in lib --- lib/supertinycron/ccronexpr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/supertinycron/ccronexpr.c b/lib/supertinycron/ccronexpr.c index 6ae027d1a5..cfef63ca29 100644 --- a/lib/supertinycron/ccronexpr.c +++ b/lib/supertinycron/ccronexpr.c @@ -323,7 +323,7 @@ static int reset_all(int (*fn)(struct tm* calendar, int field), struct tm* calen } typedef enum { T_ASTERISK, T_QUESTION, T_NUMBER, T_COMMA, T_SLASH, T_L, T_W, T_HASH, T_MINUS, T_WS, T_EOF, T_INVALID } TokenType; -typedef struct { const char* input; TokenType type; cron_expr* target; int field_type, value, min, max, offset, fix_dow; uint8_t* field; char* err; } ParserContext; +typedef struct { const char* input; TokenType type; cron_expr* target; int field_type, value, min, max, offset, fix_dow; uint8_t* field; const char* err; } ParserContext; static int compare_strings(const char* str1, const char* str2, size_t len) { size_t i;