Skip to content

Commit d9f4aa2

Browse files
committed
feat(config): Add new helper function for parsing human readable durations
Signed-off-by: Steffen Vogel <[email protected]>
1 parent f00bf85 commit d9f4aa2

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

common/include/villas/timing.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
#pragma once
99

10-
#include <cstdint>
10+
#include <chrono>
1111
#include <cstdio>
1212
#include <ctime>
1313

@@ -33,3 +33,14 @@ double time_to_double(const struct timespec *ts);
3333

3434
// Convert double containing seconds after 1970 to timespec.
3535
struct timespec time_from_double(double secs);
36+
37+
// Convert timespec to an std::chrono::time_point.
38+
template <typename Duration = std::chrono::nanoseconds>
39+
std::chrono::time_point<std::chrono::system_clock, Duration>
40+
time_to_timepoint(const struct timespec *ts) {
41+
auto dur =
42+
std::chrono::seconds(ts->tv_sec) + std::chrono::nanoseconds(ts->tv_nsec);
43+
44+
return std::chrono::time_point<std::chrono::system_clock, Duration>(
45+
std::chrono::duration_cast<Duration>(dur));
46+
}

include/villas/config_helper.hpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@
77

88
#pragma once
99

10+
#include <chrono>
11+
#include <regex>
12+
#include <stdexcept>
13+
1014
#include <jansson.h>
1115

1216
#include <villas/node/config.hpp>
1317
#include <villas/sample.hpp>
1418

19+
#include "villas/exceptions.hpp"
20+
1521
#ifdef WITH_CONFIG
1622
#include <libconfig.h>
1723
#endif
@@ -42,5 +48,93 @@ int json_object_extend(json_t *orig, json_t *merge);
4248

4349
json_t *json_load_cli(int argc, const char *argv[]);
4450

51+
template <typename Duration = std::chrono::nanoseconds>
52+
Duration parse_duration(std::string_view input) {
53+
using namespace std::literals::chrono_literals;
54+
55+
// Map unit strings to their corresponding chrono durations
56+
static const std::unordered_map<std::string, std::chrono::nanoseconds>
57+
unit_map = {
58+
{"d", 24h}, // days
59+
{"h", 1h}, // hours
60+
{"m", 1min}, // minutes
61+
{"s", 1s}, // seconds
62+
{"ms", 1ms}, // milliseconds
63+
{"us", 1us}, // microseconds
64+
{"ns", 1ns} // nanoseconds
65+
};
66+
67+
std::regex token_re(R"((\d+)([a-z]+))");
68+
auto begin = std::regex_iterator(input.begin(), input.end(), token_re);
69+
auto end = std::regex_iterator<std::string_view::const_iterator>();
70+
71+
std::chrono::nanoseconds total_duration{0};
72+
73+
for (auto i = begin; i != end; ++i) {
74+
auto match = *i;
75+
auto number_str = match[1];
76+
auto unit_str = match[2];
77+
78+
auto it = unit_map.find(unit_str);
79+
if (it == unit_map.end()) {
80+
throw RuntimeError("Unknown duration unit: {}", unit_str.str());
81+
}
82+
83+
auto unit = it->second;
84+
85+
int64_t number;
86+
try {
87+
number = std::stoul(number_str);
88+
} catch (const std::invalid_argument &e) {
89+
throw RuntimeError("Invalid number in duration: {}", match.str());
90+
} catch (const std::out_of_range &e) {
91+
throw RuntimeError("Duration overflows maximum representable value: {}",
92+
match.str());
93+
}
94+
95+
auto duration = unit * number;
96+
97+
if (duration > Duration::zero() && duration < Duration(1))
98+
throw RuntimeError("Duration underflows minimum representable value");
99+
100+
if (unit.count() != 0 &&
101+
duration.count() / unit.count() != number) // Check for overflow.
102+
throw RuntimeError("Duration overflows maximum representable value: {}",
103+
match.str());
104+
;
105+
106+
total_duration += duration;
107+
}
108+
109+
return std::chrono::duration_cast<Duration>(total_duration);
110+
}
111+
112+
template <typename Duration = std::chrono::nanoseconds>
113+
Duration parse_duration(json_t *json) {
114+
switch (json_typeof(json)) {
115+
case JSON_INTEGER: {
116+
int64_t value = json_integer_value(json);
117+
if (value < 0) {
118+
throw ConfigError(json, "duration-negative", "Negative duration value");
119+
}
120+
121+
return Duration(value);
122+
}
123+
124+
case JSON_STRING: {
125+
try {
126+
return parse_duration<Duration>(
127+
std::string_view(json_string_value(json), json_string_length(json)));
128+
} catch (const RuntimeError &e) {
129+
throw ConfigError(json, "duration", "{}", e.what());
130+
}
131+
}
132+
133+
default:
134+
throw ConfigError(json, "duration",
135+
"Expected a string or integer for duration");
136+
}
137+
}
138+
45139
} // namespace node
46140
} // namespace villas

0 commit comments

Comments
 (0)