diff --git a/libcron/CMakeLists.txt b/libcron/CMakeLists.txt index e2373ae..9fdd111 100644 --- a/libcron/CMakeLists.txt +++ b/libcron/CMakeLists.txt @@ -8,4 +8,11 @@ include_directories(externals/date) add_library(${PROJECT_NAME} Cron.h - Cron.cpp Task.h CronData.h TimeTypes.h CronData.cpp) + Cron.cpp + Task.h + CronData.h + TimeTypes.h + CronData.cpp + CronSchedule.cpp + CronSchedule.h + externals/date/date.h DateTime.h) diff --git a/libcron/Cron.cpp b/libcron/Cron.cpp index 9cc49fa..06cf09e 100644 --- a/libcron/Cron.cpp +++ b/libcron/Cron.cpp @@ -7,7 +7,7 @@ bool libcron::Cron::add_schedule(const std::string &schedule, std::function -#include "TimeTypes.h" #include -#include #include #include +#include "TimeTypes.h" namespace libcron { @@ -96,6 +95,12 @@ namespace libcron return day_of_week; } + template + static uint8_t value_of(T t) + { + return static_cast(t); + } + private: void parse(const std::string& cron_expression); @@ -123,12 +128,6 @@ namespace libcron template bool get_step(const std::string& s, uint8_t& start, uint8_t& step); - template - uint8_t value_of(T t) - { - return static_cast(t); - } - std::vector split(const std::string& s, char token); bool is_number(const std::string& s); diff --git a/libcron/CronSchedule.cpp b/libcron/CronSchedule.cpp new file mode 100644 index 0000000..5b87a1d --- /dev/null +++ b/libcron/CronSchedule.cpp @@ -0,0 +1,61 @@ +#include "CronSchedule.h" + +using namespace std::chrono; +using namespace date; + +namespace libcron +{ + + std::chrono::system_clock::time_point + CronSchedule::calculate_from(const std::chrono::system_clock::time_point& from) + { + auto curr = from; + + year_month_day ymd = date::floor(curr); + + // Add months until one of the allowed days are found, or stay at the current one. + while (data.get_months().find(static_cast(unsigned(ymd.month()))) == data.get_months().end()) + { + curr += months{1}; + ymd = date::floor(curr); + }; + + + // 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 (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. + while (data.get_day_of_month().find(static_cast(unsigned(ymd.day()))) == + data.get_day_of_month().end()) + { + curr += days{1}; + ymd = date::floor(curr); + }; + } + else + { + //Add days until the current weekday is one of the allowed weekdays + year_month_weekday ymw = date::floor(curr); + + while (data.get_day_of_week().find(static_cast(unsigned(ymw.weekday()))) == + data.get_day_of_week().end()) + { + curr += days{1}; + ymw = date::floor(curr); + }; + } + + // 'curr' now represents the next year, month and day matching the expression, with a time of 0:0:0. + + // Re-add the hours, minutes and seconds to 'curr' + auto date_time = to_calendar_time(from); + curr += hours{date_time.hour}; + curr += minutes{date_time.min}; + curr += seconds{date_time.sec}; + + auto t = to_calendar_time(from); + + return curr; + } +} \ No newline at end of file diff --git a/libcron/CronSchedule.h b/libcron/CronSchedule.h new file mode 100644 index 0000000..f78c909 --- /dev/null +++ b/libcron/CronSchedule.h @@ -0,0 +1,44 @@ +#pragma once + +#include "CronData.h" +#include +#include "externals/date/date.h" +#include "DateTime.h" + +namespace libcron +{ + class CronSchedule + { + public: + explicit CronSchedule(CronData data) + : data(std::move(data)) + { + } + + std::chrono::system_clock::time_point calculate_from(const std::chrono::system_clock::time_point& from); + + // https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes#obtaining-ymd-hms-components-from-a-time_point + static DateTime to_calendar_time(std::chrono::system_clock::time_point time) + { + auto daypoint = date::floor(time); + auto ymd = date::year_month_day(daypoint); // calendar date + auto tod = date::make_time(time - daypoint); // Yields time_of_day type + + // Obtain individual components as integers + DateTime dt{ + int(ymd.year()), + unsigned(ymd.month()), + unsigned(ymd.day()), + static_cast(tod.hours().count()), + static_cast(tod.minutes().count()), + static_cast(tod.seconds().count())}; + + return dt; + } + + + private: + CronData data; + }; + +} \ No newline at end of file diff --git a/libcron/DateTime.h b/libcron/DateTime.h new file mode 100644 index 0000000..a48c431 --- /dev/null +++ b/libcron/DateTime.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace libcron +{ + struct DateTime + { + int year = 0; + unsigned month = 0; + unsigned day = 0; + uint8_t hour = 0; + uint8_t min = 0; + uint8_t sec = 0; + }; +} diff --git a/libcron/Task.h b/libcron/Task.h index 0cb235b..b44a5f6 100644 --- a/libcron/Task.h +++ b/libcron/Task.h @@ -2,25 +2,30 @@ #include #include "CronData.h" +#include "CronSchedule.h" namespace libcron { class Task { - public: + public: - Task(CronData time, std::function task) - : time(std::move(time)), task(std::move(task)) - { - } + Task(CronSchedule schedule, std::function task) + : schedule(std::move(schedule))//, task(std::move(task)) + { + } - bool operator<(const Task& other) const - { - return false; - } + Task(const Task&) = default; - private: - CronData time{}; - std::function task; + Task& operator=(const Task&) = default; + + bool operator<(const Task& other) const + { + return false; + } + + private: + CronSchedule schedule; + //std::function task; }; } \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9bd8c4c..dda57bf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -fsanitize=address -las include_directories( externals/Catch2/single_include/ .. + ../libcron/externals/date ) add_executable( diff --git a/test/test.cpp b/test/test.cpp index 7fc38b6..56bb18b 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1,11 +1,13 @@ #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file #include - +#include #include #include using namespace libcron; +using namespace date; +using namespace std::chrono; template bool has_value_range(const std::set& set, uint8_t low, uint8_t high) @@ -231,18 +233,30 @@ SCENARIO("Calculating next runtime") { auto c = CronData::create("0 0 * * * *"); REQUIRE(c.is_valid()); + CronSchedule sched(c); WHEN("Start time is midnight") { -// std::chrono::system_clock::time_point run_time = c.calculate_from(midnight); -// THEN("Next runtime is 01:00") -// { -// auto t = std::chrono::system_clock::to_time_t(run_time); + sys_days midnight = 2010_y/1/1; + std::chrono::system_clock::time_point run_time = sched.calculate_from(midnight); + + THEN("Next runtime is 01:00 of the same date") + { + auto t = CronSchedule::to_calendar_time(run_time); + REQUIRE(t.year == 2010); + REQUIRE(t.month == 1); + REQUIRE(t.day == 1); + REQUIRE(t.hour == 1); + REQUIRE(t.min == 0); + REQUIRE(t.sec == 0); + + + // auto t = std::chrono::system_clock::to_time_t(run_time); // REQUIRE(t.get_seconds() == 0); // REQUIRE(t.minute == 0); // REQUIRE(t.hour == 1); // REQUIRE(t.) -// } + } }