mirror of
https://github.com/fmtlib/fmt.git
synced 2025-02-09 21:39:58 +00:00
Implement glibc ext for sec, min, and hour (#3271)
This commit is contained in:
parent
44e0eea94e
commit
7718eeeacc
@ -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() {
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user