diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index f3cc9654..6610e62e 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -48,6 +48,16 @@ FMT_CONSTEXPR const Char *parse_chrono_format( case '%': handler.on_text(ptr - 1, ptr); break; + case 'n': { + const char newline[] = "\n"; + handler.on_text(newline, newline + 1); + break; + } + case 't': { + const char tab[] = "\t"; + handler.on_text(tab, tab + 1); + break; + } // Day of the week: case 'a': handler.on_abbr_weekday(); @@ -83,13 +93,13 @@ FMT_CONSTEXPR const Char *parse_chrono_format( break; // Other: case 'c': - handler.on_std_datetime(); + handler.on_datetime(numeric_system::standard); break; case 'x': - handler.on_loc_date(); + handler.on_loc_date(numeric_system::standard); break; case 'X': - handler.on_loc_time(); + handler.on_loc_time(numeric_system::standard); break; case 'D': handler.on_us_date(); @@ -103,7 +113,38 @@ FMT_CONSTEXPR const Char *parse_chrono_format( case 'R': handler.on_24_hour_time(); break; - // Alternative numeric system: + case 'T': + handler.on_iso_time(); + break; + case 'p': + handler.on_am_pm(); + break; + case 'z': + handler.on_utc_offset(); + break; + case 'Z': + handler.on_tz_name(); + break; + // Alternative representation: + case 'E': { + if (ptr == end) + throw format_error("invalid format"); + c = *ptr++; + switch (c) { + case 'c': + handler.on_datetime(numeric_system::alternative); + break; + case 'x': + handler.on_loc_date(numeric_system::alternative); + break; + case 'X': + handler.on_loc_time(numeric_system::alternative); + break; + default: + throw format_error("invalid format"); + } + break; + } case 'O': if (ptr == end) throw format_error("invalid format"); @@ -127,9 +168,12 @@ FMT_CONSTEXPR const Char *parse_chrono_format( case 'S': handler.on_second(numeric_system::alternative); break; + default: + throw format_error("invalid format"); } break; - // TODO: parse more format specifiers + default: + throw format_error("invalid format"); } begin = ptr; } @@ -139,25 +183,31 @@ FMT_CONSTEXPR const Char *parse_chrono_format( } struct chrono_format_checker { + void report_no_date() { throw format_error("no date"); } + 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_abbr_weekday() { report_no_date(); } + void on_full_weekday() { report_no_date(); } + void on_dec0_weekday(numeric_system) { report_no_date(); } + void on_dec1_weekday(numeric_system) { report_no_date(); } + void on_abbr_month() { report_no_date(); } + void on_full_month() { report_no_date(); } 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_datetime(numeric_system) { report_no_date(); } + void on_loc_date(numeric_system) { report_no_date(); } + void on_loc_time(numeric_system) { report_no_date(); } + void on_us_date() { report_no_date(); } + void on_iso_date() { report_no_date(); } void on_12_hour_time() {} void on_24_hour_time() {} + void on_iso_time() {} + void on_am_pm() {} + void on_utc_offset() { report_no_date(); } + void on_tz_name() { report_no_date(); } }; template @@ -197,18 +247,6 @@ struct chrono_formatter { 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); @@ -232,12 +270,20 @@ struct chrono_formatter { std::copy(begin, end, out); } + // These are not implemented because durations don't have date information. 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_datetime(numeric_system) {} + void on_loc_date(numeric_system) {} + void on_loc_time(numeric_system) {} + void on_us_date() {} + void on_iso_date() {} + void on_utc_offset() {} + void on_tz_name() {} void on_24_hour(numeric_system ns) { if (ns == numeric_system::standard) @@ -277,26 +323,6 @@ struct chrono_formatter { 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() { @@ -304,9 +330,44 @@ struct chrono_formatter { *out++ = ':'; write(minute(), 2); } + + void on_iso_time() { + on_24_hour_time(); + *out++ = ':'; + write(second(), 2); + } + + void on_am_pm() { format_localized(time(), "%p"); } }; } // namespace internal +template FMT_CONSTEXPR const char *get_units() { + return FMT_NULL; +} +template <> FMT_CONSTEXPR const char *get_units() { return "as"; } +template <> FMT_CONSTEXPR const char *get_units() { return "fs"; } +template <> FMT_CONSTEXPR const char *get_units() { return "ps"; } +template <> FMT_CONSTEXPR const char *get_units() { return "ns"; } +template <> FMT_CONSTEXPR const char *get_units() { return "µs"; } +template <> FMT_CONSTEXPR const char *get_units() { return "ms"; } +template <> FMT_CONSTEXPR const char *get_units() { return "cs"; } +template <> FMT_CONSTEXPR const char *get_units() { return "ds"; } +template <> FMT_CONSTEXPR const char *get_units>() { return "s"; } +template <> FMT_CONSTEXPR const char *get_units() { return "das"; } +template <> FMT_CONSTEXPR const char *get_units() { return "hs"; } +template <> FMT_CONSTEXPR const char *get_units() { return "ks"; } +template <> FMT_CONSTEXPR const char *get_units() { return "Ms"; } +template <> FMT_CONSTEXPR const char *get_units() { return "Gs"; } +template <> FMT_CONSTEXPR const char *get_units() { return "Ts"; } +template <> FMT_CONSTEXPR const char *get_units() { return "Ps"; } +template <> FMT_CONSTEXPR const char *get_units() { return "Es"; } +template <> FMT_CONSTEXPR const char *get_units>() { + return "m"; +} +template <> FMT_CONSTEXPR const char *get_units>() { + return "h"; +} + template struct formatter, Char> { mutable basic_string_view format_str; @@ -323,10 +384,19 @@ struct formatter, Char> { template auto format(const duration &d, FormatContext &ctx) -> decltype(ctx.out()) { + auto begin = format_str.begin(), end = format_str.end(); + if (begin == end || *begin == '}') { + if (const char *unit = get_units()) + return format_to(ctx.out(), "{}{}", d.count(), unit); + if (Period::den == 1) + return format_to(ctx.out(), "{}[{}s]", d.count(), Period::num); + return format_to(ctx.out(), "{}[{}/{}s]", + d.count(), Period::num, Period::den); + } 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); + parse_chrono_format(begin, end, f); return f.out; } }; diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 1786cf13..ca25d18c 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -6,7 +6,7 @@ // For the license information refer to format.h. #include "fmt/chrono.h" -#include "gtest.h" +#include "gtest-extra.h" #include @@ -49,7 +49,52 @@ std::string format_tm(const std::tm &time, const char *spec, fmt::format(loc, "{:" spec "}", duration)); \ } -TEST(ChronoTest, Format) { +TEST(ChronoTest, FormatDefault) { + EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42))); + EXPECT_EQ("42as", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42fs", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42ps", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42ns", fmt::format("{}", std::chrono::nanoseconds(42))); + EXPECT_EQ("42µs", fmt::format("{}", std::chrono::microseconds(42))); + EXPECT_EQ("42ms", fmt::format("{}", std::chrono::milliseconds(42))); + EXPECT_EQ("42cs", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42ds", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42))); + EXPECT_EQ("42das", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42hs", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42ks", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42Ms", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42Gs", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42Ts", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42Ps", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42Es", + fmt::format("{}", std::chrono::duration(42))); + EXPECT_EQ("42m", fmt::format("{}", std::chrono::minutes(42))); + EXPECT_EQ("42h", fmt::format("{}", std::chrono::hours(42))); + EXPECT_EQ("42[15s]", + fmt::format("{}", + std::chrono::duration>(42))); + EXPECT_EQ("42[15/4s]", + fmt::format("{}", + std::chrono::duration>(42))); +} + +TEST(ChronoTest, FormatSpecs) { + EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0))); + EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0))); + EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0))); 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))); @@ -69,9 +114,34 @@ TEST(ChronoTest, Format) { 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))); + EXPECT_EQ("03:25:45", fmt::format("{:%T}", std::chrono::seconds(12345))); +} + +TEST(ChronoTest, InvalidSpecs) { + auto sec = std::chrono::seconds(0); + EXPECT_THROW_MSG(fmt::format("{:%a}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%A}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%c}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%x}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%Ex}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%X}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%EX}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%D}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%F}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%Ec}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%w}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%u}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%b}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%B}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%z}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%Z}", sec), fmt::format_error, "no date"); + EXPECT_THROW_MSG(fmt::format("{:%q}", sec), fmt::format_error, + "invalid format"); + EXPECT_THROW_MSG(fmt::format("{:%Eq}", sec), fmt::format_error, + "invalid format"); + EXPECT_THROW_MSG(fmt::format("{:%Oq}", sec), fmt::format_error, + "invalid format"); } TEST(ChronoTest, Locale) { @@ -94,8 +164,7 @@ TEST(ChronoTest, Locale) { 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)); + auto sec = std::chrono::seconds(12345); + EXPECT_TIME("%r", time, sec); + EXPECT_TIME("%p", time, sec); }