Adding robust date calculation to WMO header

This commit is contained in:
Dan Paulat 2025-02-17 23:39:52 -06:00
parent c00016cb69
commit 7e9895e002
3 changed files with 178 additions and 72 deletions

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <chrono>
#include <memory> #include <memory>
#include <string> #include <string>
@ -27,7 +28,7 @@ public:
explicit WmoHeader(); explicit WmoHeader();
~WmoHeader(); ~WmoHeader();
WmoHeader(const WmoHeader&) = delete; WmoHeader(const WmoHeader&) = delete;
WmoHeader& operator=(const WmoHeader&) = delete; WmoHeader& operator=(const WmoHeader&) = delete;
WmoHeader(WmoHeader&&) noexcept; WmoHeader(WmoHeader&&) noexcept;
@ -45,8 +46,41 @@ public:
std::string product_category() const; std::string product_category() const;
std::string product_designator() 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<std::chrono::minutes> GetDateTime(
std::optional<std::chrono::system_clock::time_point> endTimeHint =
std::nullopt);
/**
* @brief Parse a WMO header
*
* @param [in] is The input stream to parse
*/
bool Parse(std::istream& is); 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: private:
std::unique_ptr<WmoHeaderImpl> p; std::unique_ptr<WmoHeaderImpl> p;
}; };

View file

@ -119,71 +119,11 @@ std::chrono::system_clock::time_point Segment::event_begin() const
// If event begin is 000000T0000Z // If event begin is 000000T0000Z
if (eventBegin == std::chrono::system_clock::time_point {}) if (eventBegin == std::chrono::system_clock::time_point {})
{ {
using namespace std::chrono;
// Determine event end from P-VTEC string // 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(); header_->vtecString_[0].pVtec_.event_end();
auto endDays = floor<days>(eventEnd); eventBegin = wmoHeader_->GetDateTime(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<unsigned int>(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<int>((endDate.year() - 1y).count())} /
December / day {dayOfMonth}} +
hours {beginHour} + minutes {beginMinute};
}
else
{
// Back up one month
eventBegin =
sys_days {endDate.year() /
month {static_cast<unsigned int>(
(endDate.month() - month {1}).count())} /
day {dayOfMonth}} +
hours {beginHour} + minutes {beginMinute};
}
}
}
} }
} }

View file

@ -42,17 +42,26 @@ public:
WmoHeaderImpl(const WmoHeaderImpl&&) = delete; WmoHeaderImpl(const WmoHeaderImpl&&) = delete;
WmoHeaderImpl& operator=(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; bool operator==(const WmoHeaderImpl& o) const;
std::string sequenceNumber_; std::string sequenceNumber_ {};
std::string dataType_; std::string dataType_ {};
std::string geographicDesignator_; std::string geographicDesignator_ {};
std::string bulletinId_; std::string bulletinId_ {};
std::string icao_; std::string icao_ {};
std::string dateTime_; std::string dateTime_ {};
std::string bbbIndicator_; std::string bbbIndicator_ {};
std::string productCategory_; std::string productCategory_ {};
std::string productDesignator_; std::string productDesignator_ {};
std::optional<std::chrono::year_month> dateHint_ {};
std::optional<std::chrono::sys_time<std::chrono::minutes>>
absoluteDateTime_ {};
}; };
WmoHeader::WmoHeader() : p(std::make_unique<WmoHeaderImpl>()) {} WmoHeader::WmoHeader() : p(std::make_unique<WmoHeaderImpl>()) {}
@ -124,6 +133,71 @@ std::string WmoHeader::product_designator() const
return p->productDesignator_; return p->productDesignator_;
} }
std::chrono::sys_time<std::chrono::minutes> WmoHeader::GetDateTime(
std::optional<std::chrono::system_clock::time_point> endTimeHint)
{
std::chrono::sys_time<std::chrono::minutes> 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<days>(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<int>((endDate.year() - 1y).count())} /
December / day {dayOfMonth}} +
hours {hour} + minutes {minute};
}
else
{
// Back up one month
wmoDateTime =
sys_days {endDate.year() /
month {static_cast<unsigned int>(
(endDate.month() - month {1}).count())} /
day {dayOfMonth}} +
hours {hour} + minutes {minute};
}
}
}
}
return wmoDateTime;
}
bool WmoHeader::Parse(std::istream& is) bool WmoHeader::Parse(std::istream& is)
{ {
bool headerValid = true; bool headerValid = true;
@ -224,6 +298,8 @@ bool WmoHeader::Parse(std::istream& is)
p->icao_ = wmoTokenList[1]; p->icao_ = wmoTokenList[1];
p->dateTime_ = wmoTokenList[2]; p->dateTime_ = wmoTokenList[2];
p->CalculateAbsoluteDateTime();
if (wmoTokenList.size() == 4) if (wmoTokenList.size() == 4)
{ {
p->bbbIndicator_ = wmoTokenList[3]; p->bbbIndicator_ = wmoTokenList[3];
@ -255,4 +331,60 @@ bool WmoHeader::Parse(std::istream& is)
return headerValid; 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<unsigned int>(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 } // namespace scwx::awips