diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 2881ab97..3c7a937c 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -991,16 +991,28 @@ inline auto tm_mon_short_name(int mon) -> const char* { } template -struct has_member_data_tm_gmtoff : std::false_type {}; +struct has_tm_gmtoff : std::false_type {}; template -struct has_member_data_tm_gmtoff> - : std::true_type {}; +struct has_tm_gmtoff> : std::true_type {}; -template -struct has_member_data_tm_zone : std::false_type {}; +template struct has_tm_zone : std::false_type {}; template -struct has_member_data_tm_zone> - : std::true_type {}; +struct has_tm_zone> : std::true_type {}; + +template ::value)> +bool set_tm_zone(T& time, char* tz) { + time.tm_zone = tz; + return true; +} +template ::value)> +bool set_tm_zone(T&, char*) { + return false; +} + +inline char* utc() { + static char tz[] = "UTC"; + return tz; +} // Converts value to Int and checks that it's in the range [0, upper). template ::value)> @@ -1260,25 +1272,22 @@ class tm_writer { write2(static_cast(offset % 60)); } - template ::value)> - void format_utc_offset_impl(const T& tm, numeric_system ns) { + template ::value)> + void format_utc_offset(const T& tm, numeric_system ns) { write_utc_offset(tm.tm_gmtoff, ns); } - template ::value)> - void format_utc_offset_impl(const T&, numeric_system ns) { + template ::value)> + void format_utc_offset(const T&, numeric_system ns) { write_utc_offset(0, ns); } - template ::value)> - void format_tz_name_impl(const T& tm) { - if (is_classic_) - out_ = write_tm_str(out_, tm.tm_zone, loc_); - else - format_localized('Z'); + template ::value)> + void format_tz_name(const T& tm) { + out_ = write_tm_str(out_, tm.tm_zone, loc_); } - template ::value)> - void format_tz_name_impl(const T&) { - format_localized('Z'); + template ::value)> + void format_tz_name(const T&) { + out_ = std::copy_n(utc(), 3, out_); } void format_localized(char format, char modifier = 0) { @@ -1389,8 +1398,8 @@ class tm_writer { out_ = copy(std::begin(buf) + offset, std::end(buf), out_); } - void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } - void on_tz_name() { format_tz_name_impl(tm_); } + void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); } + void on_tz_name() { format_tz_name(tm_); } void on_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) @@ -1987,7 +1996,6 @@ class year_month_day { template struct formatter : private formatter { private: - bool localized_ = false; bool use_tm_formatter_ = false; public: @@ -1995,8 +2003,7 @@ struct formatter : private formatter { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == 'L') { ++it; - localized_ = true; - return it; + this->set_localized(); } use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; @@ -2007,7 +2014,7 @@ struct formatter : private formatter { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); if (use_tm_formatter_) return formatter::format(time, ctx); - detail::get_locale loc(localized_, ctx.locale()); + detail::get_locale loc(this->localized(), ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_weekday(); return w.out(); @@ -2041,7 +2048,6 @@ struct formatter : private formatter { template struct formatter : private formatter { private: - bool localized_ = false; bool use_tm_formatter_ = false; public: @@ -2049,8 +2055,7 @@ struct formatter : private formatter { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == 'L') { ++it; - localized_ = true; - return it; + this->set_localized(); } use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; @@ -2061,7 +2066,7 @@ struct formatter : private formatter { auto time = std::tm(); time.tm_mon = static_cast(static_cast(m)) - 1; if (use_tm_formatter_) return formatter::format(time, ctx); - detail::get_locale loc(localized_, ctx.locale()); + detail::get_locale loc(this->localized(), ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_month(); return w.out(); @@ -2195,6 +2200,9 @@ template struct formatter { detail::string_literal(); protected: + auto localized() const -> bool { return specs_.localized(); } + FMT_CONSTEXPR void set_localized() { specs_.set_localized(); } + FMT_CONSTEXPR auto do_parse(parse_context& ctx, bool has_timezone) -> const Char* { auto it = ctx.begin(), end = ctx.end(); @@ -2209,6 +2217,11 @@ template struct formatter { if (it == end) return it; } + if (*it == 'L') { + specs_.set_localized(); + ++it; + } + end = detail::parse_chrono_format(it, end, detail::tm_format_checker(has_timezone)); // Replace the default format string only if the new spec is not empty. @@ -2225,7 +2238,7 @@ template struct formatter { detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); - auto loc_ref = ctx.locale(); + auto loc_ref = specs.localized() ? ctx.locale() : detail::locale_ref(); detail::get_locale loc(static_cast(loc_ref), loc_ref); auto w = detail::tm_writer, Char, Duration>( loc, out, tm, subsecs); @@ -2236,7 +2249,7 @@ template struct formatter { public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { - return do_parse(ctx, detail::has_member_data_tm_gmtoff::value); + return do_parse(ctx, detail::has_tm_gmtoff::value); } template @@ -2261,6 +2274,7 @@ struct formatter, Char> : private formatter { if (detail::const_check( period::num == 1 && period::den == 1 && !std::is_floating_point::value)) { + detail::set_tm_zone(tm, detail::utc()); return formatter::format(tm, ctx); } Duration epoch = val.time_since_epoch(); @@ -2268,10 +2282,12 @@ struct formatter, Char> : private formatter { epoch - detail::duration_cast(epoch)); if (subsecs.count() < 0) { auto second = detail::duration_cast(std::chrono::seconds(1)); - if (tm.tm_sec != 0) + if (tm.tm_sec != 0) { --tm.tm_sec; - else + } else { tm = gmtime(val - second); + detail::set_tm_zone(tm, detail::utc()); + } subsecs += second; } return formatter::do_format(tm, ctx, &subsecs); diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 3a0c2aac..b3b199ed 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -335,30 +335,16 @@ TEST(chrono_test, local_time) { fmt::format_error, "no timezone"); } -template ::value)> +template ::value)> bool set_tm_gmtoff(T& time, long offset) { time.tm_gmtoff = offset; return true; } -template ::value)> +template ::value)> bool set_tm_gmtoff(T&, long) { return false; } -template ::value)> -bool set_tm_zone(T& time, char* tz) { - time.tm_zone = tz; - return true; -} -template ::value)> -bool set_tm_zone(T&, char*) { - return false; -} - TEST(chrono_test, tm) { auto time = fmt::gmtime(290088000); test_time(time); @@ -371,7 +357,7 @@ TEST(chrono_test, tm) { fmt::format_error, "no timezone"); } char tz[] = "EET"; - if (set_tm_zone(time, tz)) { + if (fmt::detail::set_tm_zone(time, tz)) { EXPECT_EQ(fmt::format(fmt::runtime("{:%Z}"), time), "EET"); } else { EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time), @@ -727,8 +713,8 @@ TEST(chrono_test, weekday) { if (loc != std::locale::classic()) { auto saturdays = std::vector{"sáb", "sá.", "sáb."}; EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat))); - EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat))); - EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm))); + EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", sat))); + EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", tm))); } } @@ -1032,6 +1018,6 @@ TEST(chrono_test, year_month_day) { if (loc != std::locale::classic()) { auto months = std::vector{"ene.", "ene"}; EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month))); - EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month))); + EXPECT_THAT(months, Contains(fmt::format(loc, "{:L%b}", month))); } }