From fb25228f24ff0d918c6bc5f3b4acb75125af85d4 Mon Sep 17 00:00:00 2001 From: Yat Ho Date: Wed, 15 Oct 2025 01:53:08 +0800 Subject: [PATCH] chore: bump {fmt} to 11.2.0 and fix compatibility (#7612) * chore: bump fmt to 11.2.0 Acquire https://github.com/fmtlib/fmt/commit/6797f0c39a4ef13061cbc3bb850c35af7428fdc4, which fixes compile error on GCC 15.1. Acquire https://github.com/fmtlib/fmt/commit/9f269062a7ba1440d91c12f096a8cf3b91d6fbe9, which fixes buffer overflow in NetBSD 10.1 on GCC 10.5. Replace `fmt::localtime` with `std::localtime`, as `fmt::` is now deprecated. * fix: format timezone ourselves on Windows {fmt} 11.2.0 removed the ability to format `std::tm` with the format specifier `%z` on Windows, for a good reason. Ref: https://github.com/fmtlib/fmt/issues/4444 This forces us to implement our own solution on Windows as there's no alternative. * fix: support `FMT_USE_EXCEPTIONS` --- cmake/FindFmt.cmake | 15 +++++++-- gtk/DetailsDialog.cc | 5 +-- libtransmission/log.cc | 46 ++++++++++++++++++++------- libtransmission/session-alt-speeds.cc | 3 +- libtransmission/torrent.cc | 5 ++- third-party/fmt | 2 +- utils/remote.cc | 3 +- utils/show.cc | 2 +- 8 files changed, 61 insertions(+), 20 deletions(-) diff --git a/cmake/FindFmt.cmake b/cmake/FindFmt.cmake index 829f6077b..deda0c5f7 100644 --- a/cmake/FindFmt.cmake +++ b/cmake/FindFmt.cmake @@ -1,10 +1,21 @@ add_library(fmt::fmt-header-only INTERFACE IMPORTED) +set(${CMAKE_FIND_PACKAGE_NAME}_INCLUDE "${TR_THIRD_PARTY_SOURCE_DIR}/fmt/include") + target_include_directories(fmt::fmt-header-only INTERFACE - ${TR_THIRD_PARTY_SOURCE_DIR}/fmt/include) + ${${CMAKE_FIND_PACKAGE_NAME}_INCLUDE}) + +file(READ "${${CMAKE_FIND_PACKAGE_NAME}_INCLUDE}/fmt/base.h" _FMT_BASE_H) +if(_FMT_BASE_H MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])") + # Use math to skip leading zeros if any. + math(EXPR _FMT_VERSION_MAJOR ${CMAKE_MATCH_1}) + math(EXPR _FMT_VERSION_MINOR ${CMAKE_MATCH_2}) + math(EXPR _FMT_VERSION_PATCH ${CMAKE_MATCH_3}) + set(${CMAKE_FIND_PACKAGE_NAME}_VERSION "${_FMT_VERSION_MAJOR}.${_FMT_VERSION_MINOR}.${_FMT_VERSION_PATCH}") +endif() target_compile_definitions(fmt::fmt-header-only INTERFACE - FMT_EXCEPTIONS=0 + $,FMT_USE_EXCEPTIONS,FMT_EXCEPTIONS>=0 FMT_HEADER_ONLY=1) diff --git a/gtk/DetailsDialog.cc b/gtk/DetailsDialog.cc index 72b59512d..e51032dbb 100644 --- a/gtk/DetailsDialog.cc +++ b/gtk/DetailsDialog.cc @@ -52,6 +52,7 @@ #include #include +#include #include #include // abort() #include @@ -611,12 +612,12 @@ void gtr_text_buffer_set_text(Glib::RefPtr const& b, Glib::ustr [[nodiscard]] std::string get_date_string(time_t t) { - return t == 0 ? _("N/A") : fmt::format("{:%x}", fmt::localtime(t)); + return t == 0 ? _("N/A") : fmt::format("{:%x}", *std::localtime(&t)); } [[nodiscard]] std::string get_date_time_string(time_t t) { - return t == 0 ? _("N/A") : fmt::format("{:%c}", fmt::localtime(t)); + return t == 0 ? _("N/A") : fmt::format("{:%c}", *std::localtime(&t)); } } // namespace diff --git a/libtransmission/log.cc b/libtransmission/log.cc index 07b69c094..ff7b10a0b 100644 --- a/libtransmission/log.cc +++ b/libtransmission/log.cc @@ -14,6 +14,10 @@ #include #include +#ifdef _WIN32 +#include // GetTimeZoneInformation +#endif + #ifdef __ANDROID__ #include #endif @@ -26,7 +30,7 @@ #include "libtransmission/file.h" #include "libtransmission/log.h" #include "libtransmission/tr-assert.h" -#include "libtransmission/tr-strbuf.h" +#include "libtransmission/tr-macros.h" #include "libtransmission/utils.h" using namespace std::literals; @@ -194,17 +198,37 @@ void tr_logFreeQueue(tr_log_message* freeme) // --- -std::string_view tr_logGetTimeStr(std::chrono::system_clock::time_point now, char* buf, size_t buflen) +std::string_view tr_logGetTimeStr(std::chrono::system_clock::time_point const now, char* const buf, size_t const buflen) { - auto const now_tm = fmt::localtime(std::chrono::system_clock::to_time_t(now)); - auto const subseconds = now - std::chrono::time_point_cast(now); - auto const [out, len] = fmt::format_to_n( - buf, - buflen, - "{0:%FT%T.}{1:0>3%Q}{0:%z}", - now_tm, - std::chrono::duration_cast(subseconds)); - return { buf, len }; + auto* walk = buf; + auto const now_time_t = std::chrono::system_clock::to_time_t(now); + auto const now_tm = *std::localtime(&now_time_t); + walk = fmt::format_to_n( + walk, + buflen, + "{0:%FT%R:}{1:%S}" TR_IF_WIN32("", "{0:%z}"), + now_tm, + std::chrono::time_point_cast(now)) + .out; +#ifdef _WIN32 + if (auto tz_info = TIME_ZONE_INFORMATION{}; GetTimeZoneInformation(&tz_info) != TIME_ZONE_ID_INVALID) + { + // https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-gettimezoneinformation + // All translations between UTC time and local time are based on the following formula: + // UTC = local time + bias + // The bias is the difference, in minutes, between UTC time and local time. + auto const offset = tz_info.Bias < 0 ? -tz_info.Bias : tz_info.Bias; + walk = fmt::format_to_n( + walk, + buflen - (walk - buf), + "{:c}{:02d}{:02d}", + tz_info.Bias < 0 ? '+' : '-', + offset / 60, + offset % 60) + .out; + } +#endif + return { buf, static_cast(walk - buf) }; } std::string_view tr_logGetTimeStr(char* buf, size_t buflen) diff --git a/libtransmission/session-alt-speeds.cc b/libtransmission/session-alt-speeds.cc index a15d153ed..312615627 100644 --- a/libtransmission/session-alt-speeds.cc +++ b/libtransmission/session-alt-speeds.cc @@ -3,6 +3,7 @@ // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. +#include #include // size_t #include #include @@ -78,7 +79,7 @@ void tr_session_alt_speeds::set_active(bool active, ChangeReason reason, bool fo [[nodiscard]] bool tr_session_alt_speeds::is_active_minute(time_t time) const noexcept { - auto const tm = fmt::localtime(time); + auto const tm = *std::localtime(&time); size_t minute_of_the_week = (tm.tm_wday * MinutesPerDay) + (tm.tm_hour * MinutesPerHour) + tm.tm_min; diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index a23323c13..02c130e15 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -6,6 +6,7 @@ #include #include #include // EINVAL +#include #include // size_t #include #include @@ -373,6 +374,8 @@ void torrentCallScript(tr_torrent const* tor, std::string const& script) return; } + auto const now = tr_time(); + auto torrent_dir = tr_pathbuf{ tor->current_dir() }; tr_sys_path_native_separators(std::data(torrent_dir)); @@ -382,7 +385,7 @@ void torrentCallScript(tr_torrent const* tor, std::string const& script) auto const labels_str = build_labels_string(tor->labels()); auto const trackers_str = buildTrackersString(tor); auto const bytes_downloaded_str = std::to_string(tor->bytes_downloaded_.ever()); - auto const localtime_str = fmt::format("{:%a %b %d %T %Y%n}", fmt::localtime(tr_time())); + auto const localtime_str = fmt::format("{:%a %b %d %T %Y%n}", *std::localtime(&now)); auto const priority_str = std::to_string(tor->get_priority()); auto const env = std::map{ diff --git a/third-party/fmt b/third-party/fmt index 0c9fce2ff..40626af88 160000 --- a/third-party/fmt +++ b/third-party/fmt @@ -1 +1 @@ -Subproject commit 0c9fce2ffefecfdce794e1859584e25877b7b592 +Subproject commit 40626af88bd7df9a5fb80be7b25ac85b122d6c21 diff --git a/utils/remote.cc b/utils/remote.cc index f2e05c307..b45d68bc4 100644 --- a/utils/remote.cc +++ b/utils/remote.cc @@ -7,6 +7,7 @@ #include #include /* isspace */ #include // floor +#include #include // int64_t #include #include @@ -883,7 +884,7 @@ template std::string_view format_date(std::array& buf, time_t now) { auto begin = std::data(buf); - auto end = fmt::format_to_n(begin, N, "{:%a %b %d %T %Y}", fmt::localtime(now)).out; + auto end = fmt::format_to_n(begin, N, "{:%a %b %d %T %Y}", *std::localtime(&now)).out; return { begin, static_cast(end - begin) }; } diff --git a/utils/show.cc b/utils/show.cc index 60eac16ec..bb8c51eed 100644 --- a/utils/show.cc +++ b/utils/show.cc @@ -165,7 +165,7 @@ int parseCommandLine(app_opts& opts, int argc, char const* const* argv) [[nodiscard]] auto toString(time_t now) { - return now == 0 ? "Unknown" : fmt::format("{:%a %b %d %T %Y}", fmt::localtime(now)); + return now == 0 ? "Unknown" : fmt::format("{:%a %b %d %T %Y}", *std::localtime(&now)); } bool compareSecondField(std::string_view l, std::string_view r)