From 3a713c477a4a2be94c79c19b7f927f7a2ceeaa86 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 14 May 2025 21:47:16 +0300 Subject: [PATCH] [traffic] Refactor IsoTime into a class Signed-off-by: mvglasow --- map/traffic_manager.cpp | 6 +-- traffxml/traff_model.cpp | 73 +++++++++++++++++++++++++++++++++--- traffxml/traff_model.hpp | 48 ++++++++++++++++++++---- traffxml/traff_model_xml.cpp | 48 +++--------------------- 4 files changed, 118 insertions(+), 57 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 7e2e6182c..9e33135f2 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -367,13 +367,13 @@ void TrafficManager::ConsolidateFeedQueue() if (it_i->m_id == it_j->m_id) { // dupe, remove older - if (traffxml::operator<(it_i->m_updateTime, it_j->m_updateTime)) + if (it_i->m_updateTime < it_j->m_updateTime) { // standard case: i has the newer one ++it_i; it_j = m_feedQueue[j].erase(it_j); } - else if (traffxml::operator<(it_i->m_updateTime, it_j->m_updateTime)) + else if (it_i->m_updateTime < it_j->m_updateTime) { // j has the newer one it_i = m_feedQueue[i].erase(it_i); @@ -425,7 +425,7 @@ void TrafficManager::UpdateMessageCache(std::mapsecond.m_updateTime)) < timegm(&(message.m_updateTime))); + process = (it->second.m_updateTime < message.m_updateTime); if (process) cache.insert_or_assign(message.m_id, message); } diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index c68ed461d..7479f713f 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -4,6 +4,8 @@ #include "geometry/mercator.hpp" +#include + using namespace std; namespace traffxml @@ -76,17 +78,78 @@ const std::map kEventDelayMap{ // TODO Security*, Transport*, Weather* (not in enum yet) }; +std::optional IsoTime::ParseIsoTime(std::string timeString) +{ + /* + * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher + * will contain the following items: + * + * 0: 2019-11-01T11:55:42+01:00 (entire expression) + * 1: 2019 (year) + * 2: 11 (month) + * 3: 01 (day) + * 4: 11 (hour, local) + * 5: 55 (minute, local) + * 6: 42.445 (second, local, float) + * 7: .445 (fractional seconds) + * 8: +01:00 (complete UTC offset, or Z; blank if not specified) + * 9: +01:00 (complete UTC offset, blank for Z or of not specified) + * 10: +01 (UTC offset, hours with sign; blank for Z or if not specified) + * 11: :00 (UTC offset, minutes, prefixed with separator) + * 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified) + */ + std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); + + std::smatch iso8601Matcher; + if (std::regex_search(timeString, iso8601Matcher, iso8601Regex)) + { + int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0; + int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0; + if (offset_h < 0) + offset_m *= -1; + + std::tm tm = {}; + tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900; + tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1; + tm.tm_mday = std::stoi(iso8601Matcher[3]); + tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; + tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; + tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; + // Call timegm once to normalize tm; return value can be discarded + timegm(&tm); + IsoTime result(tm); + return result; + } + else + { + LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString)); + return std::nullopt; + } +} + +IsoTime IsoTime::Now() +{ + std::time_t t = std::time(nullptr); + std::tm* tm = std::gmtime(&t); + return IsoTime(*tm); +} + +IsoTime::IsoTime(std::tm tm) + : m_tm(tm) +{} + + bool operator< (IsoTime lhs, IsoTime rhs) { - std::time_t t_lhs = std::mktime(&lhs); - std::time_t t_rhs = std::mktime(&rhs); + std::time_t t_lhs = std::mktime(&lhs.m_tm); + std::time_t t_rhs = std::mktime(&rhs.m_tm); return t_lhs < t_rhs; } bool operator> (IsoTime lhs, IsoTime rhs) { - std::time_t t_lhs = std::mktime(&lhs); - std::time_t t_rhs = std::mktime(&rhs); + std::time_t t_lhs = std::mktime(&lhs.m_tm); + std::time_t t_rhs = std::mktime(&rhs.m_tm); return t_lhs > t_rhs; } @@ -321,7 +384,7 @@ string DebugPrint(LinearSegmentSource source) std::string DebugPrint(IsoTime time) { std::ostringstream os; - os << std::put_time(&time, "%Y-%m-%d %H:%M:%S %z"); + os << std::put_time(&time.m_tm, "%Y-%m-%d %H:%M:%S %z"); return os.str(); } diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index faa8135ab..221b0c500 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -35,11 +35,45 @@ constexpr uint8_t kMaxspeedNone = 255; * Where no time zone is indicated, the timestamp shall always be interpreted as UTC. * `IsoTime` currently maps to `std::tm`, but this is not guaranteed. */ -// TODO make this a class with a private std::tm member -using IsoTime = std::tm; +class IsoTime +{ +public: + /** + * @brief Parses time in ISO 8601 format from a string and stores it in an `IsoTime`. + * + * ISO 8601 timestamps have the format `yyyy-mm-ddThh:mm:ss[.sss]`, optionally followed by a UTC + * offset. For example, `2019-11-01T11:45:42+01:00` refers to 11:45:42 in the UTC+1 timezone, which + * is 10:45:42 UTC. + * + * A UTC offset of `Z` denotes UTC and is equivalent to `+00:00` or `-00:00`. UTC is also assumed + * if no UTC offset is specified. + * + * The UTC offset can be specified as `hh:mm`, `hhmm` or `hh`. + * + * Seconds can be specified as integer or float, but will be rounded to the nearest integer. For + * example, 42.645 seconds will be rounded to 43 seconds. + * + * @param timeString Time in ISO8601 format + * @return An `IsoTime` instance corresponding to `timeString`, or `std::nullopt` if `timeString` is not a valid ISO8601 time string. + */ + static std::optional ParseIsoTime(std::string timeString); -bool operator< (IsoTime lhs, IsoTime rhs); -bool operator> (IsoTime lhs, IsoTime rhs); + /** + * @brief Returns an `IsoTime` corresponding to current wall clock time. + * + * @return An `IsoTime` corresponding to current wall clock time. + */ + static IsoTime Now(); + + friend bool operator< (IsoTime lhs, IsoTime rhs); + friend bool operator> (IsoTime lhs, IsoTime rhs); +private: + friend std::string DebugPrint(IsoTime time); + + IsoTime(std::tm tm); + + std::tm m_tm; +}; // TODO enum urgency @@ -301,9 +335,9 @@ struct TraffMessage std::optional GetTrafficImpact(); std::string m_id; - IsoTime m_receiveTime = {}; - IsoTime m_updateTime = {}; - IsoTime m_expirationTime = {}; + IsoTime m_receiveTime = IsoTime::Now(); + IsoTime m_updateTime = IsoTime::Now(); + IsoTime m_expirationTime = IsoTime::Now(); std::optional m_startTime = {}; std::optional m_endTime = {}; bool m_cancellation = false; diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index ae7cb4f6d..1bc634648 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -196,49 +196,13 @@ bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm) std::string timeString; if (!StringFromXml(attribute, timeString)) return false; - /* - * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher - * will contain the following items: - * - * 0: 2019-11-01T11:55:42+01:00 (entire expression) - * 1: 2019 (year) - * 2: 11 (month) - * 3: 01 (day) - * 4: 11 (hour, local) - * 5: 55 (minute, local) - * 6: 42.445 (second, local, float) - * 7: .445 (fractional seconds) - * 8: +01:00 (complete UTC offset, or Z; blank if not specified) - * 9: +01:00 (complete UTC offset, blank for Z or of not specified) - * 10: +01 (UTC offset, hours with sign; blank for Z or if not specified) - * 11: :00 (UTC offset, minutes, prefixed with separator) - * 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified) - */ - std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); - std::smatch iso8601Matcher; - if (std::regex_search(timeString, iso8601Matcher, iso8601Regex)) - { - int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0; - int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0; - if (offset_h < 0) - offset_m *= -1; - - tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900; - tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1; - tm.tm_mday = std::stoi(iso8601Matcher[3]); - tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; - tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; - tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; - // Call timegm once to normalize tm; return value can be discarded - timegm(&tm); - return true; - } - else - { - LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString)); + std::optional result = IsoTime::ParseIsoTime(timeString); + if (!result) return false; - } + + tm = result.value(); + return true; } /** @@ -261,7 +225,7 @@ bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm) */ std::optional OptionalTimeFromXml(pugi::xml_attribute attribute) { - IsoTime result = {}; + IsoTime result = IsoTime::Now(); if (!TimeFromXml(attribute, result)) return std::nullopt; return result;