From 26e24da4b55f1cf2f797f0be9e13247595d619f6 Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Sun, 8 Jun 2025 15:40:44 -0400 Subject: [PATCH 1/5] Make animation dock follow default timezones --- scwx-qt/source/scwx/qt/main/main_window.cpp | 15 ++ scwx-qt/source/scwx/qt/map/map_widget.cpp | 5 + scwx-qt/source/scwx/qt/map/map_widget.hpp | 7 +- .../scwx/qt/ui/animation_dock_widget.cpp | 164 ++++++++++++++---- .../scwx/qt/ui/animation_dock_widget.hpp | 2 + .../scwx/qt/ui/animation_dock_widget.ui | 28 +-- scwx-qt/source/scwx/qt/util/time.cpp | 12 ++ scwx-qt/source/scwx/qt/util/time.hpp | 19 ++ 8 files changed, 205 insertions(+), 47 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index bab92b7e..4ab5d56b 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -141,6 +141,7 @@ public: ~MainWindowImpl() { homeRadarConnection_.disconnect(); + defaultTimeZoneConnection_.disconnect(); auto& generalSettings = settings::GeneralSettings::Instance(); @@ -245,6 +246,7 @@ public: bool layerActionsInitialized_ {false}; boost::signals2::scoped_connection homeRadarConnection_ {}; + boost::signals2::scoped_connection defaultTimeZoneConnection_ {}; std::vector maps_; @@ -377,6 +379,7 @@ MainWindow::MainWindow(QWidget* parent) : p->animationDockWidget_ = new ui::AnimationDockWidget(this); p->timelineGroup_->GetContentsLayout()->addWidget(p->animationDockWidget_); ui->radarToolboxScrollAreaContents->layout()->addWidget(p->timelineGroup_); + p->animationDockWidget_->UpdateTimeZone(p->activeMap_->GetDefaultTimeZone()); // Reset toolbox spacer at the bottom ui->radarToolboxScrollAreaContents->layout()->removeItem( @@ -982,6 +985,16 @@ void MainWindowImpl::ConnectMapSignals() void MainWindowImpl::ConnectAnimationSignals() { + defaultTimeZoneConnection_ = settings::GeneralSettings::Instance() + .default_time_zone() + .changed_signal() + .connect( + [this]() + { + animationDockWidget_->UpdateTimeZone( + activeMap_->GetDefaultTimeZone()); + }); + connect(animationDockWidget_, &ui::AnimationDockWidget::DateTimeChanged, timelineManager_.get(), @@ -1602,6 +1615,8 @@ void MainWindowImpl::UpdateRadarSite() alertManager_->SetRadarSite(radarSite); placefileManager_->SetRadarSite(radarSite); + + animationDockWidget_->UpdateTimeZone(activeMap_->GetDefaultTimeZone()); } void MainWindowImpl::UpdateVcp() diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index e219bfb6..763be1b6 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -821,6 +821,11 @@ void MapWidget::SetSmoothingEnabled(bool smoothingEnabled) } } +const scwx::util::time_zone* MapWidget::GetDefaultTimeZone() const +{ + return p->radarProductManager_->default_time_zone(); +} + void MapWidget::SelectElevation(float elevation) { auto radarProductView = p->context_->radar_product_view(); diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index 348e9113..f5f68b22 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -45,9 +45,10 @@ public: void DumpLayerList() const; [[nodiscard]] common::Level3ProductCategoryMap - GetAvailableLevel3Categories(); - [[nodiscard]] std::optional GetElevation() const; - [[nodiscard]] std::vector GetElevationCuts() const; + GetAvailableLevel3Categories(); + [[nodiscard]] const scwx::util::time_zone* GetDefaultTimeZone() const; + [[nodiscard]] std::optional GetElevation() const; + [[nodiscard]] std::vector GetElevationCuts() const; [[nodiscard]] std::optional GetIncomingLevel2Elevation() const; [[nodiscard]] std::vector GetLevel3Products(); [[nodiscard]] std::string GetMapStyle() const; diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index e8519c47..1685e099 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -18,6 +18,14 @@ namespace ui static const std::string logPrefix_ = "scwx::qt::ui::animation_dock_widget"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); +#if (__cpp_lib_chrono >= 201907L) + using local_days = std::chrono::local_days; + using zoned_time_ = std::chrono::zoned_time; +#else + using local_days = date::local_days; + using zoned_time_ = date::zoned_time; +#endif + class AnimationDockWidgetImpl { public: @@ -47,8 +55,14 @@ public: types::MapTime viewType_ {types::MapTime::Live}; bool isLive_ {true}; - std::chrono::sys_days selectedDate_ {}; - std::chrono::seconds selectedTime_ {}; + local_days selectedDate_ {}; + std::chrono::seconds selectedTime_ {}; + + const scwx::util::time_zone* timeZone_ {nullptr}; + + void UpdateTimeZoneLabel(const zoned_time_ zonedTime); + std::chrono::system_clock::time_point GetTimePoint(); + void SetTimePoint(std::chrono::system_clock::time_point time); void ConnectSignals(); void UpdateAutoUpdateLabel(); @@ -61,21 +75,19 @@ AnimationDockWidget::AnimationDockWidget(QWidget* parent) : { ui->setupUi(this); - // Set current date/time - QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); - QDate currentDate = currentDateTime.date(); - QTime currentTime = currentDateTime.time(); - currentTime = currentTime.addSecs(-currentTime.second() + 59); - - ui->dateEdit->setDate(currentDate); - ui->timeEdit->setTime(currentTime); - ui->dateEdit->setMaximumDate(currentDateTime.date()); - p->selectedDate_ = util::SysDays(currentDate); - p->selectedTime_ = - std::chrono::seconds(currentTime.msecsSinceStartOfDay() / 1000); +#if (__cpp_lib_chrono >= 201907L) + p->timeZone_ = std::chrono::get_tzdb().locate_zone("UTC"); +#else + p->timeZone_ = date::get_tzdb().locate_zone("UTC"); +#endif + const std::chrono::sys_seconds currentTimePoint = + std::chrono::floor( + std::chrono::system_clock::now()); + p->SetTimePoint(currentTimePoint); // Update maximum date on a timer - QTimer* maxDateTimer = new QTimer(this); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) Qt Owns this memory + auto* maxDateTimer = new QTimer(this); connect(maxDateTimer, &QTimer::timeout, this, @@ -108,6 +120,76 @@ AnimationDockWidget::~AnimationDockWidget() delete ui; } +void AnimationDockWidgetImpl::UpdateTimeZoneLabel(const zoned_time_ zonedTime) +{ +#if (__cpp_lib_chrono >= 201907L) + namespace df = std; + static constexpr std::string_view kFormatStringTimezone = "{:%Z}"; +#else + namespace df = date; + static constexpr std::string kFormatStringTimezone = "%Z"; +#endif + const std::string timeZoneStr = df::format(kFormatStringTimezone, zonedTime); + self_->ui->timeZoneLabel->setText(timeZoneStr.c_str()); +} + +std::chrono::system_clock::time_point AnimationDockWidgetImpl::GetTimePoint() +{ +#if (__cpp_lib_chrono >= 201907L) + using namespace std::chrono; +#else + using namespace date; +#endif + + // Convert the local time, to a zoned time, to a system time + const local_time localTime = + selectedDate_ + selectedTime_; + const auto zonedTime = + zoned_time(timeZone_, localTime); + const std::chrono::sys_time systemTime = zonedTime.get_sys_time(); + + // This is done to update it when the date changes + UpdateTimeZoneLabel(zonedTime); + + return systemTime; +} + +void AnimationDockWidgetImpl::SetTimePoint( + std::chrono::system_clock::time_point systemTime) +{ +#if (__cpp_lib_chrono >= 201907L) + using namespace std::chrono; +#else + using namespace date; +#endif + // Convert the time to a local time + auto systemTimeSeconds = time_point_cast(systemTime); + auto zonedTime = + zoned_time(timeZone_, systemTimeSeconds); + const local_seconds localTime = zonedTime.get_local_time(); + + // Get the date and time as seperate fields + selectedDate_ = floor(localTime); + selectedTime_ = localTime - selectedDate_; + + // Pull out the local date and time as qt times (with c++20 this could be + // simplified) + auto time = QTime::fromMSecsSinceStartOfDay(static_cast( + duration_cast(selectedTime_).count())); + auto yearMonthDay = year_month_day(selectedDate_); + auto date = QDate(int(yearMonthDay.year()), + // These are always in a small range, so cast is safe + static_cast(unsigned(yearMonthDay.month())), + static_cast(unsigned(yearMonthDay.day()))); + + // Update labels + self_->ui->timeEdit->setTime(time); + self_->ui->dateEdit->setDate(date); + + // Time zone almost certainly just changed, so update it + UpdateTimeZoneLabel(zonedTime); +} + void AnimationDockWidgetImpl::ConnectSignals() { // View type @@ -142,23 +224,24 @@ void AnimationDockWidgetImpl::ConnectSignals() { if (date.isValid()) { - selectedDate_ = util::SysDays(date); - Q_EMIT self_->DateTimeChanged(selectedDate_ + selectedTime_); - } - }); - QObject::connect( - self_->ui->timeEdit, - &QDateTimeEdit::timeChanged, - self_, - [this](QTime time) - { - if (time.isValid()) - { - selectedTime_ = - std::chrono::seconds(time.msecsSinceStartOfDay() / 1000); - Q_EMIT self_->DateTimeChanged(selectedDate_ + selectedTime_); + selectedDate_ = util::LocalDays(date); + Q_EMIT self_->DateTimeChanged(GetTimePoint()); } }); + QObject::connect(self_->ui->timeEdit, + &QDateTimeEdit::timeChanged, + self_, + [this](QTime time) + { + if (time.isValid()) + { + selectedTime_ = + std::chrono::duration_cast( + std::chrono::milliseconds( + time.msecsSinceStartOfDay())); + Q_EMIT self_->DateTimeChanged(GetTimePoint()); + } + }); // Loop controls QObject::connect( @@ -302,6 +385,27 @@ void AnimationDockWidgetImpl::UpdateAutoUpdateLabel() } } +void AnimationDockWidget::UpdateTimeZone(const scwx::util::time_zone* timeZone) +{ + // null timezone is really UTC. This simplifies other code. + if (timeZone == nullptr) + { +#if (__cpp_lib_chrono >= 201907L) + timeZone = std::chrono::get_tzdb().locate_zone("UTC"); +#else + timeZone = date::get_tzdb().locate_zone("UTC"); +#endif + } + + // Get the (UTC relative) time that is selected. We want to preserve this + // across timezone changes. + auto currentTime = p->GetTimePoint(); + p->timeZone_ = timeZone; + // Set the (UTC relative) time that was already selected. This ensures that + // the actual time does not change, only the time zone. + p->SetTimePoint(currentTime); +} + } // namespace ui } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp index abc79c88..c22b0849 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -32,6 +33,7 @@ public slots: void UpdateAnimationState(types::AnimationState state); void UpdateLiveState(bool isLive); void UpdateViewType(types::MapTime viewType); + void UpdateTimeZone(const scwx::util::time_zone* timeZone); signals: void ViewTypeChanged(types::MapTime viewType); diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui index d5cfe3aa..67203ffc 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.ui @@ -7,14 +7,14 @@ 0 0 189 - 264 + 276 - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -56,7 +56,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue @@ -79,10 +79,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -100,7 +100,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue HH:mm @@ -108,7 +108,7 @@ - + UTC @@ -120,10 +120,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -141,7 +141,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue min @@ -193,7 +193,7 @@ - QAbstractSpinBox::CorrectToNearestValue + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue x @@ -219,10 +219,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised diff --git a/scwx-qt/source/scwx/qt/util/time.cpp b/scwx-qt/source/scwx/qt/util/time.cpp index ea3afa1d..73d7820b 100644 --- a/scwx-qt/source/scwx/qt/util/time.cpp +++ b/scwx-qt/source/scwx/qt/util/time.cpp @@ -17,6 +17,18 @@ std::chrono::sys_days SysDays(const QDate& date) julianEpoch); } +local_days LocalDays(const QDate& date) +{ +#if (__cpp_lib_chrono >= 201907L) + using namespace std::chrono; +#else + using namespace date; +#endif + auto yearMonthDay = + year_month_day(year(date.year()), month(date.month()), day(date.day())); + return local_days(yearMonthDay); +} + } // namespace util } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/time.hpp b/scwx-qt/source/scwx/qt/util/time.hpp index 8f3e7a53..34af655e 100644 --- a/scwx-qt/source/scwx/qt/util/time.hpp +++ b/scwx-qt/source/scwx/qt/util/time.hpp @@ -2,6 +2,10 @@ #include +#if (__cpp_lib_chrono < 201907L) +# include +#endif + #include namespace scwx @@ -11,6 +15,12 @@ namespace qt namespace util { +#if (__cpp_lib_chrono >= 201907L) +using local_days = std::chrono::local_days; +#else +using local_days = date::local_days; +#endif + /** * @brief Convert QDate to std::chrono::sys_days. * @@ -20,6 +30,15 @@ namespace util */ std::chrono::sys_days SysDays(const QDate& date); +/** + * @brief Convert QDate to std::chrono::local_days. + * + * @param [in] date Date to convert + * + * @return Days + */ +local_days LocalDays(const QDate& date); + } // namespace util } // namespace qt } // namespace scwx From e3cf37f9ed6a770235270d6ec4754dd6884f10dc Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Sun, 8 Jun 2025 15:47:04 -0400 Subject: [PATCH 2/5] Fix formatting issues for timezones-for-timeline --- .../scwx/qt/ui/animation_dock_widget.cpp | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index 1685e099..8e376ddb 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -19,11 +19,11 @@ static const std::string logPrefix_ = "scwx::qt::ui::animation_dock_widget"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); #if (__cpp_lib_chrono >= 201907L) - using local_days = std::chrono::local_days; - using zoned_time_ = std::chrono::zoned_time; +using local_days = std::chrono::local_days; +using zoned_time_ = std::chrono::zoned_time; #else - using local_days = date::local_days; - using zoned_time_ = date::zoned_time; +using local_days = date::local_days; +using zoned_time_ = date::zoned_time; #endif class AnimationDockWidgetImpl @@ -228,20 +228,19 @@ void AnimationDockWidgetImpl::ConnectSignals() Q_EMIT self_->DateTimeChanged(GetTimePoint()); } }); - QObject::connect(self_->ui->timeEdit, - &QDateTimeEdit::timeChanged, - self_, - [this](QTime time) - { - if (time.isValid()) - { - selectedTime_ = - std::chrono::duration_cast( - std::chrono::milliseconds( - time.msecsSinceStartOfDay())); - Q_EMIT self_->DateTimeChanged(GetTimePoint()); - } - }); + QObject::connect( + self_->ui->timeEdit, + &QDateTimeEdit::timeChanged, + self_, + [this](QTime time) + { + if (time.isValid()) + { + selectedTime_ = std::chrono::duration_cast( + std::chrono::milliseconds(time.msecsSinceStartOfDay())); + Q_EMIT self_->DateTimeChanged(GetTimePoint()); + } + }); // Loop controls QObject::connect( From 8d7d29bf5e080948e874c2dcf6c972bdf9e4a66b Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Sun, 8 Jun 2025 16:12:53 -0400 Subject: [PATCH 3/5] use const instead of constexpr because gcc-11 did not like it --- scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index 8e376ddb..6714d925 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -123,11 +123,11 @@ AnimationDockWidget::~AnimationDockWidget() void AnimationDockWidgetImpl::UpdateTimeZoneLabel(const zoned_time_ zonedTime) { #if (__cpp_lib_chrono >= 201907L) - namespace df = std; - static constexpr std::string_view kFormatStringTimezone = "{:%Z}"; + namespace df = std; + static const std::string_view kFormatStringTimezone = "{:%Z}"; #else - namespace df = date; - static constexpr std::string kFormatStringTimezone = "%Z"; + namespace df = date; + static const std::string kFormatStringTimezone = "%Z"; #endif const std::string timeZoneStr = df::format(kFormatStringTimezone, zonedTime); self_->ui->timeZoneLabel->setText(timeZoneStr.c_str()); From a87cb01935f6ffd7dfbb1e3f35af0c53afa83e5e Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Sun, 8 Jun 2025 16:14:30 -0400 Subject: [PATCH 4/5] use sys_seconds instead of sys_tim --- scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index 6714d925..06ba6864 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -146,7 +146,7 @@ std::chrono::system_clock::time_point AnimationDockWidgetImpl::GetTimePoint() selectedDate_ + selectedTime_; const auto zonedTime = zoned_time(timeZone_, localTime); - const std::chrono::sys_time systemTime = zonedTime.get_sys_time(); + const std::chrono::sys_seconds systemTime = zonedTime.get_sys_time(); // This is done to update it when the date changes UpdateTimeZoneLabel(zonedTime); From cb5f5379cbb50d20ea9fd10435becc575095dbbf Mon Sep 17 00:00:00 2001 From: AdenKoperczak Date: Sun, 8 Jun 2025 16:59:43 -0400 Subject: [PATCH 5/5] use constexpr for things that need them --- scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp index 06ba6864..85fdb74f 100644 --- a/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp +++ b/scwx-qt/source/scwx/qt/ui/animation_dock_widget.cpp @@ -123,8 +123,8 @@ AnimationDockWidget::~AnimationDockWidget() void AnimationDockWidgetImpl::UpdateTimeZoneLabel(const zoned_time_ zonedTime) { #if (__cpp_lib_chrono >= 201907L) - namespace df = std; - static const std::string_view kFormatStringTimezone = "{:%Z}"; + namespace df = std; + static constexpr std::string_view kFormatStringTimezone = "{:%Z}"; #else namespace df = date; static const std::string kFormatStringTimezone = "%Z";