From d7c17be4d1d55c5934ca33162b089283cb021aaf Mon Sep 17 00:00:00 2001 From: Per Malmberg Date: Sun, 11 Mar 2018 18:45:27 +0100 Subject: [PATCH] Updated README. Added tests. Made use of ? mandatory for DoM/DoW. --- README.md | 60 +++++++++++++++++++++++++++ libcron/CronData.cpp | 26 ++++++++++-- libcron/CronData.h | 50 ++--------------------- libcron/CronSchedule.cpp | 5 +-- test/CronDataTest.cpp | 62 ++++++++++++++-------------- test/CronScheduleTest.cpp | 86 +++++++++++++++++++++++++++------------ test/CronTest.cpp | 4 +- 7 files changed, 182 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index f34166d..4ee1c7a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ # libcron A C++ scheduling library using cron formatting. + +# Supported formatting + +This implementation supports cron format, as specified below. + +Each schedule expression conststs of 6 parts, all mandatory. However, if 'day of month' specifies specific days, then 'day of week' is ignored. + +```text +┌──────────────seconds (0 - 59) +│ ┌───────────── minute (0 - 59) +│ │ ┌───────────── hour (0 - 23) +│ │ │ ┌───────────── day of month (1 - 31) +│ │ │ │ ┌───────────── month (1 - 12) +│ │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday) +│ │ │ │ │ │ +│ │ │ │ │ │ +│ │ │ │ │ │ +* * * * * * +``` +* Allowed formats: + * Special characters: '*', meaning the entire range. + * '?' used to ignore day of month/day of week as noted below. + + * Ranges: 1,2,4-6 + * Result: 1,2,4,5,6 + * Steps: n/m, where n is the start and m is the step. + * `1/2` yields 1,3,5,7... + * `5/3` yields 5,8,11,14... + * `*/2` yields Result: 1,3,5,7... + * Reversed ranges: + * `0 0 23-2 * * *`, meaning top of each minute and hour, of hours, 23, 0, 1 and 2, every day. + * Compare to `0 0 2-23 * * *` which means top of each minute and hour, of hours, 2,3...21,22,23 every day. + + + +For `month`, these (case insensitive) strings can be used instead of numbers: `JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC`. +Example: `JAN,MAR,SEP-NOV` + +For `day of week`, these (case insensitive) strings can be used instead of numbers: `SUN, MON, TUE, WED, THU, FRI, SAT`. +Example: `MON-THU,SAT` + +Each part is separated by one or more whitespaces. It is thus important to keep whitespaces out of the respective parts. + +* Valid: + * 0,3,40-50 * * * * ? + +* Invalid: + * 0, 3, 40-50 * * * * ? + +`Day of month` and `day of week` are mutually exclusive so one of them must at always be ignored using +the '?'-character unless one field already is something other than '*'. + +# Examples + +|Expression | Meaning +| --- | --- | +| * * * * * ? | Every second +|0 0 12 * * MON-FRI | Every Weekday at noon +|0 0 12 1/2 * ? | Every 2 days, starting on the 1st at noon +| 0 0 */12 ? * * | Every twelve hours \ No newline at end of file diff --git a/libcron/CronData.cpp b/libcron/CronData.cpp index 56552ff..bbd6d67 100644 --- a/libcron/CronData.cpp +++ b/libcron/CronData.cpp @@ -36,6 +36,7 @@ namespace libcron valid &= validate_numeric(match[4], day_of_month); valid &= validate_literal(match[5], months, month_names); valid &= validate_literal(match[6], day_of_week, day_names); + valid &= check_dom_vs_dow(match[4], match[6]); valid &= validate_date_vs_months(); } } @@ -84,10 +85,10 @@ namespace libcron res = has_any_in_range(day_of_month, 1, 29); } - if(res) + if (res) { // Make sure that if the days contains only 31, at least one month allows that date. - if(day_of_month.size() == 1 && day_of_month.find(DayOfMonth::Last) != day_of_month.end()) + if (day_of_month.size() == 1 && day_of_month.find(DayOfMonth::Last) != day_of_month.end()) { std::vector months_with_31; for (int32_t i = 1; i <= 12; ++i) @@ -100,7 +101,7 @@ namespace libcron } res = false; - for(size_t i = 0; !res && i < months_with_31.size(); ++i) + for (size_t i = 0; !res && i < months_with_31.size(); ++i) { res = months.find(static_cast(months_with_31[i])) != months.end(); } @@ -110,4 +111,23 @@ namespace libcron return res; } + + bool CronData::check_dom_vs_dow(const std::string& dom, const std::string& dow) const + { + // Day of month and day of week are mutually exclusive so one of them must at always be ignored using + // the '?'-character unless one field already is something other than '*'. + // + // Since we treat an ignored field as allowing the full range, we're OK with both being flagged + // as ignored. To make it explicit to the user of the library, we do however require the use of + // '?' as the ignore flag, although it is functionally equivalent to '*'. + + auto check = [](const std::string& l, std::string r) + { + return l == "*" && (r != "*" || r == "?"); + }; + + return (dom == "?" || dow == "?") + || check(dom, dow) + || check(dow, dom); + } } \ No newline at end of file diff --git a/libcron/CronData.h b/libcron/CronData.h index e9121a1..4f83c65 100644 --- a/libcron/CronData.h +++ b/libcron/CronData.h @@ -8,51 +8,6 @@ namespace libcron { - /* - This class parses strings in the format specified below and holds the resulting allowed values - in its internal sets. - - Cron format, 6 parts: - - ┌──────────────seconds (0 - 59) - │ ┌───────────── minute (0 - 59) - │ │ ┌───────────── hour (0 - 23) - │ │ │ ┌───────────── day of month (1 - 31) - │ │ │ │ ┌───────────── month (1 - 12) - │ │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday; - │ │ │ │ │ │ 7 is also Sunday on some systems) - │ │ │ │ │ │ - │ │ │ │ │ │ - * * * * * * - - Allowed formats: - Special characters: '*', meaning the entire range. - - Ranges: 1,2,4-6 - Result: 1,2,4,5,6 - Steps: 1/2 - Result: 1,3,5,7... - - For day of month, these strings are valid, case insensitive: - SUN, MON, TUE, WED, THU, FRI, SAT - Example: MON-THU,SAT - - For month, these strings are valid, case insensitive: - JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC - Example: JAN,MAR,SEP-NOV - - Each part is separated by one or more whitespaces. It is thus important to keep - whitespaces out of the respective parts. - - Valid: - * * * * * * - 0,3,40-50 * * * * * - - Invalid: - 0, 3, 40-50 * * * * * - - */ - class CronData { public: @@ -149,6 +104,8 @@ namespace libcron bool validate_date_vs_months() const; + bool check_dom_vs_dow(const std::string& dom, const std::string& dow) const; + std::set seconds{}; std::set minutes{}; std::set hours{}; @@ -213,8 +170,9 @@ namespace libcron for (const auto& p : parts) { - if (p == "*") + if (p == "*" || p == "?") { + // We treat the ignore-character '?' the same as the full range being allowed. add_full_range(numbers); } else if (is_number(p)) diff --git a/libcron/CronSchedule.cpp b/libcron/CronSchedule.cpp index 7d2b66d..bd50860 100644 --- a/libcron/CronSchedule.cpp +++ b/libcron/CronSchedule.cpp @@ -27,8 +27,7 @@ namespace libcron curr = s; date_changed = true; } - // If all days are allowed, then the 'day of week' takes precedence, which also means that - // day of week only is ignored when specific days of months are specified. + // If all days are allowed (or the field is ignored via '?'), then the 'day of week' takes precedence. else if (data.get_day_of_month().size() != CronData::value_of(DayOfMonth::Last)) { // Add days until one of the allowed days are found, or stay at the current one. @@ -56,7 +55,7 @@ namespace libcron } } - if(!date_changed) + if (!date_changed) { auto date_time = to_calendar_time(curr); if (data.get_hours().find(static_cast(date_time.hour)) == data.get_hours().end()) diff --git a/test/CronDataTest.cpp b/test/CronDataTest.cpp index 21208ff..bd75483 100644 --- a/test/CronDataTest.cpp +++ b/test/CronDataTest.cpp @@ -31,7 +31,7 @@ SCENARIO("Numerical inputs") { THEN("All parts are filled") { - auto c = CronData::create("* * * * * *"); + auto c = CronData::create("* * * * * ?"); REQUIRE(c.is_valid()); REQUIRE(c.get_seconds().size() == 60); REQUIRE(has_value_range(c.get_seconds(), 0, 59)); @@ -49,7 +49,7 @@ SCENARIO("Numerical inputs") { THEN("Ranges are correct") { - auto c = CronData::create("* 0-59 * * * *"); + auto c = CronData::create("* 0-59 * * * ?"); REQUIRE(c.is_valid()); REQUIRE(c.get_seconds().size() == 60); REQUIRE(c.get_minutes().size() == 60); @@ -63,7 +63,7 @@ SCENARIO("Numerical inputs") { THEN("Ranges are correct") { - auto c = CronData::create("* * * 20-30 * *"); + auto c = CronData::create("* * * 20-30 * ?"); REQUIRE(c.is_valid()); REQUIRE(c.get_seconds().size() == 60); REQUIRE(c.get_minutes().size() == 60); @@ -77,7 +77,7 @@ SCENARIO("Numerical inputs") { THEN("Number of hours are correct") { - auto c = CronData::create("* * 20-5 * * *"); + auto c = CronData::create("* * 20-5 * * ?"); REQUIRE(c.is_valid()); REQUIRE(c.get_hours().size() == 10); REQUIRE(c.get_hours().find(Hours::First) != c.get_hours().end()); @@ -87,12 +87,12 @@ SCENARIO("Numerical inputs") { THEN("Validation succeeds") { - REQUIRE(CronData::create("0-59 * * * * *").is_valid()); - REQUIRE(CronData::create("* 0-59 * * * *").is_valid()); - REQUIRE(CronData::create("* * 0-23 * * *").is_valid()); - REQUIRE(CronData::create("* * * 1-31 * *").is_valid()); - REQUIRE(CronData::create("* * * * 1-12 *").is_valid()); - REQUIRE(CronData::create("* * * * * 0-6").is_valid()); + REQUIRE(CronData::create("0-59 * * * * ?").is_valid()); + REQUIRE(CronData::create("* 0-59 * * * ?").is_valid()); + REQUIRE(CronData::create("* * 0-23 * * ?").is_valid()); + REQUIRE(CronData::create("* * * 1-31 * ?").is_valid()); + REQUIRE(CronData::create("* * * * 1-12 ?").is_valid()); + REQUIRE(CronData::create("* * * ? * 0-6").is_valid()); } } } @@ -105,19 +105,19 @@ SCENARIO("Numerical inputs") REQUIRE_FALSE(CronData::create("").is_valid()); REQUIRE_FALSE(CronData::create("-").is_valid()); REQUIRE_FALSE(CronData::create("* ").is_valid()); - REQUIRE_FALSE(CronData::create("* 0-60 * * * *").is_valid()); - REQUIRE_FALSE(CronData::create("* * 0-25 * * *").is_valid()); - REQUIRE_FALSE(CronData::create("* * * 1-32 * *").is_valid()); - REQUIRE_FALSE(CronData::create("* * * * 1-13 *").is_valid()); + REQUIRE_FALSE(CronData::create("* 0-60 * * * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * 0-25 * * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * * 1-32 * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * * * 1-13 ?").is_valid()); REQUIRE_FALSE(CronData::create("* * * * * 0-7").is_valid()); - REQUIRE_FALSE(CronData::create("* * * 0-31 * *").is_valid()); - REQUIRE_FALSE(CronData::create("* * * * 0-12 *").is_valid()); - REQUIRE_FALSE(CronData::create("60 * * * * *").is_valid()); - REQUIRE_FALSE(CronData::create("* 60 * * * *").is_valid()); - REQUIRE_FALSE(CronData::create("* * 25 * * *").is_valid()); - REQUIRE_FALSE(CronData::create("* * * 32 * *").is_valid()); - REQUIRE_FALSE(CronData::create("* * * * 13 *").is_valid()); - REQUIRE_FALSE(CronData::create("* * * * * 7").is_valid()); + REQUIRE_FALSE(CronData::create("* * * 0-31 * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * * * 0-12 ?").is_valid()); + REQUIRE_FALSE(CronData::create("60 * * * * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* 60 * * * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * 25 * * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * * 32 * ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * * * 13 ?").is_valid()); + REQUIRE_FALSE(CronData::create("* * * ? * 7").is_valid()); } } } @@ -131,13 +131,13 @@ SCENARIO("Literal input") { THEN("Range is valid") { - auto c = CronData::create("* * * * JAN-MAR *"); + auto c = CronData::create("* * * * JAN-MAR ?"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_months(), 1, 3)); } AND_THEN("Range is valid") { - auto c = CronData::create("* * * * * SUN-FRI"); + auto c = CronData::create("* * * ? * SUN-FRI"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_day_of_week(), 0, 5)); } @@ -146,7 +146,7 @@ SCENARIO("Literal input") { THEN("Range is valid") { - auto c = CronData::create("* * * * JAN-MAR,DEC *"); + auto c = CronData::create("* * * * JAN-MAR,DEC ?"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_months(), 1, 3)); REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 4, 11)); @@ -154,7 +154,7 @@ SCENARIO("Literal input") } AND_THEN("Range is valid") { - auto c = CronData::create("* * * * JAN-MAR,DEC FRI,MON,THU"); + auto c = CronData::create("* * * ? JAN-MAR,DEC FRI,MON,THU"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_months(), 1, 3)); REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 4, 11)); @@ -171,7 +171,7 @@ SCENARIO("Literal input") { THEN("Range is valid") { - auto c = CronData::create("* * * * APR-JAN *"); + auto c = CronData::create("* * * ? APR-JAN *"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_months(), 4, 12)); REQUIRE(has_value_range(c.get_months(), 1, 1)); @@ -179,7 +179,7 @@ SCENARIO("Literal input") } AND_THEN("Range is valid") { - auto c = CronData::create("* * * * * sat-tue,wed"); + auto c = CronData::create("* * * ? * sat-tue,wed"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_day_of_week(), 6, 6)); // Has saturday REQUIRE(has_value_range(c.get_day_of_week(), 0, 3)); // Has sun, mon, tue, wed @@ -197,7 +197,7 @@ SCENARIO("Using step syntax") { THEN("Range is valid") { - auto c = CronData::create("* * * * JAN/2 *"); + auto c = CronData::create("* * * * JAN/2 ?"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_months(), 1, 1)); REQUIRE(has_value_range(c.get_months(), 3, 3)); @@ -225,5 +225,5 @@ SCENARIO("Dates that does not exist") SCENARIO("Date that exist in one of the months") { - REQUIRE(CronData::create("0 0 * 31 APR,MAY *").is_valid()); -} + REQUIRE(CronData::create("0 0 * 31 APR,MAY ?").is_valid()); +} \ No newline at end of file diff --git a/test/CronScheduleTest.cpp b/test/CronScheduleTest.cpp index a40f236..f76f866 100644 --- a/test/CronScheduleTest.cpp +++ b/test/CronScheduleTest.cpp @@ -77,34 +77,34 @@ bool test(const std::string& schedule, system_clock::time_point from, system_clo SCENARIO("Calculating next runtime") { - REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1), DT(2010_y / 1 / 1, hours{0}))); - REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1, hours{0}, minutes{0}, seconds{1}), DT(2010_y / 1 / 1, hours{1}))); - REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1, hours{5}), DT(2010_y / 1 / 1, hours{5}))); - REQUIRE(test("0 0 * * * *", DT(2010_y / 1 / 1, hours{5}, minutes{1}), DT(2010_y / 1 / 1, hours{6}))); - REQUIRE(test("0 0 * * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), + REQUIRE(test("0 0 * * * ?", DT(2010_y / 1 / 1), DT(2010_y / 1 / 1, hours{0}))); + REQUIRE(test("0 0 * * * ?", DT(2010_y / 1 / 1, hours{0}, minutes{0}, seconds{1}), DT(2010_y / 1 / 1, hours{1}))); + REQUIRE(test("0 0 * * * ?", DT(2010_y / 1 / 1, hours{5}), DT(2010_y / 1 / 1, hours{5}))); + REQUIRE(test("0 0 * * * ?", DT(2010_y / 1 / 1, hours{5}, minutes{1}), DT(2010_y / 1 / 1, hours{6}))); + REQUIRE(test("0 0 * * * ?", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 1 / 1, hours{0}))); - REQUIRE(test("0 0 10 * * *", DT(2017_y / 12 / 31, hours{9}, minutes{59}, seconds{58}), + REQUIRE(test("0 0 10 * * ?", DT(2017_y / 12 / 31, hours{9}, minutes{59}, seconds{58}), DT(2017_y / 12 / 31, hours{10}))); - REQUIRE(test("0 0 10 * * *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), + REQUIRE(test("0 0 10 * * ?", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 1 / 1, hours{10}))); - REQUIRE(test("0 0 10 * FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), + REQUIRE(test("0 0 10 ? FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 2 / 1, hours{10}))); - REQUIRE(test("0 0 10 25 FEB *", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), + REQUIRE(test("0 0 10 25 FEB ?", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(2018_y / 2 / 25, hours{10}))); - REQUIRE(test("0 0 10 * FEB 1", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), + REQUIRE(test("0 0 10 ? FEB 1", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(year_month_day{2018_y / 2 / mon[1]}, hours{10}))); - REQUIRE(test("0 0 10 * FEB 6", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), + REQUIRE(test("0 0 10 ? FEB 6", DT(2017_y / 12 / 31, hours{23}, minutes{59}, seconds{58}), DT(year_month_day{2018_y / 2 / sat[1]}, hours{10}))); - REQUIRE(test("* * * 10-12 NOV *", DT(2018_y / 11 / 11, hours{10}, minutes{11}, seconds{12}), + REQUIRE(test("* * ? 10-12 NOV ?", DT(2018_y / 11 / 11, hours{10}, minutes{11}, seconds{12}), DT(year_month_day{2018_y / 11 / 11}, hours{10}, minutes{11}, seconds{12}))); - REQUIRE(test("0 0 * 31 APR,MAY *", DT(2017_y / 6 / 1), DT(2018_y / may / 31))); + REQUIRE(test("0 0 * 31 APR,MAY ?", DT(2017_y / 6 / 1), DT(2018_y / may / 31))); } SCENARIO("Leap year") { REQUIRE(test("0 0 * 29 FEB *", DT(2015_y / 1 / 1), DT(2016_y / 2 / 29))); - REQUIRE(test("0 0 * 29 FEB *", DT(2018_y / 1 / 1), DT(2020_y / 2 / 29))); - REQUIRE(test("0 0 * 29 FEB *", DT(2020_y / 2 / 29, hours{15}, minutes{13}, seconds{13}), + REQUIRE(test("0 0 * 29 FEB ?", DT(2018_y / 1 / 1), DT(2020_y / 2 / 29))); + REQUIRE(test("0 0 * 29 FEB ?", DT(2020_y / 2 / 29, hours{15}, minutes{13}, seconds{13}), DT(2020_y / 2 / 29, hours{16}))); } @@ -112,7 +112,7 @@ SCENARIO("Multiple calculations") { WHEN("Every 15 minutes, every 2nd hour") { - REQUIRE(test("0 0/15 0/2 * * *", DT(2018_y / 1 / 1, hours{13}, minutes{14}, seconds{59}), + REQUIRE(test("0 0/15 0/2 * * ?", DT(2018_y / 1 / 1, hours{13}, minutes{14}, seconds{59}), {DT(2018_y / 1 / 1, hours{14}, minutes{00}), DT(2018_y / 1 / 1, hours{14}, minutes{15}), DT(2018_y / 1 / 1, hours{14}, minutes{30}), @@ -123,7 +123,7 @@ SCENARIO("Multiple calculations") WHEN("Every top of the hour, every 12th hour, during 12 and 13:th July") { - REQUIRE(test("0 0 0/12 12-13 JUL *", DT(2018_y / 1 / 1), + REQUIRE(test("0 0 0/12 12-13 JUL ?", DT(2018_y / 1 / 1), {DT(2018_y / 7 / 12, hours{0}), DT(2018_y / 7 / 12, hours{12}), DT(2018_y / 7 / 13, hours{0}), @@ -134,7 +134,7 @@ SCENARIO("Multiple calculations") WHEN("Every first of the month, 15h, every second month, 22m") { - REQUIRE(test("0 22 15 1 * *", DT(2018_y / 1 / 1), + REQUIRE(test("0 22 15 1 * ?", DT(2018_y / 1 / 1), {DT(2018_y / 1 / 1, hours{15}, minutes{22}), DT(2018_y / 2 / 1, hours{15}, minutes{22}), DT(2018_y / 3 / 1, hours{15}, minutes{22}), @@ -152,30 +152,64 @@ SCENARIO("Multiple calculations") WHEN("“At minute 0 past hour 0 and 12 on day-of-month 1 in every 2nd month") { - // Note that day-of-week, 5, is not in effect in this schedule. - REQUIRE(test("0 0 0,12 1 */2 5", DT(2018_y / 3 / 10, hours{16}, minutes{51}), DT(2018_y / 5 / 1))); + REQUIRE(test("0 0 0,12 1 */2 ?", DT(2018_y / 3 / 10, hours{16}, minutes{51}), DT(2018_y / 5 / 1))); } WHEN("“At 00:05 in August") { - // Note that day-of-week, 5, is not in effect in this schedule. - REQUIRE(test("0 5 0 * 8 *", DT(2018_y / 3 / 10, hours{16}, minutes{51}), - DT(2018_y / 8 / 1, hours{0}, minutes{5}))); + REQUIRE(test("0 5 0 * 8 ?", DT(2018_y / 3 / 10, hours{16}, minutes{51}), + {DT(2018_y / 8 / 1, hours{0}, minutes{5}), + DT(2018_y / 8 / 2, hours{0}, minutes{5})})); } WHEN("At 22:00 on every day-of-week from Monday through Friday") { - // Note that day-of-week, 5, is not in effect in this schedule. - REQUIRE(test("0 0 22 * * 1-5", DT(2021_y / 12 / 15, hours{16}, minutes{51}), + REQUIRE(test("0 0 22 ? * 1-5", DT(2021_y / 12 / 15, hours{16}, minutes{51}), {DT(2021_y / 12 / 15, hours{22}), DT(2021_y / 12 / 16, hours{22}), DT(2021_y / 12 / 17, hours{22}), - // 18-19 are weekend + // 18-19 are weekend DT(2021_y / 12 / 20, hours{22}), DT(2021_y / 12 / 21, hours{22})})); } } +SCENARIO("Examples from README.md") +{ + REQUIRE(test("* * * * * ?", DT(2018_y / 03 / 1, hours{12}, minutes{13}, seconds{45}), + { + DT(2018_y / 03 / 1, hours{12}, minutes{13}, seconds{45}), + DT(2018_y / 03 / 1, hours{12}, minutes{13}, seconds{46}), + DT(2018_y / 03 / 1, hours{12}, minutes{13}, seconds{47}), + DT(2018_y / 03 / 1, hours{12}, minutes{13}, seconds{48}) + })); + + REQUIRE(test("0 0 12 * * MON-FRI", DT(2018_y / 03 / 10, hours{12}, minutes{13}, seconds{45}), + { + DT(2018_y / 03 / 12, hours{12}), + DT(2018_y / 03 / 13, hours{12}), + DT(2018_y / 03 / 14, hours{12}), + DT(2018_y / 03 / 15, hours{12}), + DT(2018_y / 03 / 16, hours{12}), + DT(2018_y / 03 / 19, hours{12}) + })); + + REQUIRE(test("0 0 12 1/2 * ?", DT(2018_y / 01 / 2, hours{12}, minutes{13}, seconds{45}), + { + DT(2018_y / 1 / 3, hours{12}), + DT(2018_y / 1 / 5, hours{12}), + DT(2018_y / 1 / 7, hours{12}) + })); + + REQUIRE(test("0 0 */12 ? * *", DT(2018_y / 8 / 15, hours{13}, minutes{13}, seconds{45}), + { + DT(2018_y / 8 / 16, hours{0}), + DT(2018_y / 8 / 16, hours{12}), + DT(2018_y / 8 / 17, hours{0}) + })); + +} + SCENARIO("Unable to calculate time point") { // TODO: Find a schedule that is unsolvable. diff --git a/test/CronTest.cpp b/test/CronTest.cpp index 1f79fad..5dde473 100644 --- a/test/CronTest.cpp +++ b/test/CronTest.cpp @@ -14,7 +14,7 @@ std::string create_schedule_expiring_in(hours h, minutes m, seconds s) std::string res{}; res += std::to_string(dt.sec) + " "; res += std::to_string(dt.min) + " "; - res += std::to_string(dt.hour) + " * * *"; + res += std::to_string(dt.hour) + " * * ?"; return res; } @@ -34,7 +34,7 @@ SCENARIO("Adding a task") WHEN("Adding a task that runs every second") { - REQUIRE(c.add_schedule("A task", "* * * * * *", + REQUIRE(c.add_schedule("A task", "* * * * * ?", [&expired]() { expired = true;