diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 83987953..1c798df9 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -574,7 +574,8 @@ inline auto localtime(std::time_t time) -> std::tm { #if FMT_USE_LOCAL_TIME template ())> -FMT_DEPRECATED inline auto localtime(std::chrono::local_time time) -> std::tm { +FMT_DEPRECATED inline auto localtime(std::chrono::local_time time) + -> std::tm { using namespace std::chrono; using namespace fmt_detail; return localtime(detail::to_time_t(current_zone()->to_sys(time))); @@ -911,7 +912,14 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; -struct tm_format_checker : null_chrono_spec_handler { +class tm_format_checker : public null_chrono_spec_handler { + private: + bool no_timezone_ = false; + + public: + constexpr explicit tm_format_checker(bool no_timezone = false) + : no_timezone_(no_timezone) {} + FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no format")); } @@ -949,8 +957,12 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_utc_offset(numeric_system) {} - FMT_CONSTEXPR void on_tz_name() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) { + if (no_timezone_) FMT_THROW(format_error("no timezone")); + } + FMT_CONSTEXPR void on_tz_name() { + if (no_timezone_) FMT_THROW(format_error("no timezone")); + } }; inline auto tm_wday_full_name(int wday) -> const char* { @@ -1005,7 +1017,7 @@ template ::value)> inline auto to_nonnegative_int(T value, Int upper) -> Int { if (!std::is_unsigned::value && (value < 0 || to_unsigned(value) > to_unsigned(upper))) { - FMT_THROW(fmt::format_error("chrono value is out of range")); + FMT_THROW(format_error("chrono value is out of range")); } return static_cast(value); } @@ -2242,8 +2254,8 @@ template struct formatter { ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } - public: - FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + FMT_CONSTEXPR auto do_parse(parse_context& ctx, + bool no_timezone = false) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end || *it == '}') return it; @@ -2256,12 +2268,18 @@ template struct formatter { if (it == end) return it; } - end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + end = detail::parse_chrono_format(it, end, + detail::tm_format_checker(no_timezone)); // Replace the default format string only if the new spec is not empty. if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; return end; } + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return do_parse(ctx); + } + template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { @@ -2317,6 +2335,10 @@ struct formatter, Char> : formatter { this->fmt_ = detail::string_literal(); } + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return this->do_parse(ctx, true); + } + template auto format(local_time val, FormatContext& ctx) const -> decltype(ctx.out()) { diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 9e86d126..0aae98df 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -371,7 +371,7 @@ TEST(chrono_test, local_time) { #elif !FMT_HAS_C99_STRFTIME // Only C89 conversion specifiers when using MSVCRT instead of UCRT specs = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a", - "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"}; + "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"}; #endif specs.push_back("%Y-%m-%d %H:%M:%S"); @@ -385,7 +385,10 @@ TEST(chrono_test, local_time) { EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); } - // TODO: check that %z and %Z result in an error + EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time), + fmt::format_error, "no timezone"); + EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time), + fmt::format_error, "no timezone"); } TEST(chrono_test, daylight_savings_time_end) {