Improve year formatter

This commit is contained in:
Vladislav Shchapov
2021-10-10 13:50:43 +05:00
committed by Victor Zverovich
parent b04601b918
commit cde44ddb72
2 changed files with 41 additions and 23 deletions

View File

@ -1406,12 +1406,29 @@ template <typename FormatContext, typename OutputIt> struct tm_formatter {
OutputIt out; OutputIt out;
const std::tm& tm; const std::tm& tm;
auto tm_year() const -> decltype(tm.tm_year) { return 1900 + tm.tm_year; } auto tm_year() const -> int { return 1900 + tm.tm_year; }
// POSIX and the C Standard are unclear or inconsistent about what %C and %y
// do if the year is negative or exceeds 9999. Use the convention that %C
// concatenated with %y yields the same output as %Y, and that %Y contains at
// least 4 bytes, with more only if necessary.
struct split_year {
int upper;
int lower;
};
auto tm_split_year() const -> split_year {
auto year = tm_year();
auto q = year / 100;
auto r = year % 100;
if (r < 0) {
// r in [0, 99]
r = -r;
}
return {q, r};
}
auto tm_hour12() const -> decltype(tm.tm_hour) { auto tm_hour12() const -> decltype(tm.tm_hour) {
auto hour = tm.tm_hour % 12; auto hour = tm.tm_hour % 12;
return hour == 0 ? 12 : hour; return hour == 0 ? 12 : hour;
} }
static constexpr char digits1(size_t value) { static constexpr char digits1(size_t value) {
return detail::digits2(value)[1]; return detail::digits2(value)[1];
} }
@ -1478,12 +1495,11 @@ template <typename FormatContext, typename OutputIt> struct tm_formatter {
format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); format_localized('X', ns == numeric_system::standard ? '\0' : 'E');
} }
void on_us_date() { void on_us_date() {
auto year = tm_year();
char buf[8]; char buf[8];
detail::write_digit2_separated( detail::write_digit2_separated(buf, detail::to_unsigned(tm.tm_mon + 1),
buf, detail::to_unsigned(tm.tm_mon + 1), detail::to_unsigned(tm.tm_mday),
detail::to_unsigned(tm.tm_mday), detail::to_unsigned(tm_split_year().lower),
detail::to_unsigned((year < 0 ? -year : year) % 100), '/'); '/');
out = std::copy_n(buf, sizeof(buf), out); out = std::copy_n(buf, sizeof(buf), out);
} }
void on_iso_date() { void on_iso_date() {
@ -1513,9 +1529,8 @@ template <typename FormatContext, typename OutputIt> struct tm_formatter {
} }
void on_last2_year(numeric_system ns) { void on_last2_year(numeric_system ns) {
if (ns == numeric_system::standard) { if (ns == numeric_system::standard) {
// TODO: Negative years? out = std::copy_n(
out = std::copy_n(detail::digits2(detail::to_unsigned(tm_year() % 100)), detail::digits2(detail::to_unsigned(tm_split_year().lower)), 2, out);
2, out);
} else { } else {
format_localized('y', 'O'); format_localized('y', 'O');
} }
@ -1523,13 +1538,13 @@ template <typename FormatContext, typename OutputIt> struct tm_formatter {
void on_offset_year() { format_localized('y', 'E'); } void on_offset_year() { format_localized('y', 'E'); }
void on_base_year(numeric_system ns) { void on_base_year(numeric_system ns) {
if (ns == numeric_system::standard) { if (ns == numeric_system::standard) {
// TODO: Negative years? auto split = tm_split_year();
auto format_specs = basic_format_specs<char_type>(); if (split.upper >= 0 && split.upper < 100) {
format_specs.width = 2; out = std::copy_n(detail::digits2(detail::to_unsigned(split.upper)), 2,
format_specs.align = align::numeric; out);
format_specs.fill[0] = char_type('0'); } else {
auto year = tm_year(); out = detail::write<char_type>(out, split.upper);
out = detail::write(out, year / 100, format_specs, {}); }
} else { } else {
format_localized('C', 'E'); format_localized('C', 'E');
} }

View File

@ -52,6 +52,7 @@ TEST(chrono_test, format_tm) {
"The date is 2016-04-25 11:22:33."); "The date is 2016-04-25 11:22:33.");
EXPECT_EQ(fmt::format("{:%Y}", tm), "2016"); EXPECT_EQ(fmt::format("{:%Y}", tm), "2016");
EXPECT_EQ(fmt::format("{:%C}", tm), "20"); EXPECT_EQ(fmt::format("{:%C}", tm), "20");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
EXPECT_EQ(fmt::format("{:%e}", tm), "25"); EXPECT_EQ(fmt::format("{:%e}", tm), "25");
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/16"); EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/16");
EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25"); EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25");
@ -74,6 +75,7 @@ TEST(chrono_test, format_tm_future) {
"The date is 12345-04-25 11:22:33."); "The date is 12345-04-25 11:22:33.");
EXPECT_EQ(fmt::format("{:%Y}", tm), "12345"); EXPECT_EQ(fmt::format("{:%Y}", tm), "12345");
EXPECT_EQ(fmt::format("{:%C}", tm), "123"); EXPECT_EQ(fmt::format("{:%C}", tm), "123");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/45"); EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/45");
EXPECT_EQ(fmt::format("{:%F}", tm), "12345-04-25"); EXPECT_EQ(fmt::format("{:%F}", tm), "12345-04-25");
EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33"); EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33");
@ -91,14 +93,15 @@ TEST(chrono_test, format_tm_past) {
"The date is -101-04-25 11:22:33."); "The date is -101-04-25 11:22:33.");
EXPECT_EQ(fmt::format("{:%Y}", tm), "-101"); EXPECT_EQ(fmt::format("{:%Y}", tm), "-101");
// macOS %C - "-1" // macOS %C - "-1"
// Linux %C - "-2" // Linux %C - "-2"
// Simple impl %C - "-1" // fmt %C - "-1"
EXPECT_EQ(fmt::format("{:%C}", tm), "-1"); EXPECT_EQ(fmt::format("{:%C}", tm), "-1");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
// macOS %D - "04/25/01" (%y) // macOS %D - "04/25/01" (%y)
// Linux %D - "04/25/99" (%y) // Linux %D - "04/25/99" (%y)
// Simple impl %D - "04/25/01" (%y) // fmt %D - "04/25/01" (%y)
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/01"); EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/01");
EXPECT_EQ(fmt::format("{:%F}", tm), "-101-04-25"); EXPECT_EQ(fmt::format("{:%F}", tm), "-101-04-25");