Skip to content

Commit b2e2127

Browse files
committed
changes
1 parent 2139ee2 commit b2e2127

31 files changed

Lines changed: 1683 additions & 1638 deletions

CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ file(GLOB_RECURSE ORYX_CHRON_HEADERS
6464

6565
target_sources(${PROJECT_NAME}
6666
PRIVATE
67-
src/cron_clock.cpp
68-
src/cron_data.cpp
69-
src/cron_randomization.cpp
70-
src/cron_schedule.cpp
67+
src/clock.cpp
68+
src/data.cpp
69+
src/randomization.cpp
70+
src/schedule.cpp
7171
src/task.cpp
7272
PUBLIC
7373
FILE_SET HEADERS

README.md

Lines changed: 205 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,209 @@
22

33
## WARNING!: Still in Development expect changes
44

5-
This Library is a C++20 / C++23 knockoff of [libcron](https://github.com/PerMalmberg/libcron).
5+
This Library is a C++20 / C++23 (knockoff / fork) of [libcron](https://github.com/PerMalmberg/libcron).
66

77
## Differences from libcron
88

99
- Proper CMake support
1010
- date lib replaced with std::chrono implementation
11-
- TimeZoneClock
12-
- Automated test workflows
11+
- Time zone clock
12+
- Automated tests for (clang, gcc, msvc)
1313

14-
## Build Locally
14+
## Using the Scheduler
1515

16-
```bash
17-
cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -Bbuild -H.
18-
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
19-
Only needed for clangd
16+
chron-cpp offers an easy to use API to add callbacks with corresponding cron-formatted strings:
17+
18+
```cpp
19+
#include <chrono>
20+
#include <thread>
21+
22+
#include <oryx/chron.hpp>
23+
24+
using namespace std::chrono_literals;
25+
26+
oryx::chron::Scheduler scheduler;
27+
28+
scheduler.AddSchedule("Hello from Cron", "* * * * * ?", [=](auto&) {
29+
std::cout << "Hello from libcron!\n";
30+
});
2031
```
2132
22-
```bash
23-
cmake --build build -j32
33+
To trigger the execution of callbacks, one must call `oryx::chron::Scheduler::Tick` at least once a second to prevent missing schedules:
34+
35+
```cpp
36+
for(;;)
37+
{
38+
scheduler.Tick();
39+
std::this_thread::sleep_for(1s);
40+
}
41+
```
42+
43+
In case there is a lot of time between you call `AddSchedule` and `Tick`, you can call `RecalculateSchedule`.
44+
45+
`oryx::chron::Taskinformation` offers a convenient API to retrieve further information:
46+
47+
- `oryx::chron::TaskInformation::GetDelay()` informs about the delay between planned and actual execution of the callback. Hence, it is possible to ensure that a task was executed within a specific tolerance:
48+
49+
```cpp
50+
oryx::chron::Scheduler scheduler;
51+
52+
scheduler.AddSchedule("Hello from Cron", "* * * * * ?", [=](auto& task_info) {
53+
using namespace std::chrono_literals;
54+
if (task_info.GetDelay() >= 1s)
55+
{
56+
std::cout << "The Task was executed too late...\n";
57+
}
58+
});
59+
```
60+
61+
### Adding multiple tasks with individual schedules at once
62+
63+
oryx::chron::Scheduler::AddSchedule needs to sort the underlying container each time you add a schedule. To improve performance when adding many tasks by only sorting once, there is a convinient way to pass either a `std::map<std::string, std::string>`, a `std::vector<std::pair<std::string, std::string>>`, a `std::vector<std::tuple<std::string, std::string>>` or a `std::unordered_map<std::string, std::string>` to `AddSchedule`, where the first element corresponds to the task name and the second element to the task schedule. Only if all schedules in the container are valid, they will be added to `oryx::chron::Scheduler`. The return type is a `std::tuple<bool, std::string, std::string>`, where the boolean is `true` if the schedules have been added or false otherwise. If the schedules have not been added, the second element in the tuple corresponds to the task-name with the given invalid schedule. If there are multiple invalid schedules in the container, `AddSchedule` will abort at the first invalid element:
64+
65+
```cpp
66+
std::map<std::string, std::string> name_schedule_map;
67+
for(int i = 1; i <= 1000; i++)
68+
{
69+
name_schedule_map["Task-" + std::to_string(i)] = "* * * * * ?";
70+
}
71+
name_schedule_map["Task-1000"] = "invalid";
72+
auto res = c1.AddSchedule(name_schedule_map, [](auto&) { });
73+
if (std::get<0>(res) == false)
74+
{
75+
std::cout << "Task " << std::get<1>(res)
76+
<< "has an invalid schedule: "
77+
<< std::get<2>(res) << "\n";
78+
}
79+
```
80+
81+
82+
83+
### Removing schedules from `oryx::chron::Scheduler`
84+
85+
`oryx::chron::Scheduler` offers two convenient functions to remove schedules:
86+
87+
- `ClearSchedules()` will remove all schedules
88+
- `RemoveSchedule(std::string)` will remove a specific schedule
89+
90+
For example, `scheduler.RemoveSchedule("Hello from Cron")` will remove the previously added task.
91+
92+
93+
94+
### ThreadSafe Scheduler
95+
96+
The scheduler by default is not thread safe if you need a thread safe Scheduler use `MTScheduler`. Alternatively you can also just drop in your own mutex like object. It just needs to satisfy
97+
`traits::BasicLockable`:
98+
99+
```cpp
100+
oryx::chron::MTScheduler scheduler;
101+
cron.AddSchedule("Hello from Cron", "* * * * * ?", [=](auto&) {
102+
std::cout << "I was called\n";
103+
});
104+
```
105+
106+
## Scheduler Clock
107+
108+
The following clock are available for the scheduler:
109+
110+
- (default) `LocalClock` offsets by system_clocks time
111+
- `UTCClock` offsets by 0
112+
- `TzClock` offsets by 0 until a valid timezone has been set with `TrySetTimezone`
113+
114+
## Supported formatting
115+
116+
This implementation supports cron format, as specified below.
117+
118+
Each schedule expression conststs of 6 parts, all mandatory. However, if 'day of month' specifies specific days, then 'day of week' is ignored.
119+
120+
```text
121+
┌──────────────seconds (0 - 59)
122+
│ ┌───────────── minute (0 - 59)
123+
│ │ ┌───────────── hour (0 - 23)
124+
│ │ │ ┌───────────── day of month (1 - 31)
125+
│ │ │ │ ┌───────────── month (1 - 12)
126+
│ │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday)
127+
│ │ │ │ │ │
128+
│ │ │ │ │ │
129+
│ │ │ │ │ │
130+
* * * * * *
24131
```
132+
* Allowed formats:
133+
* Special characters: '*', meaning the entire range.
134+
* '?' used to ignore day of month/day of week as noted below.
135+
136+
* Ranges: 1,2,4-6
137+
* Result: 1,2,4,5,6
138+
* Steps: n/m, where n is the start and m is the step.
139+
* `1/2` yields 1,3,5,7...<max>
140+
* `5/3` yields 5,8,11,14...<max>
141+
* `*/2` yields Result: 1,3,5,7...<max>
142+
* Reversed ranges:
143+
* `0 0 23-2 * * *`, meaning top of each minute and hour, of hours, 23, 0, 1 and 2, every day.
144+
* Compare to `0 0 2-23 * * *` which means top of each minute and hour, of hours, 2,3...21,22,23 every day.
145+
146+
147+
148+
For `month`, these (case insensitive) strings can be used instead of numbers: `JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC`.
149+
Example: `JAN,MAR,SEP-NOV`
150+
151+
For `day of week`, these (case insensitive) strings can be used instead of numbers: `SUN, MON, TUE, WED, THU, FRI, SAT`.
152+
Example: `MON-THU,SAT`
153+
154+
Each part is separated by one or more whitespaces. It is thus important to keep whitespaces out of the respective parts.
155+
156+
* Valid:
157+
* 0,3,40-50 * * * * ?
158+
159+
* Invalid:
160+
* 0, 3, 40-50 * * * * ?
161+
162+
163+
`Day of month` and `day of week` are mutually exclusive so one of them must at always be ignored using
164+
the '?'-character to ensure that it is not possible to specify a statement which results in an impossible mix of these fields.
165+
166+
### Examples
167+
168+
|Expression | Meaning
169+
| --- | --- |
170+
| * * * * * ? | Every second
171+
| 0 * * * * ? | Every minute
172+
| 0 0 12 * * MON-FRI | Every Weekday at noon
173+
| 0 0 12 1/2 * ? | Every 2 days, starting on the 1st at noon
174+
| 0 0 */12 ? * * | Every twelve hours
175+
| @hourly | Every hour
176+
177+
Note that the expression formatting has a part for seconds and the day of week.
178+
For the day of week part, a question mark ? is utilized. This format
179+
may not be parsed by all online crontab calculators or expression generators.
180+
181+
### Convenience scheduling
182+
183+
These special time specification tokens which replace the 5 initial time and date fields, and are prefixed with the '@' character, are supported:
184+
185+
|Token|Meaning
186+
| --- | --- |
187+
| @yearly | Run once a year, ie. "0 0 1 1 *".
188+
| @annually | Run once a year, ie. "0 0 1 1 *".
189+
| @monthly | Run once a month, ie. "0 0 1 * *".
190+
| @weekly | Run once a week, ie. "0 0 * * 0".
191+
| @daily | Run once a day, ie. "0 0 * * *".
192+
| @hourly | Run once an hour, ie. "0 * * * *".
193+
194+
## Randomization
195+
196+
The standard cron format does not allow for randomization, but with the use of `oryx::chron::Randomization` you can generate random
197+
schedules using the following format: `R(range_start-range_end)`, where `range_start` and `range_end` follow the same rules
198+
as for a regular cron range (step-syntax is not supported). All the rules for a regular cron expression still applies
199+
when using randomization, i.e. mutual exclusiveness and no extra spaces.
200+
201+
### Examples
202+
|Expression | Meaning
203+
| --- | --- |
204+
| 0 0 R(13-20) * * ? | On the hour, on a random hour 13-20, inclusive.
205+
| 0 0 0 ? * R(0-6) | A random weekday, every week, at midnight.
206+
| 0 R(45-15) */12 ? * * | A random minute between 45-15, inclusive, every 12 hours.
207+
|0 0 0 ? R(DEC-MAR) R(SAT-SUN)| On the hour, on a random month december to march, on a random weekday saturday to sunday.
25208

26209
## Adding this library to your project
27210

@@ -44,6 +227,18 @@ target_link_libraries(my_project PUBLIC
44227
)
45228
```
46229

230+
## Build Locally
231+
232+
```bash
233+
cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -Bbuild -H.
234+
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
235+
Only needed for clangd
236+
```
237+
238+
```bash
239+
cmake --build build -j32
240+
```
241+
47242
## IDE Setup VsCode
48243

49244
### Clangd Extension (Recommended)

include/oryx/chron.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#pragma once
2+
3+
#include <oryx/chron/clock.hpp>
4+
#include <oryx/chron/schedule.hpp>

include/oryx/chron/clock.hpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
3+
#include <string_view>
4+
#include <mutex>
5+
#include <chrono>
6+
7+
#include "traits.hpp"
8+
9+
namespace oryx::chron {
10+
11+
class UTCClock {
12+
public:
13+
auto Now() const -> std::chrono::system_clock::time_point { return std::chrono::system_clock::now(); }
14+
auto UtcOffset(std::chrono::system_clock::time_point) const -> std::chrono::seconds {
15+
using namespace std::chrono;
16+
return 0s;
17+
}
18+
};
19+
20+
class LocalClock {
21+
public:
22+
auto Now() const -> std::chrono::system_clock::time_point {
23+
auto now = std::chrono::system_clock::now();
24+
return now + UtcOffset(now);
25+
}
26+
27+
auto UtcOffset(std::chrono::system_clock::time_point now) const -> std::chrono::seconds;
28+
};
29+
30+
class TzClock {
31+
public:
32+
auto Now() const -> std::chrono::system_clock::time_point {
33+
auto now = std::chrono::system_clock::now();
34+
return now + UtcOffset(now);
35+
}
36+
37+
auto TrySetTimezone(std::string_view name) -> bool;
38+
auto UtcOffset(std::chrono::system_clock::time_point now) const -> std::chrono::seconds;
39+
40+
private:
41+
mutable std::mutex mtx_{};
42+
const std::chrono::time_zone* timezone_{};
43+
};
44+
45+
static_assert(traits::Clock<LocalClock>);
46+
static_assert(traits::Clock<UTCClock>);
47+
static_assert(traits::Clock<TzClock>);
48+
49+
} // namespace oryx::chron

0 commit comments

Comments
 (0)