Fix formatting std::chrono::duration types to wide strings (#1533)

* Fix formatting chrono durations to wide strings

* Make format buffers const correct

* Add FormatWide chrono test case

* Fix incorrect wide encoding of 'µs'
I think might be a source file encoding issue, so I used \u00B5 instead.

* Update FormatWide test to use proper encoding of µs

* Revert changes to format_localized's parameters

* Use different overload of `std::time_put<T>::put` to avoid needing a format string

* Use utf8_to_utf16 instead of having redundant overloads of get_units

* Revert some minor changes

* Remove FMT_CONSTEXPR from expression

This should hopefully fix compilation on VS <2019

* Make suggested changes from code review

* Run clang-format on chrono.h

* Make sure unit isn't null before constructing a string_view from it
This commit is contained in:
zeffy 2020-01-23 18:48:36 -08:00 committed by Victor Zverovich
parent 09a13244c8
commit 1acb73f970
2 changed files with 80 additions and 25 deletions

View File

@ -8,14 +8,14 @@
#ifndef FMT_CHRONO_H_ #ifndef FMT_CHRONO_H_
#define FMT_CHRONO_H_ #define FMT_CHRONO_H_
#include "format.h"
#include "locale.h"
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
#include <locale> #include <locale>
#include <sstream> #include <sstream>
#include "format.h"
#include "locale.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
// Enable safe chrono durations, unless explicitly disabled. // Enable safe chrono durations, unless explicitly disabled.
@ -495,12 +495,12 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
handler.on_text(ptr - 1, ptr); handler.on_text(ptr - 1, ptr);
break; break;
case 'n': { case 'n': {
const char newline[] = "\n"; const Char newline[]{'\n', 0};
handler.on_text(newline, newline + 1); handler.on_text(newline, newline + 1);
break; break;
} }
case 't': { case 't': {
const char tab[] = "\t"; const Char tab[]{'\t', 0};
handler.on_text(tab, tab + 1); handler.on_text(tab, tab + 1);
break; break;
} }
@ -759,18 +759,30 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms)); return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
} }
template <typename Rep, typename OutputIt> template <typename Char, typename Rep, typename OutputIt>
OutputIt format_duration_value(OutputIt out, Rep val, int precision) { OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
if (precision >= 0) return format_to(out, "{:.{}f}", val, precision); const Char pr_f[]{'{', ':', '.', '{', '}', 'f', '}', 0};
return format_to(out, std::is_floating_point<Rep>::value ? "{:g}" : "{}", if (precision >= 0) return format_to(out, pr_f, val, precision);
const Char fp_f[]{'{', ':', 'g', '}', 0};
const Char format[]{'{', '}', 0};
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
val); val);
} }
template <typename Period, typename OutputIt> template <typename Char, typename Period, typename OutputIt>
OutputIt format_duration_unit(OutputIt out) { OutputIt format_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>()) return format_to(out, "{}", unit); if (const char* unit = get_units<Period>()) {
if (Period::den == 1) return format_to(out, "[{}]s", Period::num); string_view s(unit);
return format_to(out, "[{}/{}]s", Period::num, Period::den); if (const_check(std::is_same<Char, wchar_t>())) {
utf8_to_utf16 u(s);
return std::copy(u.c_str(), u.c_str() + u.size(), out);
}
return std::copy(s.begin(), s.end(), out);
}
const Char num_f[]{'[', '{', '}', ']', 's', 0};
if (Period::den == 1) return format_to(out, num_f, Period::num);
const Char num_def_f[]{'[', '{', '}', '/', '{', '}', ']', 's', 0};
return format_to(out, num_def_f, Period::num, Period::den);
} }
template <typename FormatContext, typename OutputIt, typename Rep, template <typename FormatContext, typename OutputIt, typename Rep,
@ -871,13 +883,13 @@ struct chrono_formatter {
void write_pinf() { std::copy_n("inf", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); }
void write_ninf() { std::copy_n("-inf", 4, out); } void write_ninf() { std::copy_n("-inf", 4, out); }
void format_localized(const tm& time, const char* format) { void format_localized(const tm& time, char format, char modifier = 0) {
if (isnan(val)) return write_nan(); if (isnan(val)) return write_nan();
auto locale = context.locale().template get<std::locale>(); auto locale = context.locale().template get<std::locale>();
auto& facet = std::use_facet<std::time_put<char_type>>(locale); auto& facet = std::use_facet<std::time_put<char_type>>(locale);
std::basic_ostringstream<char_type> os; std::basic_ostringstream<char_type> os;
os.imbue(locale); os.imbue(locale);
facet.put(os, os, ' ', &time, format, format + std::strlen(format)); facet.put(os, os, ' ', &time, format, modifier);
auto str = os.str(); auto str = os.str();
std::copy(str.begin(), str.end(), out); std::copy(str.begin(), str.end(), out);
} }
@ -907,7 +919,7 @@ struct chrono_formatter {
if (ns == numeric_system::standard) return write(hour(), 2); if (ns == numeric_system::standard) return write(hour(), 2);
auto time = tm(); auto time = tm();
time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_hour = to_nonnegative_int(hour(), 24);
format_localized(time, "%OH"); format_localized(time, 'H', 'O');
} }
void on_12_hour(numeric_system ns) { void on_12_hour(numeric_system ns) {
@ -916,7 +928,7 @@ struct chrono_formatter {
if (ns == numeric_system::standard) return write(hour12(), 2); if (ns == numeric_system::standard) return write(hour12(), 2);
auto time = tm(); auto time = tm();
time.tm_hour = to_nonnegative_int(hour12(), 12); time.tm_hour = to_nonnegative_int(hour12(), 12);
format_localized(time, "%OI"); format_localized(time, 'I', 'O');
} }
void on_minute(numeric_system ns) { void on_minute(numeric_system ns) {
@ -925,7 +937,7 @@ struct chrono_formatter {
if (ns == numeric_system::standard) return write(minute(), 2); if (ns == numeric_system::standard) return write(minute(), 2);
auto time = tm(); auto time = tm();
time.tm_min = to_nonnegative_int(minute(), 60); time.tm_min = to_nonnegative_int(minute(), 60);
format_localized(time, "%OM"); format_localized(time, 'M', 'O');
} }
void on_second(numeric_system ns) { void on_second(numeric_system ns) {
@ -950,13 +962,12 @@ 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_localized(time, "%OS"); format_localized(time, 'S', 'O');
} }
void on_12_hour_time() { void on_12_hour_time() {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
format_localized(time(), 'r');
format_localized(time(), "%r");
} }
void on_24_hour_time() { void on_24_hour_time() {
@ -980,16 +991,18 @@ struct chrono_formatter {
void on_am_pm() { void on_am_pm() {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
format_localized(time(), "%p"); format_localized(time(), 'p');
} }
void on_duration_value() { void on_duration_value() {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
write_sign(); write_sign();
out = format_duration_value(out, val, precision); out = format_duration_value<char_type>(out, val, precision);
} }
void on_duration_unit() { out = format_duration_unit<Period>(out); } void on_duration_unit() {
out = format_duration_unit<char_type, Period>(out);
}
}; };
} // namespace internal } // namespace internal
@ -1088,8 +1101,8 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
internal::handle_dynamic_spec<internal::precision_checker>( internal::handle_dynamic_spec<internal::precision_checker>(
precision, precision_ref, ctx); precision, precision_ref, ctx);
if (begin == end || *begin == '}') { if (begin == end || *begin == '}') {
out = internal::format_duration_value(out, d.count(), precision); out = internal::format_duration_value<Char>(out, d.count(), precision);
internal::format_duration_unit<Period>(out); internal::format_duration_unit<Char, Period>(out);
} else { } else {
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f( internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
ctx, out, d); ctx, out, d);

View File

@ -145,6 +145,48 @@ TEST(ChronoTest, FormatDefault) {
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42))); fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
} }
TEST(ChronoTest, FormatWide) {
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
EXPECT_EQ(L"42as",
fmt::format(L"{}", std::chrono::duration<int, std::atto>(42)));
EXPECT_EQ(L"42fs",
fmt::format(L"{}", std::chrono::duration<int, std::femto>(42)));
EXPECT_EQ(L"42ps",
fmt::format(L"{}", std::chrono::duration<int, std::pico>(42)));
EXPECT_EQ(L"42ns", fmt::format(L"{}", std::chrono::nanoseconds(42)));
EXPECT_EQ(L"42\u00B5s", fmt::format(L"{}", std::chrono::microseconds(42)));
EXPECT_EQ(L"42ms", fmt::format(L"{}", std::chrono::milliseconds(42)));
EXPECT_EQ(L"42cs",
fmt::format(L"{}", std::chrono::duration<int, std::centi>(42)));
EXPECT_EQ(L"42ds",
fmt::format(L"{}", std::chrono::duration<int, std::deci>(42)));
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
EXPECT_EQ(L"42das",
fmt::format(L"{}", std::chrono::duration<int, std::deca>(42)));
EXPECT_EQ(L"42hs",
fmt::format(L"{}", std::chrono::duration<int, std::hecto>(42)));
EXPECT_EQ(L"42ks",
fmt::format(L"{}", std::chrono::duration<int, std::kilo>(42)));
EXPECT_EQ(L"42Ms",
fmt::format(L"{}", std::chrono::duration<int, std::mega>(42)));
EXPECT_EQ(L"42Gs",
fmt::format(L"{}", std::chrono::duration<int, std::giga>(42)));
EXPECT_EQ(L"42Ts",
fmt::format(L"{}", std::chrono::duration<int, std::tera>(42)));
EXPECT_EQ(L"42Ps",
fmt::format(L"{}", std::chrono::duration<int, std::peta>(42)));
EXPECT_EQ(L"42Es",
fmt::format(L"{}", std::chrono::duration<int, std::exa>(42)));
EXPECT_EQ(L"42m", fmt::format(L"{}", std::chrono::minutes(42)));
EXPECT_EQ(L"42h", fmt::format(L"{}", std::chrono::hours(42)));
EXPECT_EQ(
L"42[15]s",
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 1>>(42)));
EXPECT_EQ(
L"42[15/4]s",
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
}
TEST(ChronoTest, Align) { TEST(ChronoTest, Align) {
auto s = std::chrono::seconds(42); auto s = std::chrono::seconds(42);
EXPECT_EQ("42s ", fmt::format("{:5}", s)); EXPECT_EQ("42s ", fmt::format("{:5}", s));