From 6004e3d14a28d28c74f413ff5d24790bd6b5990b Mon Sep 17 00:00:00 2001 From: Gabi Melman Date: Fri, 28 Nov 2025 16:20:28 +0000 Subject: [PATCH] Fix issue #3483 (#3491) Fix issue #3483 --- CMakeLists.txt | 8 +++-- include/spdlog/details/os-inl.h | 48 ++++---------------------- include/spdlog/pattern_formatter-inl.h | 8 +++-- tests/test_pattern_formatter.cpp | 7 +++- tests/test_stdout_api.cpp | 10 ++++-- 5 files changed, 32 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bca70042..c7708eb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,8 @@ include(GNUInstallDirs) # --------------------------------------------------------------------------------------- if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) # Set CMAKE_BUILD_TYPE only if this project is top-level - if((DEFINED PROJECT_IS_TOP_LEVEL AND PROJECT_IS_TOP_LEVEL) - OR (NOT DEFINED PROJECT_IS_TOP_LEVEL AND CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)) + if((DEFINED PROJECT_IS_TOP_LEVEL AND PROJECT_IS_TOP_LEVEL) OR (NOT DEFINED PROJECT_IS_TOP_LEVEL + AND CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) endif() endif() @@ -97,6 +97,7 @@ option(SPDLOG_USE_STD_FORMAT "Use std::format instead of fmt library." OFF) option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF) option(SPDLOG_FMT_EXTERNAL_HO "Use external fmt header-only library instead of bundled" OFF) option(SPDLOG_NO_EXCEPTIONS "Compile with -fno-exceptions. Call abort() on any spdlog exceptions" OFF) +option(SPDLOG_NO_TZ_OFFSET "Omit %z timezone offset (use on platforms without tm_gmtoff)" OFF) if(SPDLOG_FMT_EXTERNAL AND SPDLOG_FMT_EXTERNAL_HO) message(FATAL_ERROR "SPDLOG_FMT_EXTERNAL and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive") @@ -286,7 +287,8 @@ foreach( SPDLOG_NO_TLS SPDLOG_NO_ATOMIC_LEVELS SPDLOG_DISABLE_DEFAULT_LOGGER - SPDLOG_USE_STD_FORMAT) + SPDLOG_USE_STD_FORMAT + SPDLOG_NO_TZ_OFFSET) if(${SPDLOG_OPTION}) target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION}) target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION}) diff --git a/include/spdlog/details/os-inl.h b/include/spdlog/details/os-inl.h index f7a0a277..f0ddd192 100644 --- a/include/spdlog/details/os-inl.h +++ b/include/spdlog/details/os-inl.h @@ -244,15 +244,16 @@ SPDLOG_INLINE size_t filesize(FILE *f) { #endif // Return utc offset in minutes or throw spdlog_ex on failure +#if !defined(SPDLOG_NO_TZ_OFFSET) SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) { -#ifdef _WIN32 - #if _WIN32_WINNT < _WIN32_WINNT_WS08 + #ifdef _WIN32 + #if _WIN32_WINNT < _WIN32_WINNT_WS08 TIME_ZONE_INFORMATION tzinfo; auto rv = ::GetTimeZoneInformation(&tzinfo); - #else + #else DYNAMIC_TIME_ZONE_INFORMATION tzinfo; auto rv = ::GetDynamicTimeZoneInformation(&tzinfo); - #endif + #endif if (rv == TIME_ZONE_ID_INVALID) throw_spdlog_ex("Failed getting timezone info. ", errno); int offset = -tzinfo.Bias; @@ -262,47 +263,12 @@ SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) { offset -= tzinfo.StandardBias; } return offset; -#else - - #if defined(sun) || defined(__sun) || defined(_AIX) || \ - (defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || \ - (!defined(__APPLE__) && !defined(_BSD_SOURCE) && !defined(_GNU_SOURCE) && \ - (!defined(_POSIX_VERSION) || (_POSIX_VERSION < 202405L))) - // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris - struct helper { - static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), - const std::tm &gmtm = details::os::gmtime()) { - int local_year = localtm.tm_year + (1900 - 1); - int gmt_year = gmtm.tm_year + (1900 - 1); - - long int days = ( - // difference in day of year - localtm.tm_yday - - gmtm.tm_yday - - // + intervening leap days - + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + - ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) - - // + difference in years * 365 */ - + static_cast(local_year - gmt_year) * 365); - - long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); - long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); - long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); - - return secs; - } - }; - - auto offset_seconds = helper::calculate_gmt_offset(tm); #else auto offset_seconds = tm.tm_gmtoff; - #endif - return static_cast(offset_seconds / 60); -#endif + #endif } +#endif // SPDLOG_NO_TZ_OFFSET // Return current thread id as size_t // It exists because the std::this_thread::get_id() is much slower(especially diff --git a/include/spdlog/pattern_formatter-inl.h b/include/spdlog/pattern_formatter-inl.h index fd408ed5..716a87fe 100644 --- a/include/spdlog/pattern_formatter-inl.h +++ b/include/spdlog/pattern_formatter-inl.h @@ -510,6 +510,7 @@ public: }; // ISO 8601 offset from UTC in timezone (+-HH:MM) +// If SPDLOG_NO_TZ_OFFSET is defined, print "+??.??" instead. template class z_formatter final : public flag_formatter { public: @@ -524,6 +525,10 @@ public: const size_t field_size = 6; ScopedPadder p(field_size, padinfo_, dest); +#ifdef SPDLOG_NO_TZ_OFFSET + const char *str = "+??:??"; + dest.append(str, str + 6); +#else auto total_minutes = get_cached_offset(msg, tm_time); bool is_negative = total_minutes < 0; if (is_negative) { @@ -536,6 +541,7 @@ public: fmt_helper::pad2(total_minutes / 60, dest); // hours dest.push_back(':'); fmt_helper::pad2(total_minutes % 60, dest); // minutes +#endif // SPDLOG_NO_TZ_OFFSET } private: @@ -1154,12 +1160,10 @@ SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_i formatters_.push_back(details::make_unique>(padding)); need_localtime_ = true; break; - case ('z'): // timezone formatters_.push_back(details::make_unique>(padding)); need_localtime_ = true; break; - case ('P'): // pid formatters_.push_back(details::make_unique>(padding)); break; diff --git a/tests/test_pattern_formatter.cpp b/tests/test_pattern_formatter.cpp index 17a1bbcb..39f47e99 100644 --- a/tests/test_pattern_formatter.cpp +++ b/tests/test_pattern_formatter.cpp @@ -82,8 +82,13 @@ TEST_CASE("GMT offset ", "[pattern_formatter]") { const auto now = std::chrono::system_clock::now(); const auto yesterday = now - 24h; +#ifndef SPDLOG_NO_TZ_OFFSET + const std::string expected_result = "+00:00\n"; +#else + const std::string expected_result = "+??:??\n"; +#endif REQUIRE(log_to_str_with_time(yesterday, "Some message", "%z", spdlog::pattern_time_type::utc, - "\n") == "+00:00\n"); + "\n") == expected_result); } TEST_CASE("color range test1", "[pattern_formatter]") { diff --git a/tests/test_stdout_api.cpp b/tests/test_stdout_api.cpp index 67659b84..54cd47ab 100644 --- a/tests/test_stdout_api.cpp +++ b/tests/test_stdout_api.cpp @@ -5,6 +5,7 @@ #include "includes.h" #include "spdlog/sinks/stdout_sinks.h" #include "spdlog/sinks/stdout_color_sinks.h" + TEST_CASE("stdout_st", "[stdout]") { auto l = spdlog::stdout_logger_st("test"); l->set_pattern("%+"); @@ -72,8 +73,14 @@ TEST_CASE("stderr_color_mt", "[stderr]") { spdlog::drop_all(); } -#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +TEST_CASE("show_utc_offset", "[stdout]") { + auto l = spdlog::stdout_color_mt("test"); + l->set_pattern("[%c %z] [%n] [%^%l%$] %v"); + l->info("Full date"); + spdlog::drop_all(); +} +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT TEST_CASE("wchar_api", "[stdout]") { auto l = spdlog::stdout_logger_st("wchar_logger"); l->set_pattern("%+"); @@ -86,5 +93,4 @@ TEST_CASE("wchar_api", "[stdout]") { SPDLOG_LOGGER_DEBUG(l, L"Test SPDLOG_LOGGER_DEBUG {}", L"param"); spdlog::drop_all(); } - #endif