From cb6a7958e84cd61e826dbfafadabd82fae6c0be4 Mon Sep 17 00:00:00 2001 From: Per Malmberg Date: Sat, 10 Mar 2018 00:39:04 +0100 Subject: [PATCH] Validation of days vs. months with more tests. --- libcron/CronData.cpp | 47 ++++++++++++++++++++++++++++++++++++++- libcron/CronData.h | 14 ++++++++++++ test/CronScheduleTest.cpp | 6 +---- test/test.cpp | 46 +++++++++++++++++++------------------- 4 files changed, 84 insertions(+), 29 deletions(-) diff --git a/libcron/CronData.cpp b/libcron/CronData.cpp index e6ce890..9496829 100644 --- a/libcron/CronData.cpp +++ b/libcron/CronData.cpp @@ -1,5 +1,8 @@ +#include #include "CronData.h" +using namespace date; + namespace libcron { @@ -33,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 &= validate_date_vs_months(); } } @@ -59,11 +63,52 @@ namespace libcron return !s.empty() && std::find_if(s.begin(), s.end(), [](char c) - { return !std::isdigit(c); }) == s.end(); + { + return !std::isdigit(c); + }) == s.end(); } bool CronData::is_between(int32_t value, int32_t low_limit, int32_t high_limt) { return value >= low_limit && value <= high_limt; } + + bool CronData::validate_date_vs_months() const + { + bool res = true; + + // Verify that the available dates are possible based on the given months + if (months.find(static_cast(2)) != months.end()) + { + // February allowed, make sure that the allowed date(s) includes 29 and below. + res = has_any_in_range(day_of_month, 1, 29); + } + + 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()) + { + std::vector months_with_31; + for (int32_t i = 1; i <= 12; ++i) + { + auto ymd = 2018_y / i / date::last; + if (unsigned(ymd.day()) == 31) + { + months_with_31.push_back(i); + } + } + + res = false; + for(size_t i = 0; !res && i < months_with_31.size(); ++i) + { + auto month = months_with_31[i]; + res = months.find(static_cast(month)) != months.end(); + } + } + } + + + return res; + } } \ No newline at end of file diff --git a/libcron/CronData.h b/libcron/CronData.h index a418902..60c6832 100644 --- a/libcron/CronData.h +++ b/libcron/CronData.h @@ -101,6 +101,18 @@ namespace libcron return static_cast(t); } + template + static bool has_any_in_range(const std::set& set, uint8_t low, uint8_t high) + { + bool found = false; + for (auto i = low; !found && i <= high; ++i) + { + found |= set.find(static_cast(i)) != set.end(); + } + + return found; + } + private: void parse(const std::string& cron_expression); @@ -134,6 +146,8 @@ namespace libcron bool is_between(int32_t value, int32_t low_limit, int32_t high_limit); + bool validate_date_vs_months() const; + std::set seconds{}; std::set minutes{}; std::set hours{}; diff --git a/test/CronScheduleTest.cpp b/test/CronScheduleTest.cpp index 091f6c8..caeb199 100644 --- a/test/CronScheduleTest.cpp +++ b/test/CronScheduleTest.cpp @@ -53,14 +53,10 @@ SCENARIO("Calculating next runtime") 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}), 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}), 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))); } SCENARIO("Leap year") { REQUIRE(test("0 0 * 29 FEB *", DT(2018_y / 1 / 1), DT(2020_y / 2 / 29))); -} - -SCENARIO("Date that does not exist") -{ - //REQUIRE_FALSE(test("0 0 * 30 FEB *", DT(2018_y / 1 / 1), DT(2020_y / 2 / 29))); } \ No newline at end of file diff --git a/test/test.cpp b/test/test.cpp index 53cd06c..21208ff 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -21,17 +21,7 @@ bool has_value_range(const std::set& set, uint8_t low, uint8_t high) return found; } -template -bool has_any_in_range(const std::set& set, uint8_t low, uint8_t high) -{ - bool found = false; - for (auto i = low; !found && i <= high; ++i) - { - found |= set.find(static_cast(i)) != set.end(); - } - return found; -} SCENARIO("Numerical inputs") { @@ -159,7 +149,7 @@ SCENARIO("Literal input") auto c = CronData::create("* * * * JAN-MAR,DEC *"); REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_months(), 1, 3)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 4, 11)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 4, 11)); REQUIRE(has_value_range(c.get_months(), 12, 12)); } AND_THEN("Range is valid") @@ -167,14 +157,14 @@ SCENARIO("Literal input") 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(has_any_in_range(c.get_months(), 4, 11)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 4, 11)); REQUIRE(has_value_range(c.get_months(), 12, 12)); REQUIRE(has_value_range(c.get_day_of_week(), 5, 5)); REQUIRE(has_value_range(c.get_day_of_week(), 1, 1)); REQUIRE(has_value_range(c.get_day_of_week(), 4, 4)); - REQUIRE_FALSE(has_any_in_range(c.get_day_of_week(), 0, 0)); - REQUIRE_FALSE(has_any_in_range(c.get_day_of_week(), 2, 3)); - REQUIRE_FALSE(has_any_in_range(c.get_day_of_week(), 6, 6)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_day_of_week(), 0, 0)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_day_of_week(), 2, 3)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_day_of_week(), 6, 6)); } } AND_WHEN("Using backward range") @@ -185,7 +175,7 @@ SCENARIO("Literal input") REQUIRE(c.is_valid()); REQUIRE(has_value_range(c.get_months(), 4, 12)); REQUIRE(has_value_range(c.get_months(), 1, 1)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 2, 3)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 2, 3)); } AND_THEN("Range is valid") { @@ -193,7 +183,7 @@ SCENARIO("Literal input") 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 - REQUIRE_FALSE(has_any_in_range(c.get_day_of_week(), 4, 5)); // Does not have thu or fri. + REQUIRE_FALSE(CronData::has_any_in_range(c.get_day_of_week(), 4, 5)); // Does not have thu or fri. } } } @@ -215,15 +205,25 @@ SCENARIO("Using step syntax") REQUIRE(has_value_range(c.get_months(), 7, 7)); REQUIRE(has_value_range(c.get_months(), 9, 9)); REQUIRE(has_value_range(c.get_months(), 11, 11)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 2, 2)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 4, 4)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 6, 6)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 8, 8)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 10, 10)); - REQUIRE_FALSE(has_any_in_range(c.get_months(), 12, 12)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 2, 2)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 4, 4)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 6, 6)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 8, 8)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 10, 10)); + REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 12, 12)); } } } } +SCENARIO("Dates that does not exist") +{ + REQUIRE_FALSE(CronData::create("0 0 * 30 FEB *").is_valid()); + REQUIRE_FALSE(CronData::create("0 0 * 31 APR *").is_valid()); +} + +SCENARIO("Date that exist in one of the months") +{ + REQUIRE(CronData::create("0 0 * 31 APR,MAY *").is_valid()); +}