From 7e9895e0025fafa97b061714605682eceea981e3 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 17 Feb 2025 23:39:52 -0600 Subject: [PATCH] Adding robust date calculation to WMO header --- wxdata/include/scwx/awips/wmo_header.hpp | 36 ++++- .../scwx/awips/text_product_message.cpp | 64 +------- wxdata/source/scwx/awips/wmo_header.cpp | 150 ++++++++++++++++-- 3 files changed, 178 insertions(+), 72 deletions(-) diff --git a/wxdata/include/scwx/awips/wmo_header.hpp b/wxdata/include/scwx/awips/wmo_header.hpp index f3487b6d..f0aed0de 100644 --- a/wxdata/include/scwx/awips/wmo_header.hpp +++ b/wxdata/include/scwx/awips/wmo_header.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -27,7 +28,7 @@ public: explicit WmoHeader(); ~WmoHeader(); - WmoHeader(const WmoHeader&) = delete; + WmoHeader(const WmoHeader&) = delete; WmoHeader& operator=(const WmoHeader&) = delete; WmoHeader(WmoHeader&&) noexcept; @@ -45,8 +46,41 @@ public: std::string product_category() const; std::string product_designator() const; + /** + * @brief Get the WMO date/time + * + * Gets the WMO date/time. Uses the optional date hint provided via + * SetDateHint(std::chrono::year_month). If the date hint has not been + * provided, the endTimeHint parameter is required. + * + * @param [in] endTimeHint The optional end time bounds to provide. This is + * ignored if a date hint has been provided to determine an absolute date. + */ + std::chrono::sys_time GetDateTime( + std::optional endTimeHint = + std::nullopt); + + /** + * @brief Parse a WMO header + * + * @param [in] is The input stream to parse + */ bool Parse(std::istream& is); + /** + * @brief Provide a date hint for the WMO parser + * + * The WMO header contains a date/time in the format DDMMSS. The year and + * month must be derived using another source. The date hint provides the + * additional context required to determine the absolute product time. + * + * This function will update any absolute date/time already calculated, or + * affect the calculation of a subsequent absolute date/time. + * + * @param [in] dateHint The date hint to provide the WMO header parser + */ + void SetDateHint(std::chrono::year_month dateHint); + private: std::unique_ptr p; }; diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp index 465b665e..9674b325 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -119,71 +119,11 @@ std::chrono::system_clock::time_point Segment::event_begin() const // If event begin is 000000T0000Z if (eventBegin == std::chrono::system_clock::time_point {}) { - using namespace std::chrono; - // Determine event end from P-VTEC string - system_clock::time_point eventEnd = + std::chrono::system_clock::time_point eventEnd = header_->vtecString_[0].pVtec_.event_end(); - auto endDays = floor(eventEnd); - year_month_day endDate {endDays}; - - // Determine WMO date/time - std::string wmoDateTime = wmoHeader_->date_time(); - - bool wmoDateTimeValid = false; - unsigned int dayOfMonth = 0; - unsigned long beginHour = 0; - unsigned long beginMinute = 0; - - try - { - // WMO date time is in the format DDHHMM - dayOfMonth = - static_cast(std::stoul(wmoDateTime.substr(0, 2))); - beginHour = std::stoul(wmoDateTime.substr(2, 2)); - beginMinute = std::stoul(wmoDateTime.substr(4, 2)); - wmoDateTimeValid = true; - } - catch (const std::exception&) - { - logger_->warn("Malformed WMO date/time: {}", wmoDateTime); - } - - if (wmoDateTimeValid) - { - // Combine end date year and month with WMO date time - eventBegin = - sys_days {endDate.year() / endDate.month() / day {dayOfMonth}} + - hours {beginHour} + minutes {beginMinute}; - - // If the begin date is after the end date, assume the start time - // was the previous month (give a 1 day grace period for expiring - // events in the past) - if (eventBegin > eventEnd + 24h) - { - // If the current end month is January - if (endDate.month() == January) - { - // The begin month must be December of last year - eventBegin = - sys_days { - year {static_cast((endDate.year() - 1y).count())} / - December / day {dayOfMonth}} + - hours {beginHour} + minutes {beginMinute}; - } - else - { - // Back up one month - eventBegin = - sys_days {endDate.year() / - month {static_cast( - (endDate.month() - month {1}).count())} / - day {dayOfMonth}} + - hours {beginHour} + minutes {beginMinute}; - } - } - } + eventBegin = wmoHeader_->GetDateTime(eventEnd); } } diff --git a/wxdata/source/scwx/awips/wmo_header.cpp b/wxdata/source/scwx/awips/wmo_header.cpp index a701e476..27d169e4 100644 --- a/wxdata/source/scwx/awips/wmo_header.cpp +++ b/wxdata/source/scwx/awips/wmo_header.cpp @@ -42,17 +42,26 @@ public: WmoHeaderImpl(const WmoHeaderImpl&&) = delete; WmoHeaderImpl& operator=(const WmoHeaderImpl&&) = delete; + void CalculateAbsoluteDateTime(); + bool ParseDateTime(unsigned int& dayOfMonth, + unsigned long& hour, + unsigned long& minute); + bool operator==(const WmoHeaderImpl& o) const; - std::string sequenceNumber_; - std::string dataType_; - std::string geographicDesignator_; - std::string bulletinId_; - std::string icao_; - std::string dateTime_; - std::string bbbIndicator_; - std::string productCategory_; - std::string productDesignator_; + std::string sequenceNumber_ {}; + std::string dataType_ {}; + std::string geographicDesignator_ {}; + std::string bulletinId_ {}; + std::string icao_ {}; + std::string dateTime_ {}; + std::string bbbIndicator_ {}; + std::string productCategory_ {}; + std::string productDesignator_ {}; + + std::optional dateHint_ {}; + std::optional> + absoluteDateTime_ {}; }; WmoHeader::WmoHeader() : p(std::make_unique()) {} @@ -124,6 +133,71 @@ std::string WmoHeader::product_designator() const return p->productDesignator_; } +std::chrono::sys_time WmoHeader::GetDateTime( + std::optional endTimeHint) +{ + std::chrono::sys_time wmoDateTime {}; + + if (p->absoluteDateTime_.has_value()) + { + wmoDateTime = p->absoluteDateTime_.value(); + } + else if (endTimeHint.has_value()) + { + bool dateTimeValid = false; + unsigned int dayOfMonth = 0; + unsigned long hour = 0; + unsigned long minute = 0; + + dateTimeValid = p->ParseDateTime(dayOfMonth, hour, minute); + + if (dateTimeValid) + { + using namespace std::chrono; + + auto endDays = floor(endTimeHint.value()); + year_month_day endDate {endDays}; + + // Combine end date year and month with WMO date time + wmoDateTime = + sys_days {endDate.year() / endDate.month() / day {dayOfMonth}} + + hours {hour} + minutes {minute}; + + // If the begin date is after the end date, assume the start time + // was the previous month (give a 1 day grace period for expiring + // events in the past) + if (wmoDateTime > endTimeHint.value() + 24h) + { + // If the current end month is January + if (endDate.month() == January) + { + year_month x = year {2024} / December; + sys_days y; + + // The begin month must be December of last year + wmoDateTime = + sys_days { + year {static_cast((endDate.year() - 1y).count())} / + December / day {dayOfMonth}} + + hours {hour} + minutes {minute}; + } + else + { + // Back up one month + wmoDateTime = + sys_days {endDate.year() / + month {static_cast( + (endDate.month() - month {1}).count())} / + day {dayOfMonth}} + + hours {hour} + minutes {minute}; + } + } + } + } + + return wmoDateTime; +} + bool WmoHeader::Parse(std::istream& is) { bool headerValid = true; @@ -224,6 +298,8 @@ bool WmoHeader::Parse(std::istream& is) p->icao_ = wmoTokenList[1]; p->dateTime_ = wmoTokenList[2]; + p->CalculateAbsoluteDateTime(); + if (wmoTokenList.size() == 4) { p->bbbIndicator_ = wmoTokenList[3]; @@ -255,4 +331,60 @@ bool WmoHeader::Parse(std::istream& is) return headerValid; } +void WmoHeader::SetDateHint(std::chrono::year_month dateHint) +{ + p->dateHint_ = dateHint; + p->CalculateAbsoluteDateTime(); +} + +bool WmoHeaderImpl::ParseDateTime(unsigned int& dayOfMonth, + unsigned long& hour, + unsigned long& minute) +{ + bool dateTimeValid = false; + + try + { + // WMO date time is in the format DDHHMM + dayOfMonth = + static_cast(std::stoul(dateTime_.substr(0, 2))); + hour = std::stoul(dateTime_.substr(2, 2)); + minute = std::stoul(dateTime_.substr(4, 2)); + dateTimeValid = true; + } + catch (const std::exception&) + { + logger_->warn("Malformed WMO date/time: {}", dateTime_); + } + + return dateTimeValid; +} + +void WmoHeaderImpl::CalculateAbsoluteDateTime() +{ + bool dateTimeValid = false; + + if (dateHint_.has_value() && !dateTime_.empty()) + { + unsigned int dayOfMonth = 0; + unsigned long hour = 0; + unsigned long minute = 0; + + dateTimeValid = ParseDateTime(dayOfMonth, hour, minute); + + if (dateTimeValid) + { + using namespace std::chrono; + absoluteDateTime_ = sys_days {dateHint_->year() / dateHint_->month() / + day {dayOfMonth}} + + hours {hour} + minutes {minute}; + } + } + + if (!dateTimeValid) + { + absoluteDateTime_.reset(); + } +} + } // namespace scwx::awips