diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 9fbeeed6798e..d092b5b87d27 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -265,6 +265,15 @@ using utc_time = std::chrono::time_point; template using local_time = std::chrono::time_point; +// Check if std::chrono::zoned_time is available. +#ifdef FMT_HAVE_STD_ZONED_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_HAVE_STD_ZONED_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_HAVE_STD_ZONED_TIME 0 +#endif + namespace detail { // Prevents expansion of a preceding token as a function-style macro. @@ -2240,6 +2249,40 @@ struct formatter, Char> } }; +#if FMT_HAVE_STD_ZONED_TIME +template +struct formatter, Char, + std::enable_if_t>> + : private formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return this->do_parse(ctx, true); + } + + template + auto format(const std::chrono::zoned_time& val, + FormatContext& ctx) const -> decltype(ctx.out()) { + auto time_info = val.get_info(); + auto time_since_epoch = val.get_local_time().time_since_epoch(); + auto seconds_since_epoch = + detail::duration_cast(time_since_epoch); + std::tm t = gmtime(seconds_since_epoch.count()); + // Create a custom tm with timezone info if supported + if constexpr (detail::has_tm_zone::value) { + t.tm_zone = time_info.abbrev.c_str(); + t.tm_gmtoff = time_info.offset.count(); + } + using period = typename Duration::period; + if (period::num == 1 && period::den == 1 && + !std::is_floating_point::value) { + return formatter::format(t, ctx); + } + auto subsecs = + detail::duration_cast(time_since_epoch - seconds_since_epoch); + return formatter::do_format(t, ctx, &subsecs); + } +}; +#endif + FMT_END_EXPORT FMT_END_NAMESPACE diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 98b37000b4eb..a30533043334 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -1021,3 +1021,30 @@ TEST(chrono_test, year_month_day) { EXPECT_THAT(months, Contains(fmt::format(loc, "{:L%b}", month))); } } + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L +TEST(chrono_test, zoned_time) { + const static std::map> map_to_test{ + {"Africa/Cairo", {"EET", "+0200"}}, + {"Africa/Johannesburg", {"SAST", "+0200"}}, + {"America/Chicago", {"CST", "-0600"}}, + {"America/Denver", {"MST", "-0700"}}, + {"America/Los_Angeles", {"PST", "-0800"}}, + {"America/New_York", {"EST", "-0500"}}, + {"Asia/Kolkata", {"IST", "+0530"}}, + {"Asia/Riyadh", {"+03", "+0300"}}, + {"Asia/Shanghai", {"CST", "+0800"}}, + {"Asia/Tokyo", {"JST", "+0900"}}, + {"Australia/Sydney", {"AEDT", "+1100"}}, + {"Europe/Berlin", {"CET", "+0100"}}, + {"Europe/London", {"GMT", "+0000"}}, + {"Europe/Paris", {"CET", "+0100"}}, + {"Pacific/Auckland", {"NZDT", "+1300"}}}; + for (const auto& entry : map_to_test) { + const std::chrono::zoned_time entry_to_test{std::chrono::locate_zone(entry.first), + std::chrono::system_clock::now()}; + EXPECT_EQ(fmt::format("{:%Z}", entry_to_test), entry.second.first); + EXPECT_EQ(fmt::format("{:%z}", entry_to_test), entry.second.second); + } +} +#endif