summaryrefslogtreecommitdiffstats
path: root/server/src/time.cppm
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/time.cppm')
-rw-r--r--server/src/time.cppm205
1 files changed, 205 insertions, 0 deletions
diff --git a/server/src/time.cppm b/server/src/time.cppm
new file mode 100644
index 0000000..767883c
--- /dev/null
+++ b/server/src/time.cppm
@@ -0,0 +1,205 @@
1export module routemon:time;
2
3import std;
4
5export namespace routemon::time {
6
7 using timestamp = std::chrono::time_point<std::chrono::utc_clock>;
8
9 class period {
10 // Assuming [start, end). Unfortunately the DATEX II model is not
11 // clear about this.
12 timestamp start_;
13 timestamp end_;
14
15 public:
16 explicit period(timestamp start, timestamp end)
17 : start_{start}, end_{end}
18 {
19 if (start >= end) {
20 throw std::invalid_argument("period: start should be before end");
21 }
22 }
23
24 [[nodiscard]] auto intersect(period other) const -> std::optional<period> {
25 auto const new_start = start_ < other.start() ? other.start() : start_;
26 auto const new_end = other.end() < end_ ? other.end() : end_;
27 return new_start < new_end ? std::make_optional(period{new_start, new_end}) : std::nullopt;
28 }
29
30 [[nodiscard]] auto except(period other) const -> std::pair<std::optional<period>, std::optional<period>> {
31 auto const before_start = start_;
32 auto const before_end = other.start();
33 auto const after_start = end_;
34 auto const after_end = other.end();
35 std::optional<period> before, after;
36 if (before_start < before_end)
37 before = period{before_start, before_end};
38 if (after_start < after_end)
39 after = period{after_end, after_start};
40 return std::make_pair(before, after);
41 }
42
43 [[nodiscard]] auto start() const -> timestamp { return start_; }
44 [[nodiscard]] auto end() const -> timestamp { return end_; }
45 };
46
47 class period_seq {
48 std::vector<period> periods_;
49
50 // The way lt and ge are ordered makes a difference for how the sorting
51 // (insertion based on lower_bound) works. Do not carelessly reorder this.
52 enum lt_ge : std::uint8_t {
53 ge, // >=
54 lt, // <
55 };
56
57 // O(n log n)
58 template<std::input_iterator I, std::sentinel_for<I> S>
59 requires std::same_as<std::iter_value_t<I>, period>
60 static auto consolidate(I begin, S end) -> std::vector<period> {
61 auto periods = std::vector<period>{};
62 auto preds = std::vector<std::pair<timestamp, lt_ge>>{};
63
64 for (auto it = begin; it != end; it++) {
65 auto const& period = *it;
66
67 auto const a = std::make_pair(period.start(), ge);
68 auto const b = std::make_pair(period.end(), lt);
69 preds.insert(std::lower_bound(preds.begin(), preds.end(), a), a);
70 preds.insert(std::lower_bound(preds.begin(), preds.end(), b), b);
71 }
72
73 if (preds.empty())
74 return periods;
75
76 if (preds.size() < 2)
77 throw std::logic_error{"period_seq::consolidate: amount of predicates should be >= 2"};
78 if (preds.front().second != ge)
79 throw std::logic_error{"period_seq::consolidate: first element of preds should be a ge-element"};
80 if (preds.back().second != lt)
81 throw std::logic_error{"period_seq::consolidate: last element of preds should be an lt-element"};
82
83 auto period_start = preds[0].first;
84 for (std::size_t i = 1; i < preds.size(); i++) {
85 if (preds[i].second == lt && (i + 1 == preds.size() || preds[i + 1].second == ge)) {
86 auto const period_end = preds[i].first;
87 if (!periods.empty() && periods.back().start() == period_start)
88 periods.back() = period{periods.back().end(), period_end};
89 else
90 periods.emplace_back(period_start, period_end);
91 if (i + 1 != preds.size()) {
92 period_start = preds[i + 1].first;
93 i++;
94 }
95 }
96 }
97
98 return periods;
99 }
100
101 explicit period_seq(std::vector<period> periods)
102 : periods_{std::move(periods)}
103 {
104 for (auto i = 0uz; i < periods_.size(); i++) {
105 if (i + 1 < periods_.size()) {
106 if (periods_[i].end() >= periods_[i + 1].start()) {
107 throw std::logic_error{"period_seq: vector provided to private constructor not ordered properly"};
108 }
109 }
110 }
111 }
112
113 public:
114 template<std::input_iterator I, std::sentinel_for<I> S>
115 requires std::same_as<std::iter_value_t<I>, period>
116 explicit period_seq(I begin, S end)
117 : periods_{consolidate(begin, end)}
118 {}
119
120 explicit period_seq(period singleton)
121 : periods_{singleton}
122 {}
123
124 [[nodiscard]] auto intersect(period_seq const& other) const -> period_seq {
125 auto it1 = periods_.begin(); auto end1 = periods_.end();
126 auto it2 = other.periods_.begin(); auto end2 = other.periods_.end();
127
128 auto res = std::vector<period>{};
129 while (it1 != end1 && it2 != end2) {
130 auto overlap = it1->intersect(*it2);
131 if (overlap) {
132 res.push_back(*overlap);
133 if (it1->end() < it2->end()) {
134 it1++;
135 } else {
136 it2++;
137 }
138 } else {
139 if (it1->end() < it2->start()) {
140 it1++;
141 } else {
142 it2++;
143 }
144 }
145 }
146
147 return period_seq{res};
148 }
149
150 [[nodiscard]] auto except(period_seq const& other) const -> period_seq {
151 // This code was pretty tricky to write, I wouldn't be surprised if it has some bugs in it.
152
153 auto it1 = periods_.begin(); auto end1 = periods_.end();
154 auto it2 = other.periods_.begin(); auto end2 = other.periods_.end();
155
156 auto res = std::vector<period>{};
157 if (it1 == end1)
158 return period_seq{res};
159 if (it2 == end2)
160 return period_seq{periods_};
161 auto period1 = period{*it1++};
162
163 while (it1 != end1 && it2 != end2) {
164 if (period1.end() <= it2->start()) {
165 res.push_back(period1);
166 period1 = *it1++;
167 } else if (it2->end() <= period1.start()) {
168 it2++;
169 } else /* period1.begin() < it2->end() && it2->begin() < period1.end() */ {
170 auto const [mbefore, mafter] = period1.except(*it2);
171 if (mbefore)
172 res.push_back(*mbefore);
173 if (mafter) {
174 period1 = *mafter;
175 } else {
176 period1 = *it1++;
177 }
178 }
179 }
180
181 return period_seq{res};
182 }
183
184 [[nodiscard]] auto periods() const -> std::vector<period> const& {
185 return periods_;
186 }
187 };
188
189 auto operator<<(std::ostream& os, period const& p) -> std::ostream& {
190 return os << "[" << p.start() << ", " << p.end() << ")";
191 }
192
193 auto operator<<(std::ostream &os, period_seq const& ps) -> std::ostream& {
194 os << "{";
195 auto it = ps.periods().begin();
196 while (it != ps.periods().end()) {
197 os << " " << *it;
198 if (++it != ps.periods().end()) {
199 os << ",";
200 }
201 }
202 return os << " }";
203 }
204
205} // namespace routemon::time