Implement glibc ext for sec, min, and hour (#3271)

This commit is contained in:
Shawn Zhong 2023-02-08 11:22:58 -06:00 committed by GitHub
parent 44e0eea94e
commit 7718eeeacc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 151 additions and 40 deletions

View File

@ -664,6 +664,30 @@ enum class numeric_system {
alternative alternative
}; };
// Glibc extensions for formatting numeric values.
enum class pad_type {
unspecified,
// Do not pad a numeric result string.
none,
// Pad a numeric result string with zeros even if the conversion specifier
// character uses space-padding by default.
zero,
// Pad a numeric result string with spaces.
space,
};
template <typename OutputIt>
auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt {
if (pad == pad_type::none) return out;
return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0');
}
template <typename OutputIt>
auto write_padding(OutputIt out, pad_type pad) -> OutputIt {
if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0';
return out;
}
// Parses a put_time-like format string and invokes handler actions. // Parses a put_time-like format string and invokes handler actions.
template <typename Char, typename Handler> template <typename Char, typename Handler>
FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
@ -672,6 +696,7 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
if (begin == end || *begin == '}') return begin; if (begin == end || *begin == '}') return begin;
if (*begin != '%') FMT_THROW(format_error("invalid format")); if (*begin != '%') FMT_THROW(format_error("invalid format"));
auto ptr = begin; auto ptr = begin;
pad_type pad = pad_type::unspecified;
while (ptr != end) { while (ptr != end) {
auto c = *ptr; auto c = *ptr;
if (c == '}') break; if (c == '}') break;
@ -682,6 +707,22 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
if (begin != ptr) handler.on_text(begin, ptr); if (begin != ptr) handler.on_text(begin, ptr);
++ptr; // consume '%' ++ptr; // consume '%'
if (ptr == end) FMT_THROW(format_error("invalid format")); if (ptr == end) FMT_THROW(format_error("invalid format"));
c = *ptr;
switch (c) {
case '_':
pad = pad_type::space;
++ptr;
break;
case '-':
pad = pad_type::none;
++ptr;
break;
case '0':
pad = pad_type::zero;
++ptr;
break;
}
if (ptr == end) FMT_THROW(format_error("invalid format"));
c = *ptr++; c = *ptr++;
switch (c) { switch (c) {
case '%': case '%':
@ -758,16 +799,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
break; break;
// Hour, minute, second: // Hour, minute, second:
case 'H': case 'H':
handler.on_24_hour(numeric_system::standard); handler.on_24_hour(numeric_system::standard, pad);
break; break;
case 'I': case 'I':
handler.on_12_hour(numeric_system::standard); handler.on_12_hour(numeric_system::standard, pad);
break; break;
case 'M': case 'M':
handler.on_minute(numeric_system::standard); handler.on_minute(numeric_system::standard, pad);
break; break;
case 'S': case 'S':
handler.on_second(numeric_system::standard); handler.on_second(numeric_system::standard, pad);
break; break;
// Other: // Other:
case 'c': case 'c':
@ -872,16 +913,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
handler.on_dec1_weekday(numeric_system::alternative); handler.on_dec1_weekday(numeric_system::alternative);
break; break;
case 'H': case 'H':
handler.on_24_hour(numeric_system::alternative); handler.on_24_hour(numeric_system::alternative, pad);
break; break;
case 'I': case 'I':
handler.on_12_hour(numeric_system::alternative); handler.on_12_hour(numeric_system::alternative, pad);
break; break;
case 'M': case 'M':
handler.on_minute(numeric_system::alternative); handler.on_minute(numeric_system::alternative, pad);
break; break;
case 'S': case 'S':
handler.on_second(numeric_system::alternative); handler.on_second(numeric_system::alternative, pad);
break; break;
case 'z': case 'z':
handler.on_utc_offset(numeric_system::alternative); handler.on_utc_offset(numeric_system::alternative);
@ -965,10 +1006,10 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
FMT_CONSTEXPR void on_day_of_year() {} FMT_CONSTEXPR void on_day_of_year() {}
FMT_CONSTEXPR void on_day_of_month(numeric_system) {} FMT_CONSTEXPR void on_day_of_month(numeric_system) {}
FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {}
FMT_CONSTEXPR void on_24_hour(numeric_system) {} FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_12_hour(numeric_system) {} FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_minute(numeric_system) {} FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_second(numeric_system) {} FMT_CONSTEXPR void on_second(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_datetime(numeric_system) {} FMT_CONSTEXPR void on_datetime(numeric_system) {}
FMT_CONSTEXPR void on_loc_date(numeric_system) {} FMT_CONSTEXPR void on_loc_date(numeric_system) {}
FMT_CONSTEXPR void on_loc_time(numeric_system) {} FMT_CONSTEXPR void on_loc_time(numeric_system) {}
@ -1238,6 +1279,17 @@ class tm_writer {
*out_++ = *d++; *out_++ = *d++;
*out_++ = *d; *out_++ = *d;
} }
void write2(int value, pad_type pad) {
unsigned int v = to_unsigned(value) % 100;
if (v >= 10) {
const char* d = digits2(v);
*out_++ = *d++;
*out_++ = *d;
} else {
out_ = detail::write_padding(out_, pad);
*out_++ = static_cast<char>('0' + v);
}
}
void write_year_extended(long long year) { void write_year_extended(long long year) {
// At least 4 characters. // At least 4 characters.
@ -1514,23 +1566,25 @@ class tm_writer {
} }
} }
void on_24_hour(numeric_system ns) { void on_24_hour(numeric_system ns, pad_type pad) {
if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour()); if (is_classic_ || ns == numeric_system::standard)
return write2(tm_hour(), pad);
format_localized('H', 'O'); format_localized('H', 'O');
} }
void on_12_hour(numeric_system ns) { void on_12_hour(numeric_system ns, pad_type pad) {
if (is_classic_ || ns == numeric_system::standard) if (is_classic_ || ns == numeric_system::standard)
return write2(tm_hour12()); return write2(tm_hour12(), pad);
format_localized('I', 'O'); format_localized('I', 'O');
} }
void on_minute(numeric_system ns) { void on_minute(numeric_system ns, pad_type pad) {
if (is_classic_ || ns == numeric_system::standard) return write2(tm_min()); if (is_classic_ || ns == numeric_system::standard)
return write2(tm_min(), pad);
format_localized('M', 'O'); format_localized('M', 'O');
} }
void on_second(numeric_system ns) { void on_second(numeric_system ns, pad_type pad) {
if (is_classic_ || ns == numeric_system::standard) { if (is_classic_ || ns == numeric_system::standard) {
write2(tm_sec()); write2(tm_sec(), pad);
if (subsecs_) { if (subsecs_) {
if (std::is_floating_point<typename Duration::rep>::value) { if (std::is_floating_point<typename Duration::rep>::value) {
auto buf = memory_buffer(); auto buf = memory_buffer();
@ -1594,10 +1648,10 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
template <typename Char> template <typename Char>
FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
FMT_CONSTEXPR void on_24_hour(numeric_system) {} FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_12_hour(numeric_system) {} FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_minute(numeric_system) {} FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_second(numeric_system) {} FMT_CONSTEXPR void on_second(numeric_system, pad_type) {}
FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_12_hour_time() {}
FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {}
FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_iso_time() {}
@ -1819,13 +1873,15 @@ struct chrono_formatter {
} }
} }
void write(Rep value, int width) { void write(Rep value, int width, pad_type pad = pad_type::unspecified) {
write_sign(); write_sign();
if (isnan(value)) return write_nan(); if (isnan(value)) return write_nan();
uint32_or_64_or_128_t<int> n = uint32_or_64_or_128_t<int> n =
to_unsigned(to_nonnegative_int(value, max_value<int>())); to_unsigned(to_nonnegative_int(value, max_value<int>()));
int num_digits = detail::count_digits(n); int num_digits = detail::count_digits(n);
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); if (width > num_digits) {
out = detail::write_padding(out, pad, width - num_digits);
}
out = format_decimal<char_type>(out, n, num_digits).end; out = format_decimal<char_type>(out, n, num_digits).end;
} }
@ -1874,34 +1930,34 @@ struct chrono_formatter {
void on_day_of_month(numeric_system) {} void on_day_of_month(numeric_system) {}
void on_day_of_month_space(numeric_system) {} void on_day_of_month_space(numeric_system) {}
void on_24_hour(numeric_system ns) { void on_24_hour(numeric_system ns, pad_type pad) {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
if (ns == numeric_system::standard) return write(hour(), 2); if (ns == numeric_system::standard) return write(hour(), 2, pad);
auto time = tm(); auto time = tm();
time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_hour = to_nonnegative_int(hour(), 24);
format_tm(time, &tm_writer_type::on_24_hour, ns); format_tm(time, &tm_writer_type::on_24_hour, ns, pad);
} }
void on_12_hour(numeric_system ns) { void on_12_hour(numeric_system ns, pad_type pad) {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
if (ns == numeric_system::standard) return write(hour12(), 2); if (ns == numeric_system::standard) return write(hour12(), 2, pad);
auto time = tm(); auto time = tm();
time.tm_hour = to_nonnegative_int(hour12(), 12); time.tm_hour = to_nonnegative_int(hour12(), 12);
format_tm(time, &tm_writer_type::on_12_hour, ns); format_tm(time, &tm_writer_type::on_12_hour, ns, pad);
} }
void on_minute(numeric_system ns) { void on_minute(numeric_system ns, pad_type pad) {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
if (ns == numeric_system::standard) return write(minute(), 2); if (ns == numeric_system::standard) return write(minute(), 2, pad);
auto time = tm(); auto time = tm();
time.tm_min = to_nonnegative_int(minute(), 60); time.tm_min = to_nonnegative_int(minute(), 60);
format_tm(time, &tm_writer_type::on_minute, ns); format_tm(time, &tm_writer_type::on_minute, ns, pad);
} }
void on_second(numeric_system ns) { void on_second(numeric_system ns, pad_type pad) {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
if (ns == numeric_system::standard) { if (ns == numeric_system::standard) {
@ -1910,10 +1966,12 @@ struct chrono_formatter {
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val), write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
precision); precision);
if (negative) *out++ = '-'; if (negative) *out++ = '-';
if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; if (buf.size() < 2 || buf[1] == '.') {
out = detail::write_padding(out, pad);
}
out = std::copy(buf.begin(), buf.end(), out); out = std::copy(buf.begin(), buf.end(), out);
} else { } else {
write(second(), 2); write(second(), 2, pad);
write_fractional_seconds<char_type>( write_fractional_seconds<char_type>(
out, std::chrono::duration<rep, Period>(val), precision); out, std::chrono::duration<rep, Period>(val), precision);
} }
@ -1921,7 +1979,7 @@ struct chrono_formatter {
} }
auto time = tm(); auto time = tm();
time.tm_sec = to_nonnegative_int(second(), 60); time.tm_sec = to_nonnegative_int(second(), 60);
format_tm(time, &tm_writer_type::on_second, ns); format_tm(time, &tm_writer_type::on_second, ns, pad);
} }
void on_12_hour_time() { void on_12_hour_time() {
@ -1945,7 +2003,7 @@ struct chrono_formatter {
on_24_hour_time(); on_24_hour_time();
*out++ = ':'; *out++ = ':';
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
on_second(numeric_system::standard); on_second(numeric_system::standard, pad_type::unspecified);
} }
void on_am_pm() { void on_am_pm() {

View File

@ -921,3 +921,56 @@ TEST(chrono_test, timestamps_sub_seconds) {
EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d)); EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d));
} }
} }
TEST(chrono_test, glibc_extensions) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%0}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%_}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%-}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
{
const auto d = std::chrono::hours(1) + std::chrono::minutes(2) +
std::chrono::seconds(3);
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", d), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", d), "1,1,2,3");
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", d), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", d), "1,1,2,3");
}
{
const auto tm = make_tm(1970, 1, 1, 1, 2, 3);
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", tm), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", tm), "1,1,2,3");
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", tm), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", tm), "1,1,2,3");
}
{
const auto d = std::chrono::seconds(3) + std::chrono::milliseconds(140);
EXPECT_EQ(fmt::format("{:%S}", d), "03.140");
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140");
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140");
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140");
}
{
const auto d = std::chrono::duration<double>(3.14);
EXPECT_EQ(fmt::format("{:%S}", d), "03.140000");
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000");
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000");
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000");
}
}