Skip to content

Commit ad93ed4

Browse files
committed
more cleanup
1 parent ee69330 commit ad93ed4

22 files changed

Lines changed: 427 additions & 436 deletions

README.md

Lines changed: 13 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ This library is completely based on [libcron](https://github.com/PerMalmberg/lib
88

99
- Proper CMake support
1010
- date lib replaced with std::chrono implementation
11-
- Time zone clock
11+
- Time zone clock `TzClock`
1212
- Automated tests for (clang, gcc, msvc)
13+
- Correct Thread Safety Guarantees
14+
- Optional Cron Expression Caching
1315

1416
## Third party libraries
1517

@@ -30,7 +32,7 @@ using namespace std::chrono_literals;
3032
auto main() -> int {
3133
oryx::chron::Scheduler scheduler;
3234

33-
scheduler.AddSchedule("Task-1", "* * * * * ?", [](auto&) { std::cout << "Hello World\n"; });
35+
scheduler.AddSchedule("Task-1", "* * * * * ?", [](auto info) { std::cout << info.name << " called with delay" << task.delay << "\n"; });
3436
for (;;) {
3537
scheduler.Tick();
3638
std::this_thread::sleep_for(1s);
@@ -40,11 +42,7 @@ auto main() -> int {
4042
}
4143
```
4244

43-
In order to trigger execution of callbacks one must call `oryx::chron::Scheduler::Tick` at least once a second to prevent missing schedules.
44-
45-
In case there is a lot of time between you call `AddSchedule` and `Tick`, you can call `RecalculateSchedule`.
46-
47-
`oryx::chron::Taskinformation` offers a convenient API to retrieve further information:
45+
In order to trigger execution of callbacks one must call `oryx::chron::Scheduler::Tick` at least once a second to prevent missing schedules:
4846

4947
```cpp
5048
#include <thread>
@@ -58,9 +56,9 @@ using namespace std::chrono_literals;
5856
auto main() -> int {
5957
oryx::chron::Scheduler scheduler;
6058

61-
scheduler.AddSchedule("Task-1", "* * * * * ?", [](const oryx::chron::TaskInformation& task_info) {
62-
if (task_info.GetDelay() >= 1s) {
63-
std::cout << task_info.GetName() << ": my scheduler is ticking to slow\n";
59+
scheduler.AddSchedule("Task-1", "* * * * * ?", [](const oryx::chron::TaskInfo& info) {
60+
if (info.delay >= 1s) {
61+
std::cout << info.name << ": my scheduler is ticking to slow\n";
6462
}
6563
});
6664
for (;;) {
@@ -72,64 +70,9 @@ auto main() -> int {
7270
}
7371
```
7472

75-
### Adding multiple tasks with individual schedules at once
76-
77-
Add schedule needs to sort the underlying container each time you add a schedule. To improve performance when adding a batch of tasks by only sorting once you can also call AddSchedule with:
78-
79-
- `std::map<std::string, std::string>`
80-
- `std::vector<std::pair<std::string, std::string>>`
81-
- `std::vector<std::tuple<std::string, std::string>>`
82-
- `std::unordered_map<std::string, std::string>`
83-
84-
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
85-
86-
```cpp
87-
#include <chrono>
88-
#include <thread>
89-
#include <iostream>
90-
#include <unordered_map>
91-
92-
#include <oryx/chron.hpp>
93-
94-
using namespace std::chrono_literals;
95-
96-
auto main() -> int {
97-
oryx::chron::Scheduler scheduler;
98-
99-
std::unordered_map<std::string, std::string> schedules;
100-
for (int i = 1; i <= 50; i++) {
101-
schedules["Task-" + std::to_string(i)] = "* * * * * ?";
102-
}
103-
104-
auto res = scheduler.AddSchedule(schedules, [](const oryx::chron::TaskInformation& task_info) {
105-
std::cout << task_info.GetName() << ": "
106-
<< std::chrono::duration_cast<std::chrono::milliseconds>(task_info.GetDelay()) << "\n";
107-
});
108-
109-
for (;;) {
110-
scheduler.Tick();
111-
std::this_thread::sleep_for(1s);
112-
}
113-
114-
return 0;
115-
}
116-
```
117-
118-
Adding multiple with an invalid schedule:
73+
### Adding a batch of schedules at once
11974

120-
```cpp
121-
std::unordered_map<std::string, std::string> schedules;
122-
for (int i = 1; i <= 50; i++) {
123-
schedules["Task-" + std::to_string(i)] = "* * * * * ?";
124-
}
125-
schedules["Task-50"] = "invalid";
126-
127-
auto res = scheduler.AddSchedule(
128-
schedules, [](const oryx::chron::TaskInformation& task_info) { std::cout << task_info.GetName(); });
129-
if (std::get<0>(res) == false) {
130-
std::cout << "Task " << std::get<1>(res) << "has an invalid schedule: " << std::get<2>(res) << "\n";
131-
}
132-
```
75+
#### TODO
13376

13477

13578

@@ -180,7 +123,7 @@ auto main() -> int {
180123
while (!stop_requested) {
181124
task_name = "Task-" + std::to_string(counter++);
182125
scheduler.AddSchedule(task_name, "* * * * * ?",
183-
[](auto& info) { std::cout << info.GetName() << ": Called\n"; });
126+
[](TaskInfo info) { std::cout << info.name << ": Called\n"; });
184127
std::cout << "Scheduled: " << task_name << "\n";
185128
std::this_thread::sleep_for(1s);
186129
}
@@ -233,7 +176,7 @@ auto main() -> int {
233176

234177
This implementation supports cron format, as specified below.
235178

236-
Each schedule expression conststs of 6 parts, all mandatory. However, if 'day of month' specifies specific days, then 'day of week' is ignored.
179+
Each schedule expression consists of 6 parts, all mandatory. However, if 'day of month' specifies specific days, then 'day of week' is ignored.
237180

238181
```text
239182
┌──────────────seconds (0 - 59)
@@ -302,7 +245,7 @@ These special time specification tokens which replace the 5 initial time and dat
302245
|Token|Meaning
303246
| --- | --- |
304247
| @yearly | Run once a year, ie. "0 0 0 1 1 *".
305-
| @annually | Run once a year, ie. "0 0 0 1 1 *"".
248+
| @annually | Run once a year, ie. "0 0 0 1 1 *".
306249
| @monthly | Run once a month, ie. "0 0 0 1 * *".
307250
| @weekly | Run once a week, ie. "0 0 0 * * 0".
308251
| @daily | Run once a day, ie. "0 0 0 * * ?".

include/oryx/chron/chron_data.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ struct ChronData {
1212
std::set<Seconds> seconds;
1313
std::set<Minutes> minutes;
1414
std::set<Hours> hours;
15-
std::set<Days> days;
16-
std::set<Weeks> weeks;
15+
std::set<MonthDays> days;
16+
std::set<Weekdays> weeks;
1717
std::set<Months> months;
1818
};
1919

include/oryx/chron/details/all_of.hpp

Lines changed: 0 additions & 17 deletions
This file was deleted.

include/oryx/chron/details/any_of.hpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,16 @@ concept StaticCastableFromUInt8 = requires(uint8_t v) {
1414
};
1515
} // namespace traits
1616

17-
template <traits::StaticCastableFromUInt8 T>
18-
auto AnyOf(const std::set<T>& set, uint8_t low, uint8_t high) -> bool {
19-
for (auto i = low; i <= high; ++i)
20-
if (set.contains(static_cast<T>(i))) return true;
21-
return false;
22-
}
23-
2417
template <typename Enum>
2518
requires std::is_enum_v<Enum>
26-
auto AnyOf(const std::set<Enum>& set, Enum low, Enum high) -> bool {
27-
for (auto i = details::to_underlying(low); i <= details::to_underlying(high); ++i)
28-
if (set.contains(static_cast<Enum>(i))) return true;
29-
return false;
19+
auto AnyOf(const std::set<Enum>& range, Enum low, Enum high) -> bool {
20+
auto it = range.lower_bound(low);
21+
return it != range.end() && *it <= high;
22+
}
23+
24+
template <traits::StaticCastableFromUInt8 T>
25+
auto AnyOf(const std::set<T>& range, uint8_t low, uint8_t high) -> bool {
26+
return AnyOf(range, static_cast<T>(low), static_cast<T>(high));
3027
}
3128

3229
} // namespace oryx::chron::details

include/oryx/chron/details/null_mutex.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
#include <oryx/chron/traits.hpp>
4+
35
namespace oryx::chron::details {
46

57
class NullMutex {
@@ -8,4 +10,6 @@ class NullMutex {
810
void unlock() {}
911
};
1012

13+
static_assert(traits::BasicLockable<NullMutex>);
14+
1115
} // namespace oryx::chron::details

include/oryx/chron/details/parser.hpp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include <set>
44
#include <algorithm>
5+
#include <ranges>
6+
#include <type_traits>
57

68
#include <oryx/chron/traits.hpp>
79
#include <oryx/chron/chron_data.hpp>
@@ -148,31 +150,29 @@ struct Parser {
148150
return false; // Invalid format
149151
}
150152

153+
template <chron::traits::TimeType T>
154+
static auto ProcessParts(auto&& parts, std::set<T>& numbers) -> bool {
155+
return std::ranges::all_of(parts, [&numbers](auto&& part) {
156+
if constexpr (std::is_convertible_v<decltype(part), std::string_view>)
157+
return ConvertFromStringRangeToNumberRange<T>(part, numbers);
158+
else
159+
return ConvertFromStringRangeToNumberRange(std::string_view(part.begin(), part.size()), numbers);
160+
});
161+
}
162+
151163
template <chron::traits::TimeType T>
152164
static auto ValidateNumeric(std::string_view s, std::set<T>& numbers) -> bool {
153-
auto parts = details::StringSplit(s, ',');
154-
return ProcessParts(parts, numbers);
165+
return ProcessParts(std::views::split(s, ','), numbers);
155166
}
156167

157168
template <chron::traits::TimeType T>
158169
static auto ValidateLiteral(const std::string& s, std::set<T>& numbers, std::span<const std::string_view> names)
159170
-> bool {
160171
auto parts = details::StringSplit(s, ',');
161-
for (auto& part : parts) {
162-
details::ReplaceWithNumeric<T>(part, names);
163-
}
172+
std::ranges::for_each(parts, [&names](auto&& part) { details::ReplaceWithNumeric<T>(part, names); });
164173
return ProcessParts(parts, numbers);
165174
}
166175

167-
template <chron::traits::TimeType T>
168-
static auto ProcessParts(std::span<const std::string> parts, std::set<T>& numbers) -> bool {
169-
bool success{true};
170-
for (auto& part : parts) {
171-
success &= ConvertFromStringRangeToNumberRange(part, numbers);
172-
}
173-
return success;
174-
}
175-
176176
static auto CheckDomVsDow(std::string_view dom, std::string_view dow) -> bool {
177177
// Day of month and day of week are mutually exclusive so one of them must at always be ignored using
178178
// the '?'-character unless one field already is something other than '*'.
@@ -187,13 +187,13 @@ struct Parser {
187187

188188
static auto ValidateDateVsMonths(const ChronData& data) -> bool {
189189
// Only February allowed? Ensure day_of_month includes only 1..29
190-
if (data.months.size() == 1 && data.months.count(static_cast<Months>(2))) {
190+
if (data.months.size() == 1 && data.months.contains(static_cast<Months>(2))) {
191191
if (!details::AnyOf(data.days, 1, 29)) return false;
192192
}
193193

194194
// If only day 31 is selected, ensure at least one month allows it
195-
if (data.days.size() == 1 && data.days.count(Days::Last)) {
196-
if (!std::ranges::any_of(details::kMonthsWith31, [&data](Months m) { return data.months.count(m) > 0; })) {
195+
if (data.days.size() == 1 && data.days.contains(MonthDays::Last)) {
196+
if (!std::ranges::any_of(details::kMonthsWith31, [&data](Months m) { return data.months.contains(m); })) {
197197
return false;
198198
}
199199
}
Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
#pragma once
22

33
#include <charconv>
4-
#include <stdexcept>
4+
#include <cassert>
55
#include <string_view>
6-
#include <type_traits>
76

87
namespace oryx::chron::details {
98

109
template <typename T>
11-
inline auto StringCast(std::string_view sv) -> T {
10+
auto StringCast(std::string_view sv) -> T {
1211
T value{};
1312
auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), value);
14-
if (ec != std::errc{}) throw std::invalid_argument("Invalid integer");
13+
assert(ec == std::errc{} && "Casting to string failed misserably!");
1514
return value;
1615
}
1716

18-
template <typename T>
19-
requires std::is_enum_v<T>
20-
inline auto StringCast(std::string_view sv) -> T {
21-
using _UT = std::underlying_type<T>;
22-
return static_cast<T>(StringCast<_UT>(sv));
23-
}
24-
2517
} // namespace oryx::chron::details

include/oryx/chron/details/time_types.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ auto ReplaceWithNumeric(std::string& data, NamesView names) -> std::string& {
6363
}
6464

6565
inline auto ReplaceDayNameWithNumeric(std::string& data) -> std::string& {
66-
return ReplaceWithNumeric<Weeks>(data, kDayNames);
66+
return ReplaceWithNumeric<Weekdays>(data, kDayNames);
6767
}
6868

6969
inline auto ReplaceMonthNameWithNumeric(std::string& data) -> std::string& {
Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
#pragma once
22

3-
#include <utility>
43
#include <type_traits>
54

65
namespace oryx::chron::details {
76

8-
#ifndef __cpp_lib_to_underlying
9-
10-
template <typename Enum>
11-
requires std::is_enum_v<Enum>
12-
constexpr auto to_underlying(Enum e) noexcept -> std::underlying_type_t<Enum> {
13-
return static_cast<std::underlying_type_t<Enum>>(e);
7+
template <typename T>
8+
[[nodiscard]]
9+
constexpr auto to_underlying(T __value) noexcept -> std::underlying_type_t<T> {
10+
return static_cast<std::underlying_type_t<T>>(__value);
1411
}
1512

16-
#else
17-
18-
using std::to_underlying;
19-
20-
#endif
21-
2213
} // namespace oryx::chron::details

0 commit comments

Comments
 (0)