export module routemon:time; import std; export namespace routemon::time { using timestamp = std::chrono::time_point; class period { // Assuming [start, end). Unfortunately the DATEX II model is not // clear about this. timestamp start_; timestamp end_; public: explicit period(timestamp start, timestamp end); [[nodiscard]] auto intersect(period other) const -> std::optional; [[nodiscard]] auto except(period other) const -> std::pair, std::optional>; [[nodiscard]] auto start() const -> timestamp; [[nodiscard]] auto end() const -> timestamp; }; class period_seq { std::vector periods_; // The way lt and ge are ordered makes a difference for how the sorting // (insertion based on lower_bound) works. Do not carelessly reorder this. enum lt_ge : std::uint8_t { ge, // >= lt, // < }; // O(n log n) template S> requires std::same_as, period> static auto consolidate(I begin, S end) -> std::vector { auto periods = std::vector{}; auto preds = std::vector>{}; for (auto it = begin; it != end; it++) { auto const& period = *it; auto const a = std::make_pair(period.start(), ge); auto const b = std::make_pair(period.end(), lt); preds.insert(std::lower_bound(preds.begin(), preds.end(), a), a); preds.insert(std::lower_bound(preds.begin(), preds.end(), b), b); } if (preds.empty()) return periods; if (preds.size() < 2) throw std::logic_error{ "period_seq::consolidate: amount of predicates should be >= 2" }; if (preds.front().second != ge) throw std::logic_error{"period_seq::consolidate: first element of preds " "should be a ge-element"}; if (preds.back().second != lt) throw std::logic_error{"period_seq::consolidate: last element of preds " "should be an lt-element"}; auto period_start = preds[0].first; for (std::size_t i = 1; i < preds.size(); i++) { if (preds[i].second == lt && (i + 1 == preds.size() || preds[i + 1].second == ge)) { auto const period_end = preds[i].first; if (!periods.empty() && periods.back().start() == period_start) periods.back() = period{periods.back().end(), period_end}; else periods.emplace_back(period_start, period_end); if (i + 1 != preds.size()) { period_start = preds[i + 1].first; i++; } } } return periods; } explicit period_seq(std::vector periods); public: template S> requires std::same_as, period> explicit period_seq(I begin, S end) : periods_{consolidate(begin, end)} { } explicit period_seq(period singleton); [[nodiscard]] auto intersect(period_seq const& other) const -> period_seq; [[nodiscard]] auto except(period_seq const& other) const -> period_seq; [[nodiscard]] auto periods() const -> std::vector const&; }; auto operator<<(std::ostream& os, period const& p) -> std::ostream&; auto operator<<(std::ostream& os, period_seq const& ps) -> std::ostream&; } // namespace routemon::time