From f54f3d0fb767169ebb46ecd2c196173d1235a20f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 7 Dec 2018 10:19:44 -0800 Subject: [PATCH] Move chrono-specific code to a separate header --- CMakeLists.txt | 4 +- include/fmt/chrono.h | 336 +++++++++++++++++++++++++++++++++++++++ include/fmt/time.h | 239 ---------------------------- test/CMakeLists.txt | 1 + test/chrono-test.cc | 101 ++++++++++++ test/gtest-extra-test.cc | 4 +- test/posix-test.cc | 2 +- test/time-test.cc | 58 ------- 8 files changed, 443 insertions(+), 302 deletions(-) create mode 100644 include/fmt/chrono.h create mode 100644 test/chrono-test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index d7ea23fe..2b443a15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,8 +138,8 @@ function(add_headers VAR) endfunction() # Define the fmt library, its includes and the needed defines. -add_headers(FMT_HEADERS color.h core.h format.h format-inl.h locale.h ostream.h - printf.h time.h ranges.h) +add_headers(FMT_HEADERS chrono.h color.h core.h format.h format-inl.h locale.h + ostream.h printf.h time.h ranges.h) set(FMT_SOURCES src/format.cc) if (HAVE_OPEN) add_headers(FMT_HEADERS posix.h) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h new file mode 100644 index 00000000..f3cc9654 --- /dev/null +++ b/include/fmt/chrono.h @@ -0,0 +1,336 @@ +// Formatting library for C++ - chrono support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CHRONO_H_ +#define FMT_CHRONO_H_ + +#include "format.h" +#include "locale.h" + +#include +#include +#include +#include + +FMT_BEGIN_NAMESPACE + +namespace internal{ + +enum class numeric_system { + standard, + // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. + alternative +}; + +// Parses a put_time-like format string and invokes handler actions. +template +FMT_CONSTEXPR const Char *parse_chrono_format( + const Char *begin, const Char *end, Handler &&handler) { + auto ptr = begin; + while (ptr != end) { + auto c = *ptr; + if (c == '}') break; + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) + handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) + throw format_error("invalid format"); + c = *ptr++; + switch (c) { + case '%': + handler.on_text(ptr - 1, ptr); + break; + // Day of the week: + case 'a': + handler.on_abbr_weekday(); + break; + case 'A': + handler.on_full_weekday(); + break; + case 'w': + handler.on_dec0_weekday(numeric_system::standard); + break; + case 'u': + handler.on_dec1_weekday(numeric_system::standard); + break; + // Month: + case 'b': + handler.on_abbr_month(); + break; + case 'B': + handler.on_full_month(); + break; + // Hour, minute, second: + case 'H': + handler.on_24_hour(numeric_system::standard); + break; + case 'I': + handler.on_12_hour(numeric_system::standard); + break; + case 'M': + handler.on_minute(numeric_system::standard); + break; + case 'S': + handler.on_second(numeric_system::standard); + break; + // Other: + case 'c': + handler.on_std_datetime(); + break; + case 'x': + handler.on_loc_date(); + break; + case 'X': + handler.on_loc_time(); + break; + case 'D': + handler.on_us_date(); + break; + case 'F': + handler.on_iso_date(); + break; + case 'r': + handler.on_12_hour_time(); + break; + case 'R': + handler.on_24_hour_time(); + break; + // Alternative numeric system: + case 'O': + if (ptr == end) + throw format_error("invalid format"); + c = *ptr++; + switch (c) { + case 'w': + handler.on_dec0_weekday(numeric_system::alternative); + break; + case 'u': + handler.on_dec1_weekday(numeric_system::alternative); + break; + case 'H': + handler.on_24_hour(numeric_system::alternative); + break; + case 'I': + handler.on_12_hour(numeric_system::alternative); + break; + case 'M': + handler.on_minute(numeric_system::alternative); + break; + case 'S': + handler.on_second(numeric_system::alternative); + break; + } + break; + // TODO: parse more format specifiers + } + begin = ptr; + } + if (begin != ptr) + handler.on_text(begin, ptr); + return ptr; +} + +struct chrono_format_checker { + template + void on_text(const Char *, const Char *) {} + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday(numeric_system) {} + void on_dec1_weekday(numeric_system) {} + void on_abbr_month() {} + void on_full_month() {} + void on_24_hour(numeric_system) {} + void on_12_hour(numeric_system) {} + void on_minute(numeric_system) {} + void on_second(numeric_system) {} + void on_std_datetime() {} + void on_loc_date() {} + void on_loc_time() {} + void on_us_date() {} + void on_iso_date() {} + void on_12_hour_time() {} + void on_24_hour_time() {} +}; + +template +inline int to_int(Int value) { + FMT_ASSERT(value >= (std::numeric_limits::min)() && + value <= (std::numeric_limits::max)(), "invalid value"); + return static_cast(value); +} + +template +struct chrono_formatter { + FormatContext &context; + typename FormatContext::iterator out; + std::chrono::seconds s; + std::chrono::milliseconds ms; + + typedef typename FormatContext::char_type char_type; + + explicit chrono_formatter(FormatContext &ctx) + : context(ctx), out(ctx.out()) {} + + int hour() const { return to_int((s.count() / 3600) % 24); } + + int hour12() const { + auto hour = to_int((s.count() / 3600) % 12); + return hour > 0 ? hour : 12; + } + + int minute() const { return to_int((s.count() / 60) % 60); } + int second() const { return to_int(s.count() % 60); } + + std::tm time() const { + auto time = std::tm(); + time.tm_hour = hour(); + time.tm_min = minute(); + time.tm_sec = second(); + return time; + } + + std::tm datetime() const { + auto t = time(); + t.tm_mday = 1; + return t; + } + + std::tm date() const { + auto t = std::tm(); + t.tm_mday = 1; + return t; + } + + void write(int value, int width) { + typedef typename int_traits::main_type main_type; + main_type n = to_unsigned(value); + int num_digits = static_cast(internal::count_digits(n)); + if (width > num_digits) + out = std::fill_n(out, width - num_digits, '0'); + out = format_decimal(out, n, num_digits); + } + + void format_localized(const tm &time, const char *format) { + auto locale = context.locale().template get(); + auto &facet = std::use_facet>(locale); + std::basic_ostringstream os; + os.imbue(locale); + facet.put(os, os, ' ', &time, format, format + std::strlen(format)); + auto str = os.str(); + std::copy(str.begin(), str.end(), out); + } + + void on_text(const char_type *begin, const char_type *end) { + std::copy(begin, end, out); + } + + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday(numeric_system) {} + void on_dec1_weekday(numeric_system) {} + void on_abbr_month() {} + void on_full_month() {} + + void on_24_hour(numeric_system ns) { + if (ns == numeric_system::standard) + return write(hour(), 2); + auto time = tm(); + time.tm_hour = hour(); + format_localized(time, "%OH"); + } + + void on_12_hour(numeric_system ns) { + if (ns == numeric_system::standard) + return write(hour12(), 2); + auto time = tm(); + time.tm_hour = hour(); + format_localized(time, "%OI"); + } + + void on_minute(numeric_system ns) { + if (ns == numeric_system::standard) + return write(minute(), 2); + auto time = tm(); + time.tm_min = minute(); + format_localized(time, "%OM"); + } + + void on_second(numeric_system ns) { + if (ns == numeric_system::standard) { + write(second(), 2); + if (ms != std::chrono::milliseconds(0)) { + *out++ = '.'; + write(to_int(ms.count()), 3); + } + return; + } + auto time = tm(); + time.tm_sec = second(); + format_localized(time, "%OS"); + } + + void on_std_datetime() { format_localized(datetime(), "%c"); } + void on_loc_date() { format_localized(date(), "%x"); } + void on_loc_time() { format_localized(datetime(), "%X"); } + + void on_us_date() { + write(1, 2); + *out++ = '/'; + write(0, 2); + *out++ = '/'; + write(0, 2); + } + + void on_iso_date() { + write(1, 4); + *out++ = '-'; + write(0, 2); + *out++ = '-'; + write(0, 2); + } + + void on_12_hour_time() { format_localized(time(), "%r"); } + + void on_24_hour_time() { + write(hour(), 2); + *out++ = ':'; + write(minute(), 2); + } +}; +} // namespace internal + +template +struct formatter, Char> { + mutable basic_string_view format_str; + typedef std::chrono::duration duration; + + FMT_CONSTEXPR auto parse(basic_parse_context &ctx) + -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + end = parse_chrono_format(begin, end, internal::chrono_format_checker()); + format_str = basic_string_view(&*begin, end - begin); + return end; + } + + template + auto format(const duration &d, FormatContext &ctx) + -> decltype(ctx.out()) { + internal::chrono_formatter f(ctx); + f.s = std::chrono::duration_cast(d); + f.ms = std::chrono::duration_cast(d - f.s); + parse_chrono_format(format_str.begin(), format_str.end(), f); + return f.out; + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_CHRONO_H_ diff --git a/include/fmt/time.h b/include/fmt/time.h index a3f1e6b2..b269eda1 100644 --- a/include/fmt/time.h +++ b/include/fmt/time.h @@ -12,11 +12,6 @@ #include #include -#if FMT_HAS_INCLUDE() -# include -# include -#endif - FMT_BEGIN_NAMESPACE // Prevents expansion of a preceding token as a function-style macro. @@ -28,242 +23,8 @@ inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } - -enum class numeric_system { - standard, - // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. - alternative -}; - -// Parses a put_time-like format string and invokes handler actions. -template -FMT_CONSTEXPR const Char *parse_chrono_format( - const Char *begin, const Char *end, Handler &&handler) { - auto ptr = begin; - while (ptr != end) { - auto c = *ptr; - if (c == '}') break; - if (c != '%') { - ++ptr; - continue; - } - if (begin != ptr) - handler.on_text(begin, ptr); - ++ptr; // consume '%' - if (ptr == end) - throw format_error("invalid format"); - c = *ptr++; - switch (c) { - case '%': - handler.on_text(ptr - 1, ptr); - break; - // Day of the week: - case 'a': - handler.on_abbr_weekday(); - break; - case 'A': - handler.on_full_weekday(); - break; - case 'w': - handler.on_dec0_weekday(numeric_system::standard); - break; - case 'u': - handler.on_dec1_weekday(numeric_system::standard); - break; - // Month: - case 'b': - handler.on_abbr_month(); - break; - case 'B': - handler.on_full_month(); - break; - // Hour, minute, second: - case 'H': - handler.on_24_hour(numeric_system::standard); - break; - case 'I': - handler.on_12_hour(numeric_system::standard); - break; - case 'M': - handler.on_minute(numeric_system::standard); - break; - case 'S': - handler.on_second(numeric_system::standard); - break; - // Alternative numeric system: - case 'O': - if (ptr == end) - throw format_error("invalid format"); - c = *ptr++; - switch (c) { - case 'w': - handler.on_dec0_weekday(numeric_system::alternative); - break; - case 'u': - handler.on_dec1_weekday(numeric_system::alternative); - break; - case 'H': - handler.on_24_hour(numeric_system::alternative); - break; - case 'I': - handler.on_12_hour(numeric_system::alternative); - break; - case 'M': - handler.on_minute(numeric_system::alternative); - break; - case 'S': - handler.on_second(numeric_system::alternative); - break; - } - break; - // TODO: parse more format specifiers - } - begin = ptr; - } - if (begin != ptr) - handler.on_text(begin, ptr); - return ptr; -} - -struct chrono_format_checker { - template - void on_text(const Char *, const Char *) {} - void on_abbr_weekday() {} - void on_full_weekday() {} - void on_dec0_weekday(numeric_system) {} - void on_dec1_weekday(numeric_system) {} - void on_abbr_month() {} - void on_full_month() {} - void on_24_hour(numeric_system) {} - void on_12_hour(numeric_system) {} - void on_minute(numeric_system) {} - void on_second(numeric_system) {} -}; } // namespace internal -#ifdef __cpp_lib_chrono -namespace internal { - -template -inline int to_int(Int value) { - FMT_ASSERT(value >= (std::numeric_limits::min)() && - value <= (std::numeric_limits::max)(), "invalid value"); - return static_cast(value); -} - -template -struct chrono_formatter { - FormatContext &context; - typename FormatContext::iterator out; - std::chrono::seconds s; - std::chrono::milliseconds ms; - - using char_type = typename FormatContext::char_type; - - explicit chrono_formatter(FormatContext &ctx) - : context(ctx), out(ctx.out()) {} - - void write(int value, int width) { - typedef typename int_traits::main_type main_type; - main_type n = to_unsigned(value); - int num_digits = static_cast(internal::count_digits(n)); - if (width > num_digits) - out = std::fill_n(out, width - num_digits, '0'); - out = format_decimal(out, n, num_digits); - } - - void format_localized(const tm &time, char format) { - auto locale = context.locale().template get(); - auto &facet = std::use_facet>(locale); - std::basic_ostringstream os; - os.imbue(locale); - const char format_str[] = {'%', 'O', format}; - facet.put(os, os, ' ', &time, format_str, format_str + sizeof(format_str)); - auto str = os.str(); - std::copy(str.begin(), str.end(), out); - } - - void on_text(const char_type *begin, const char_type *end) { - std::copy(begin, end, out); - } - - void on_abbr_weekday() {} - void on_full_weekday() {} - void on_dec0_weekday(numeric_system) {} - void on_dec1_weekday(numeric_system) {} - void on_abbr_month() {} - void on_full_month() {} - - void on_24_hour(numeric_system ns) { - auto hour = to_int((s.count() / 3600) % 24); - if (ns == numeric_system::standard) - return write(hour, 2); - auto time = tm(); - time.tm_hour = hour; - format_localized(time, 'H'); - } - - void on_12_hour(numeric_system ns) { - auto hour = to_int((s.count() / 3600) % 12); - hour = hour > 0 ? hour : 12; - if (ns == numeric_system::standard) - return write(hour, 2); - auto time = tm(); - time.tm_hour = hour; - format_localized(time, 'I'); - } - - void on_minute(numeric_system ns) { - auto minute = to_int((s.count() / 60) % 60); - if (ns == numeric_system::standard) - return write(minute, 2); - auto time = tm(); - time.tm_min = minute; - format_localized(time, 'M'); - } - - void on_second(numeric_system ns) { - auto second = to_int(s.count() % 60); - if (ns == numeric_system::standard) { - write(second, 2); - if (ms != std::chrono::milliseconds()) { - *out++ = '.'; - write(to_int(ms.count()), 3); - } - return; - } - auto time = tm(); - time.tm_sec = second; - format_localized(time, 'S'); - } -}; -} // namespace internal - -template -struct formatter, Char> { - mutable basic_string_view format_str; - using Duration = std::chrono::duration; - - FMT_CONSTEXPR auto parse(basic_parse_context &ctx) - -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - end = parse_chrono_format(begin, end, internal::chrono_format_checker()); - format_str = basic_string_view(&*begin, end - begin); - return end; - } - - template - auto format(const Duration &d, FormatContext &ctx) - -> decltype(ctx.out()) { - internal::chrono_formatter f(ctx); - f.s = std::chrono::duration_cast(d); - f.ms = std::chrono::duration_cast(d - f.s); - parse_chrono_format(format_str.begin(), format_str.end(), f); - return f.out; - } -}; -#endif // __cpp_lib_chrono - // Thread-safe replacement for std::localtime inline std::tm localtime(std::time_t time) { struct dispatcher { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8eba73b7..70e19db7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -85,6 +85,7 @@ function(add_fmt_test name) endfunction() add_fmt_test(assert-test) +add_fmt_test(chrono-test) add_fmt_test(core-test) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) diff --git a/test/chrono-test.cc b/test/chrono-test.cc new file mode 100644 index 00000000..1786cf13 --- /dev/null +++ b/test/chrono-test.cc @@ -0,0 +1,101 @@ +// Formatting library for C++ - time formatting tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/chrono.h" +#include "gtest.h" + +#include + +std::tm make_tm() { + auto time = std::tm(); + time.tm_mday = 1; + return time; +} + +std::tm make_hour(int h) { + auto time = make_tm(); + time.tm_hour = h; + return time; +} + +std::tm make_minute(int m) { + auto time = make_tm(); + time.tm_min = m; + return time; +} + +std::tm make_second(int s) { + auto time = make_tm(); + time.tm_sec = s; + return time; +} + +std::string format_tm(const std::tm &time, const char *spec, + const std::locale &loc) { + auto &facet = std::use_facet>(loc); + std::ostringstream os; + os.imbue(loc); + facet.put(os, os, ' ', &time, spec, spec + std::strlen(spec)); + return os.str(); +} + +#define EXPECT_TIME(spec, time, duration) { \ + std::locale loc("ja_JP.utf8"); \ + EXPECT_EQ(format_tm(time, spec, loc), \ + fmt::format(loc, "{:" spec "}", duration)); \ + } + +TEST(ChronoTest, Format) { + EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0))); + EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60))); + EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42))); + EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234))); + EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0))); + EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60))); + EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42))); + EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61))); + EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0))); + EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24))); + EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14))); + EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61))); + EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0))); + EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12))); + EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24))); + EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4))); + EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14))); + EXPECT_EQ("03:25:45", + fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345))); + EXPECT_EQ("01/00/00", fmt::format("{:%D}", std::chrono::seconds())); + EXPECT_EQ("0001-00-00", fmt::format("{:%F}", std::chrono::seconds())); + EXPECT_EQ("03:25", fmt::format("{:%R}", std::chrono::seconds(12345))); +} + +TEST(ChronoTest, Locale) { + const char *loc_name = "ja_JP.utf8"; + bool has_locale = false; + std::locale loc; + try { + loc = std::locale(loc_name); + has_locale = true; + } catch (const std::runtime_error &) {} + if (!has_locale) { + fmt::print("{} locale is missing.\n", loc_name); + return; + } + EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14)); + EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14)); + EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42)); + EXPECT_TIME("%OS", make_second(42), std::chrono::seconds(42)); + auto time = make_tm(); + time.tm_hour = 3; + time.tm_min = 25; + time.tm_sec = 45; + EXPECT_TIME("%c", time, std::chrono::seconds(12345)); + EXPECT_TIME("%x", time, std::chrono::seconds(12345)); + EXPECT_TIME("%X", time, std::chrono::seconds(12345)); + EXPECT_TIME("%r", time, std::chrono::seconds(12345)); +} diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 97abf539..43088db4 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -311,8 +311,8 @@ using fmt::error_code; using fmt::file; TEST(ErrorCodeTest, Ctor) { - EXPECT_EQ(0, error_code().get()); - EXPECT_EQ(42, error_code(42).get()); + EXPECT_EQ(error_code().get(), 0); + EXPECT_EQ(error_code(42).get(), 42); } TEST(OutputRedirectTest, ScopedRedirect) { diff --git a/test/posix-test.cc b/test/posix-test.cc index 4209b241..fbd4568a 100644 --- a/test/posix-test.cc +++ b/test/posix-test.cc @@ -334,7 +334,7 @@ TEST(FileTest, Dup2NoExcept) { file copy = open_file(); error_code ec; f.dup2(copy.descriptor(), ec); - EXPECT_EQ(0, ec.get()); + EXPECT_EQ(ec.get(), 0); EXPECT_NE(f.descriptor(), copy.descriptor()); EXPECT_READ(copy, FILE_CONTENT); } diff --git a/test/time-test.cc b/test/time-test.cc index 0b094518..686d3870 100644 --- a/test/time-test.cc +++ b/test/time-test.cc @@ -66,61 +66,3 @@ TEST(TimeTest, GMTime) { std::tm tm = *std::gmtime(&t); EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t))); } - -#ifdef __cpp_lib_chrono -TEST(TimeTest, Chrono) { - EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0))); - EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60))); - EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42))); - EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234))); - EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0))); - EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60))); - EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42))); - EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61))); - EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0))); - EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24))); - EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14))); - EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61))); - EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0))); - EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12))); - EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24))); - EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4))); - EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14))); - EXPECT_EQ("03:25:45", - fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345))); -} - -std::string format_tm(const std::tm &time, const char *spec, - const std::locale &loc) { - std::ostringstream os; - os.imbue(loc); - os << std::put_time(&time, spec); - return os.str(); -} - -#define EXPECT_TIME(spec, field, value, duration) { \ - auto time = std::tm(); \ - time.field = value; \ - std::locale("ja_JP.utf8"); \ - EXPECT_EQ(format_tm(time, spec, loc), \ - fmt::format(loc, "{:" spec "}", std::chrono::duration(value))); \ - } - -TEST(TimeTest, ChronoLocale) { - const char *loc_name = "ja_JP.utf8"; - bool has_locale = false; - std::locale loc; - try { - loc = std::locale(loc_name); - has_locale = true; - } catch (const std::runtime_error &) {} - if (!has_locale) { - fmt::print("{} locale is missing.\n", loc_name); - return; - } - EXPECT_TIME("%OH", tm_hour, 14, hours); - EXPECT_TIME("%OI", tm_hour, 14, hours); - EXPECT_TIME("%OM", tm_min, 42, minutes); - EXPECT_TIME("%OS", tm_sec, 42, seconds); -} -#endif