Skip to content

Commit

Permalink
Fix formatting std::chrono::duration types to wide strings (#1533)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
zeffy authored and vitaut committed Jan 24, 2020
1 parent 09a1324 commit 1acb73f
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 25 deletions.
63 changes: 38 additions & 25 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
#ifndef FMT_CHRONO_H_
#define FMT_CHRONO_H_

#include "format.h"
#include "locale.h"

#include <chrono>
#include <ctime>
#include <locale>
#include <sstream>

#include "format.h"
#include "locale.h"

FMT_BEGIN_NAMESPACE

// Enable safe chrono durations, unless explicitly disabled.
Expand Down Expand Up @@ -495,12 +495,12 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
handler.on_text(ptr - 1, ptr);
break;
case 'n': {
const char newline[] = "\n";
const Char newline[]{'\n', 0};
handler.on_text(newline, newline + 1);
break;
}
case 't': {
const char tab[] = "\t";
const Char tab[]{'\t', 0};
handler.on_text(tab, tab + 1);
break;
}
Expand Down Expand Up @@ -759,18 +759,30 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
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) {
if (precision >= 0) return format_to(out, "{:.{}f}", val, precision);
return format_to(out, std::is_floating_point<Rep>::value ? "{:g}" : "{}",
const Char pr_f[]{'{', ':', '.', '{', '}', 'f', '}', 0};
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);
}

template <typename Period, typename OutputIt>
template <typename Char, typename Period, typename OutputIt>
OutputIt format_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>()) return format_to(out, "{}", unit);
if (Period::den == 1) return format_to(out, "[{}]s", Period::num);
return format_to(out, "[{}/{}]s", Period::num, Period::den);
if (const char* unit = get_units<Period>()) {
string_view s(unit);
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,
Expand Down Expand Up @@ -871,13 +883,13 @@ struct chrono_formatter {
void write_pinf() { std::copy_n("inf", 3, 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();
auto locale = context.locale().template get<std::locale>();
auto& facet = std::use_facet<std::time_put<char_type>>(locale);
std::basic_ostringstream<char_type> os;
os.imbue(locale);
facet.put(os, os, ' ', &time, format, format + std::strlen(format));
facet.put(os, os, ' ', &time, format, modifier);
auto str = os.str();
std::copy(str.begin(), str.end(), out);
}
Expand Down Expand Up @@ -907,7 +919,7 @@ struct chrono_formatter {
if (ns == numeric_system::standard) return write(hour(), 2);
auto time = tm();
time.tm_hour = to_nonnegative_int(hour(), 24);
format_localized(time, "%OH");
format_localized(time, 'H', 'O');
}

void on_12_hour(numeric_system ns) {
Expand All @@ -916,7 +928,7 @@ struct chrono_formatter {
if (ns == numeric_system::standard) return write(hour12(), 2);
auto time = tm();
time.tm_hour = to_nonnegative_int(hour12(), 12);
format_localized(time, "%OI");
format_localized(time, 'I', 'O');
}

void on_minute(numeric_system ns) {
Expand All @@ -925,7 +937,7 @@ struct chrono_formatter {
if (ns == numeric_system::standard) return write(minute(), 2);
auto time = tm();
time.tm_min = to_nonnegative_int(minute(), 60);
format_localized(time, "%OM");
format_localized(time, 'M', 'O');
}

void on_second(numeric_system ns) {
Expand All @@ -950,13 +962,12 @@ struct chrono_formatter {
}
auto time = tm();
time.tm_sec = to_nonnegative_int(second(), 60);
format_localized(time, "%OS");
format_localized(time, 'S', 'O');
}

void on_12_hour_time() {
if (handle_nan_inf()) return;

format_localized(time(), "%r");
format_localized(time(), 'r');
}

void on_24_hour_time() {
Expand All @@ -980,16 +991,18 @@ struct chrono_formatter {

void on_am_pm() {
if (handle_nan_inf()) return;
format_localized(time(), "%p");
format_localized(time(), 'p');
}

void on_duration_value() {
if (handle_nan_inf()) return;
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

Expand Down Expand Up @@ -1088,8 +1101,8 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
internal::handle_dynamic_spec<internal::precision_checker>(
precision, precision_ref, ctx);
if (begin == end || *begin == '}') {
out = internal::format_duration_value(out, d.count(), precision);
internal::format_duration_unit<Period>(out);
out = internal::format_duration_value<Char>(out, d.count(), precision);
internal::format_duration_unit<Char, Period>(out);
} else {
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
ctx, out, d);
Expand Down
42 changes: 42 additions & 0 deletions test/chrono-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,48 @@ TEST(ChronoTest, FormatDefault) {
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) {
auto s = std::chrono::seconds(42);
EXPECT_EQ("42s ", fmt::format("{:5}", s));
Expand Down

0 comments on commit 1acb73f

Please sign in to comment.