Validation of days vs. months with more tests.

This commit is contained in:
Per Malmberg 2018-03-10 00:39:04 +01:00
parent f7442f6972
commit cb6a7958e8
4 changed files with 84 additions and 29 deletions

View File

@ -1,5 +1,8 @@
#include <date.h>
#include "CronData.h" #include "CronData.h"
using namespace date;
namespace libcron namespace libcron
{ {
@ -33,6 +36,7 @@ namespace libcron
valid &= validate_numeric<DayOfMonth>(match[4], day_of_month); valid &= validate_numeric<DayOfMonth>(match[4], day_of_month);
valid &= validate_literal<Months>(match[5], months, month_names); valid &= validate_literal<Months>(match[5], months, month_names);
valid &= validate_literal<DayOfWeek>(match[6], day_of_week, day_names); valid &= validate_literal<DayOfWeek>(match[6], day_of_week, day_names);
valid &= validate_date_vs_months();
} }
} }
@ -59,11 +63,52 @@ namespace libcron
return !s.empty() return !s.empty()
&& std::find_if(s.begin(), s.end(), && std::find_if(s.begin(), s.end(),
[](char c) [](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) bool CronData::is_between(int32_t value, int32_t low_limit, int32_t high_limt)
{ {
return value >= low_limit && value <= 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<Months>(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<int32_t> 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<Months>(month)) != months.end();
}
}
}
return res;
}
} }

View File

@ -101,6 +101,18 @@ namespace libcron
return static_cast<uint8_t>(t); return static_cast<uint8_t>(t);
} }
template<typename T>
static bool has_any_in_range(const std::set<T>& set, uint8_t low, uint8_t high)
{
bool found = false;
for (auto i = low; !found && i <= high; ++i)
{
found |= set.find(static_cast<T>(i)) != set.end();
}
return found;
}
private: private:
void parse(const std::string& cron_expression); 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 is_between(int32_t value, int32_t low_limit, int32_t high_limit);
bool validate_date_vs_months() const;
std::set<Seconds> seconds{}; std::set<Seconds> seconds{};
std::set<Minutes> minutes{}; std::set<Minutes> minutes{};
std::set<Hours> hours{}; std::set<Hours> hours{};

View File

@ -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 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("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("* * * 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") SCENARIO("Leap year")
{ {
REQUIRE(test("0 0 * 29 FEB *", DT(2018_y / 1 / 1), DT(2020_y / 2 / 29))); 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)));
}

View File

@ -21,17 +21,7 @@ bool has_value_range(const std::set<T>& set, uint8_t low, uint8_t high)
return found; return found;
} }
template<typename T>
bool has_any_in_range(const std::set<T>& set, uint8_t low, uint8_t high)
{
bool found = false;
for (auto i = low; !found && i <= high; ++i)
{
found |= set.find(static_cast<T>(i)) != set.end();
}
return found;
}
SCENARIO("Numerical inputs") SCENARIO("Numerical inputs")
{ {
@ -159,7 +149,7 @@ SCENARIO("Literal input")
auto c = CronData::create("* * * * JAN-MAR,DEC *"); auto c = CronData::create("* * * * JAN-MAR,DEC *");
REQUIRE(c.is_valid()); REQUIRE(c.is_valid());
REQUIRE(has_value_range(c.get_months(), 1, 3)); 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_months(), 12, 12));
} }
AND_THEN("Range is valid") AND_THEN("Range is valid")
@ -167,14 +157,14 @@ SCENARIO("Literal input")
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(c.is_valid());
REQUIRE(has_value_range(c.get_months(), 1, 3)); 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_months(), 12, 12));
REQUIRE(has_value_range(c.get_day_of_week(), 5, 5)); 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(), 1, 1));
REQUIRE(has_value_range(c.get_day_of_week(), 4, 4)); 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(CronData::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(CronData::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(), 6, 6));
} }
} }
AND_WHEN("Using backward range") AND_WHEN("Using backward range")
@ -185,7 +175,7 @@ SCENARIO("Literal input")
REQUIRE(c.is_valid()); REQUIRE(c.is_valid());
REQUIRE(has_value_range(c.get_months(), 4, 12)); REQUIRE(has_value_range(c.get_months(), 4, 12));
REQUIRE(has_value_range(c.get_months(), 1, 1)); 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") AND_THEN("Range is valid")
{ {
@ -193,7 +183,7 @@ SCENARIO("Literal input")
REQUIRE(c.is_valid()); 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(), 6, 6)); // Has saturday
REQUIRE(has_value_range(c.get_day_of_week(), 0, 3)); // Has sun, mon, tue, wed 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(), 7, 7));
REQUIRE(has_value_range(c.get_months(), 9, 9)); REQUIRE(has_value_range(c.get_months(), 9, 9));
REQUIRE(has_value_range(c.get_months(), 11, 11)); REQUIRE(has_value_range(c.get_months(), 11, 11));
REQUIRE_FALSE(has_any_in_range(c.get_months(), 2, 2)); REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 2, 2));
REQUIRE_FALSE(has_any_in_range(c.get_months(), 4, 4)); REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 4, 4));
REQUIRE_FALSE(has_any_in_range(c.get_months(), 6, 6)); REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 6, 6));
REQUIRE_FALSE(has_any_in_range(c.get_months(), 8, 8)); REQUIRE_FALSE(CronData::has_any_in_range(c.get_months(), 8, 8));
REQUIRE_FALSE(has_any_in_range(c.get_months(), 10, 10)); REQUIRE_FALSE(CronData::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(), 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());
}