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:
parent
09a13244c8
commit
1acb73f970
@ -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);
|
||||||
|
@ -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));
|
||||||
|
Loading…
Reference in New Issue
Block a user