├── .idea
├── libcron.iml
├── misc.xml
├── vcs.xml
├── modules.xml
└── runConfigurations
│ └── All.xml
├── .gitmodules
├── libcron
├── include
│ └── libcron
│ │ ├── DateTime.h
│ │ ├── TimeTypes.h
│ │ ├── CronClock.h
│ │ ├── CronSchedule.h
│ │ ├── Task.h
│ │ ├── TaskQueue.h
│ │ ├── CronRandomization.h
│ │ ├── Cron.h
│ │ └── CronData.h
├── src
│ ├── CronClock.cpp
│ ├── Task.cpp
│ ├── CronSchedule.cpp
│ ├── CronRandomization.cpp
│ └── CronData.cpp
└── CMakeLists.txt
├── CMakeLists.txt
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── test
├── CMakeLists.txt
├── CronRandomizationTest.cpp
├── CronScheduleTest.cpp
├── CronDataTest.cpp
└── CronTest.cpp
├── uncrustify.cfg
└── README.md
/.idea/libcron.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libcron/externals/date"]
2 | path = libcron/externals/date
3 | url = https://github.com/HowardHinnant/date.git
4 | [submodule "test/externals/Catch2"]
5 | path = test/externals/Catch2
6 | url = https://github.com/catchorg/Catch2.git
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/libcron/include/libcron/DateTime.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace libcron
6 | {
7 | struct DateTime
8 | {
9 | int year = 0;
10 | unsigned month = 0;
11 | unsigned day = 0;
12 | uint8_t hour = 0;
13 | uint8_t min = 0;
14 | uint8_t sec = 0;
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.6)
2 |
3 | project(top)
4 | add_subdirectory(libcron)
5 | add_subdirectory(test)
6 |
7 | add_dependencies(cron_test libcron)
8 |
9 | install(TARGETS libcron DESTINATION lib)
10 | install(DIRECTORY libcron/include/libcron DESTINATION include)
11 | install(DIRECTORY libcron/externals/date/include/date DESTINATION include)
12 |
13 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/All.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: libcron tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | Tests:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | with:
11 | submodules: true
12 | - name: Build
13 | run: |
14 | mkdir build
15 | cd build
16 | cmake ..
17 | make -j4
18 | - name: Test
19 | run: |
20 | cd test/out
21 | ./cron_test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
34 | cmake-build-*
35 | .idea/workspace.xml
36 | out/*
37 | test/out*
38 |
--------------------------------------------------------------------------------
/libcron/src/CronClock.cpp:
--------------------------------------------------------------------------------
1 | #include "libcron/CronClock.h"
2 |
3 | #ifdef WIN32
4 | #ifndef NOMINMAX
5 | #define NOMINMAX
6 | #endif
7 | #define WIN32_LEAN_AND_MEAN
8 | #include
9 | #endif
10 |
11 | using namespace std::chrono;
12 |
13 | namespace libcron
14 | {
15 |
16 | std::chrono::seconds LocalClock::utc_offset(std::chrono::system_clock::time_point now) const
17 | {
18 | #ifdef WIN32
19 | (void)now;
20 |
21 | TIME_ZONE_INFORMATION tz_info{};
22 | seconds offset{ 0 };
23 |
24 | auto res = GetTimeZoneInformation(&tz_info);
25 | if (res != TIME_ZONE_ID_INVALID)
26 | {
27 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
28 | // UTC = local time + bias => local_time = utc - bias, so UTC offset is -bias
29 | offset = minutes{ -tz_info.Bias };
30 | }
31 | #else
32 | auto t = system_clock::to_time_t(now);
33 | tm tm{};
34 | localtime_r(&t, &tm);
35 | seconds offset{ tm.tm_gmtoff };
36 | #endif
37 | return offset;
38 | }
39 | }
--------------------------------------------------------------------------------
/libcron/include/libcron/TimeTypes.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace libcron
6 | {
7 | enum class Seconds : int8_t
8 | {
9 | First = 0,
10 | Last = 59
11 | };
12 |
13 | enum class Minutes : int8_t
14 | {
15 | First = 0,
16 | Last = 59
17 | };
18 |
19 | enum class Hours : int8_t
20 | {
21 | First = 0,
22 | Last = 23
23 | };
24 |
25 | enum class DayOfMonth : uint8_t
26 | {
27 | First = 1,
28 | Last = 31
29 | };
30 |
31 | enum class Months : uint8_t
32 | {
33 | First = 1,
34 | January = First,
35 | February,
36 | March,
37 | April,
38 | May,
39 | June,
40 | July,
41 | August,
42 | September,
43 | October,
44 | November,
45 | December = 12,
46 | Last = December
47 | };
48 |
49 | enum class DayOfWeek : uint8_t
50 | {
51 | // Sunday = 0 ... Saturday = 6
52 | First = 0,
53 | Last = 6,
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Per Malmberg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/libcron/include/libcron/CronClock.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace libcron
6 | {
7 | class ICronClock
8 | {
9 | public:
10 | virtual std::chrono::system_clock::time_point now() const = 0;
11 | virtual std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const = 0;
12 | };
13 |
14 | class UTCClock
15 | : public ICronClock
16 | {
17 | public:
18 | std::chrono::system_clock::time_point now() const override
19 | {
20 | return std::chrono::system_clock::now();
21 | }
22 |
23 | std::chrono::seconds utc_offset(std::chrono::system_clock::time_point) const override
24 | {
25 | using namespace std::chrono;
26 | return 0s;
27 | }
28 | };
29 |
30 | class LocalClock
31 | : public ICronClock
32 | {
33 | public:
34 | std::chrono::system_clock::time_point now() const override
35 | {
36 | auto now = std::chrono::system_clock::now();
37 | return now + utc_offset(now);
38 | }
39 |
40 | std::chrono::seconds utc_offset(std::chrono::system_clock::time_point now) const override;
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/test/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.6)
2 | project(cron_test)
3 |
4 | set(CMAKE_CXX_STANDARD 17)
5 |
6 | # Deactivate Iterator-Debugging on Windows
7 | option(LIBCRON_DEACTIVATE_ITERATOR_DEBUGGING "Build with iterator-debugging (MSVC only)." OFF)
8 |
9 | if( MSVC )
10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
11 |
12 | if (LIBCRON_DEACTIVATE_ITERATOR_DEBUGGING)
13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_HAS_ITERATOR_DEBUGGING=0")
14 | endif()
15 | else()
16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
17 | endif()
18 |
19 | include_directories(
20 | ${CMAKE_CURRENT_LIST_DIR}/externals/Catch2/single_include/catch2
21 | ${CMAKE_CURRENT_LIST_DIR}/../libcron/externals/date/include
22 | ${CMAKE_CURRENT_LIST_DIR}/..
23 | )
24 |
25 | add_executable(
26 | ${PROJECT_NAME}
27 | CronDataTest.cpp
28 | CronRandomizationTest.cpp
29 | CronScheduleTest.cpp
30 | CronTest.cpp)
31 |
32 | if(NOT MSVC)
33 | target_link_libraries(${PROJECT_NAME} libcron pthread)
34 |
35 | # Assume a modern compiler supporting uncaught_exceptions()
36 | target_compile_definitions (${PROJECT_NAME} PRIVATE -DHAS_UNCAUGHT_EXCEPTIONS)
37 | else()
38 | target_link_libraries(${PROJECT_NAME} libcron)
39 | endif()
40 |
41 | set_target_properties(${PROJECT_NAME} PROPERTIES
42 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
43 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out"
44 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out")
45 |
--------------------------------------------------------------------------------
/libcron/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.6)
2 | project(libcron)
3 |
4 | set(CMAKE_CXX_STANDARD 17)
5 |
6 | # Deactivate Iterator-Debugging on Windows
7 | option(LIBCRON_DEACTIVATE_ITERATOR_DEBUGGING "Build with iterator-debugging (MSVC only)." OFF)
8 |
9 | if( MSVC )
10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
11 |
12 | if (LIBCRON_DEACTIVATE_ITERATOR_DEBUGGING)
13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_HAS_ITERATOR_DEBUGGING=0")
14 | endif()
15 | else()
16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
17 | endif()
18 |
19 | add_library(${PROJECT_NAME}
20 | include/libcron/Cron.h
21 | include/libcron/CronClock.h
22 | include/libcron/CronData.h
23 | include/libcron/CronRandomization.h
24 | include/libcron/CronSchedule.h
25 | include/libcron/DateTime.h
26 | include/libcron/Task.h
27 | include/libcron/TimeTypes.h
28 | src/CronClock.cpp
29 | src/CronData.cpp
30 | src/CronRandomization.cpp
31 | src/CronSchedule.cpp
32 | src/Task.cpp)
33 |
34 | target_include_directories(${PROJECT_NAME}
35 | PUBLIC ${CMAKE_CURRENT_LIST_DIR}/externals/date/include
36 | PUBLIC include)
37 |
38 | if(NOT MSVC)
39 | # Assume a modern compiler (gcc 9.3)
40 | target_compile_definitions (${PROJECT_NAME} PRIVATE -DHAS_UNCAUGHT_EXCEPTIONS)
41 | endif()
42 |
43 | set_target_properties(${PROJECT_NAME} PROPERTIES
44 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/${CMAKE_BUILD_TYPE}"
45 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/${CMAKE_BUILD_TYPE}"
46 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/${CMAKE_BUILD_TYPE}")
47 |
--------------------------------------------------------------------------------
/libcron/include/libcron/CronSchedule.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "libcron/CronData.h"
4 | #include
5 | #if defined(_MSC_VER)
6 | #pragma warning(push)
7 | #pragma warning(disable:4244)
8 | #endif
9 | #include
10 | #if defined(_MSC_VER)
11 | #pragma warning(pop)
12 | #endif
13 |
14 | #include "libcron/DateTime.h"
15 |
16 | namespace libcron
17 | {
18 | class CronSchedule
19 | {
20 | public:
21 | explicit CronSchedule(CronData& data)
22 | : data(data)
23 | {
24 | }
25 |
26 | CronSchedule(const CronSchedule&) = default;
27 |
28 | CronSchedule& operator=(const CronSchedule&) = default;
29 |
30 | std::tuple
31 | calculate_from(const std::chrono::system_clock::time_point& from) const;
32 |
33 | // https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes#obtaining-ymd-hms-components-from-a-time_point
34 | static DateTime to_calendar_time(std::chrono::system_clock::time_point time)
35 | {
36 | auto daypoint = date::floor(time);
37 | auto ymd = date::year_month_day(daypoint); // calendar date
38 | auto time_of_day = date::make_time(time - daypoint); // Yields time_of_day type
39 |
40 | // Obtain individual components as integers
41 | DateTime dt{
42 | int(ymd.year()),
43 | unsigned(ymd.month()),
44 | unsigned(ymd.day()),
45 | static_cast(time_of_day.hours().count()),
46 | static_cast(time_of_day.minutes().count()),
47 | static_cast(time_of_day.seconds().count())};
48 |
49 | return dt;
50 | }
51 |
52 | private:
53 | CronData data;
54 | };
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/libcron/src/Task.cpp:
--------------------------------------------------------------------------------
1 | #include "libcron/Task.h"
2 |
3 | using namespace std::chrono;
4 |
5 | namespace libcron
6 | {
7 |
8 | bool Task::calculate_next(std::chrono::system_clock::time_point from)
9 | {
10 | auto result = schedule.calculate_from(from);
11 |
12 | // In case the calculation fails, the task will no longer expire.
13 | valid = std::get<0>(result);
14 | if (valid)
15 | {
16 | next_schedule = std::get<1>(result);
17 |
18 | // Make sure that the task is allowed to run.
19 | last_run = next_schedule - 1s;
20 | }
21 |
22 | return valid;
23 | }
24 |
25 | bool Task::is_expired(std::chrono::system_clock::time_point now) const
26 | {
27 | return valid && now >= last_run && time_until_expiry(now) == 0s;
28 | }
29 |
30 | std::chrono::system_clock::duration Task::time_until_expiry(std::chrono::system_clock::time_point now) const
31 | {
32 | system_clock::duration d{};
33 |
34 | // Explicitly return 0s instead of a possibly negative duration when it has expired.
35 | if (now >= next_schedule)
36 | {
37 | d = 0s;
38 | }
39 | else
40 | {
41 | d = next_schedule - now;
42 | }
43 |
44 | return d;
45 | }
46 |
47 | std::string Task::get_status(std::chrono::system_clock::time_point now) const
48 | {
49 | std::string s = "'";
50 | s+= get_name();
51 | s += "' expires in ";
52 | s += std::to_string(duration_cast(time_until_expiry(now)).count());
53 | s += "ms => ";
54 |
55 | auto dt = CronSchedule::to_calendar_time(next_schedule);
56 | s+= std::to_string(dt.year) + "-";
57 | s+= std::to_string(dt.month) + "-";
58 | s+= std::to_string(dt.day) + " ";
59 | s+= std::to_string(dt.hour) + ":";
60 | s+= std::to_string(dt.min) + ":";
61 | s+= std::to_string(dt.sec);
62 | return s;
63 | }
64 | }
--------------------------------------------------------------------------------
/libcron/include/libcron/Task.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include "CronData.h"
7 | #include "CronSchedule.h"
8 |
9 | namespace libcron
10 | {
11 | class TaskInformation
12 | {
13 | public:
14 | virtual ~TaskInformation() = default;
15 | virtual std::chrono::system_clock::duration get_delay() const = 0;
16 | virtual std::string get_name() const = 0;
17 | };
18 |
19 | class Task : public TaskInformation
20 | {
21 | public:
22 | using TaskFunction = std::function;
23 |
24 | Task(std::string name, const CronSchedule schedule, TaskFunction task)
25 | : name(std::move(name)), schedule(std::move(schedule)), task(std::move(task))
26 | {
27 | }
28 |
29 | void execute(std::chrono::system_clock::time_point now)
30 | {
31 | // Next Schedule is still the current schedule, calculate delay (actual execution - planned execution)
32 | delay = now - next_schedule;
33 |
34 | last_run = now;
35 | task(*this);
36 | }
37 |
38 | std::chrono::system_clock::duration get_delay() const override
39 | {
40 | return delay;
41 | }
42 |
43 | Task(const Task& other) = default;
44 |
45 | Task& operator=(const Task&) = default;
46 |
47 | bool calculate_next(std::chrono::system_clock::time_point from);
48 |
49 | bool operator>(const Task& other) const
50 | {
51 | return next_schedule > other.next_schedule;
52 | }
53 |
54 | bool operator<(const Task& other) const
55 | {
56 | return next_schedule < other.next_schedule;
57 | }
58 |
59 | bool is_expired(std::chrono::system_clock::time_point now) const;
60 |
61 | std::chrono::system_clock::duration
62 | time_until_expiry(std::chrono::system_clock::time_point now) const;
63 |
64 | std::string get_name() const override
65 | {
66 | return name;
67 | }
68 |
69 | std::string get_status(std::chrono::system_clock::time_point now) const;
70 |
71 | private:
72 | std::string name;
73 | CronSchedule schedule;
74 | std::chrono::system_clock::time_point next_schedule;
75 | std::chrono::system_clock::duration delay = std::chrono::seconds(-1);
76 | TaskFunction task;
77 | bool valid = false;
78 | std::chrono::system_clock::time_point last_run = std::numeric_limits::min();
79 | };
80 | }
81 |
82 | inline bool operator==(const std::string &lhs, const libcron::Task &rhs)
83 | {
84 | return lhs == rhs.get_name();
85 | }
86 |
87 | inline bool operator==(const libcron::Task &lhs, const std::string &rhs)
88 | {
89 | return lhs.get_name() == rhs;
90 | }
91 |
92 | inline bool operator!=(const std::string &lhs, const libcron::Task &rhs)
93 | {
94 | return !(lhs == rhs);
95 | }
96 |
97 | inline bool operator!=(const libcron::Task &lhs, const std::string &rhs)
98 | {
99 | return !(lhs == rhs);
100 | }
101 |
--------------------------------------------------------------------------------
/libcron/include/libcron/TaskQueue.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include