mirror of
https://github.com/fmtlib/fmt.git
synced 2025-07-30 10:47:35 +02:00
Fix timezone handling in tm
This commit is contained in:
@ -420,14 +420,11 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
|
||||
}
|
||||
|
||||
template <typename Rep1, typename Rep2>
|
||||
struct is_same_arithmetic_type
|
||||
: public std::integral_constant<bool,
|
||||
(std::is_integral<Rep1>::value &&
|
||||
std::is_integral<Rep2>::value) ||
|
||||
(std::is_floating_point<Rep1>::value &&
|
||||
std::is_floating_point<Rep2>::value)> {
|
||||
};
|
||||
template <typename T, typename U>
|
||||
using is_similar_arithmetic_type =
|
||||
bool_constant<(std::is_integral<T>::value && std::is_integral<U>::value) ||
|
||||
(std::is_floating_point<T>::value &&
|
||||
std::is_floating_point<U>::value)>;
|
||||
|
||||
FMT_NORETURN inline void throw_duration_error() {
|
||||
FMT_THROW(format_error("cannot format duration"));
|
||||
@ -486,9 +483,9 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
#endif
|
||||
}
|
||||
|
||||
template <
|
||||
typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(
|
||||
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
|
||||
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
// Mixed integer <-> float cast is not supported by safe_duration_cast.
|
||||
return std::chrono::duration_cast<To>(from);
|
||||
@ -520,6 +517,7 @@ template <typename... T> auto current_zone(T...) -> time_zone* {
|
||||
template <typename... T> void _tzset(T...) {}
|
||||
} // namespace tz
|
||||
|
||||
// DEPRECATED!
|
||||
inline void tzset_once() {
|
||||
static bool init = []() {
|
||||
using namespace tz;
|
||||
@ -915,11 +913,11 @@ template <typename Derived> struct null_chrono_spec_handler {
|
||||
|
||||
class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
|
||||
private:
|
||||
bool no_timezone_ = false;
|
||||
bool has_timezone_ = false;
|
||||
|
||||
public:
|
||||
constexpr explicit tm_format_checker(bool no_timezone = false)
|
||||
: no_timezone_(no_timezone) {}
|
||||
constexpr explicit tm_format_checker(bool has_timezone)
|
||||
: has_timezone_(has_timezone) {}
|
||||
|
||||
FMT_NORETURN inline void unsupported() {
|
||||
FMT_THROW(format_error("no format"));
|
||||
@ -959,10 +957,10 @@ class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
|
||||
FMT_CONSTEXPR void on_iso_time() {}
|
||||
FMT_CONSTEXPR void on_am_pm() {}
|
||||
FMT_CONSTEXPR void on_utc_offset(numeric_system) {
|
||||
if (no_timezone_) FMT_THROW(format_error("no timezone"));
|
||||
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
|
||||
}
|
||||
FMT_CONSTEXPR void on_tz_name() {
|
||||
if (no_timezone_) FMT_THROW(format_error("no timezone"));
|
||||
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
|
||||
}
|
||||
};
|
||||
|
||||
@ -1094,7 +1092,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
|
||||
|
||||
// Format subseconds which are given as a floating point type with an
|
||||
// appropriate number of digits. We cannot pass the Duration here, as we
|
||||
// explicitly need to pass the Rep value in the chrono_formatter.
|
||||
// explicitly need to pass the Rep value in the duration_formatter.
|
||||
template <typename Duration>
|
||||
void write_floating_seconds(memory_buffer& buf, Duration duration,
|
||||
int num_fractional_digits = -1) {
|
||||
@ -1128,7 +1126,7 @@ class tm_writer {
|
||||
static constexpr int days_per_week = 7;
|
||||
|
||||
const std::locale& loc_;
|
||||
const bool is_classic_;
|
||||
bool is_classic_;
|
||||
OutputIt out_;
|
||||
const Duration* subsecs_;
|
||||
const std::tm& tm_;
|
||||
@ -1164,8 +1162,8 @@ class tm_writer {
|
||||
}
|
||||
|
||||
auto tm_hour12() const noexcept -> int {
|
||||
const auto h = tm_hour();
|
||||
const auto z = h < 12 ? h : h - 12;
|
||||
auto h = tm_hour();
|
||||
auto z = h < 12 ? h : h - 12;
|
||||
return z == 0 ? 12 : z;
|
||||
}
|
||||
|
||||
@ -1181,11 +1179,11 @@ class tm_writer {
|
||||
|
||||
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
|
||||
auto iso_year_weeks(long long curr_year) const noexcept -> int {
|
||||
const auto prev_year = curr_year - 1;
|
||||
const auto curr_p =
|
||||
auto prev_year = curr_year - 1;
|
||||
auto curr_p =
|
||||
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
|
||||
days_per_week;
|
||||
const auto prev_p =
|
||||
auto prev_p =
|
||||
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
|
||||
days_per_week;
|
||||
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
|
||||
@ -1195,15 +1193,15 @@ class tm_writer {
|
||||
days_per_week;
|
||||
}
|
||||
auto tm_iso_week_year() const noexcept -> long long {
|
||||
const auto year = tm_year();
|
||||
const auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
auto year = tm_year();
|
||||
auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
if (w < 1) return year - 1;
|
||||
if (w > iso_year_weeks(year)) return year + 1;
|
||||
return year;
|
||||
}
|
||||
auto tm_iso_week_of_year() const noexcept -> int {
|
||||
const auto year = tm_year();
|
||||
const auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
auto year = tm_year();
|
||||
auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
if (w < 1) return iso_year_weeks(year - 1);
|
||||
if (w > iso_year_weeks(year)) return 1;
|
||||
return w;
|
||||
@ -1240,9 +1238,8 @@ class tm_writer {
|
||||
uint32_or_64_or_128_t<long long> n = to_unsigned(year);
|
||||
const int num_digits = count_digits(n);
|
||||
if (negative && pad == pad_type::zero) *out_++ = '-';
|
||||
if (width > num_digits) {
|
||||
if (width > num_digits)
|
||||
out_ = detail::write_padding(out_, pad, width - num_digits);
|
||||
}
|
||||
if (negative && pad != pad_type::zero) *out_++ = '-';
|
||||
out_ = format_decimal<Char>(out_, n, num_digits);
|
||||
}
|
||||
@ -1467,11 +1464,10 @@ class tm_writer {
|
||||
void on_day_of_year(pad_type pad) {
|
||||
auto yday = tm_yday() + 1;
|
||||
auto digit1 = yday / 100;
|
||||
if (digit1 != 0) {
|
||||
if (digit1 != 0)
|
||||
write1(digit1);
|
||||
} else {
|
||||
else
|
||||
out_ = detail::write_padding(out_, pad);
|
||||
}
|
||||
write2(yday % 100, pad);
|
||||
}
|
||||
|
||||
@ -1608,18 +1604,16 @@ template <typename Rep, typename Period,
|
||||
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
||||
inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
|
||||
-> std::chrono::duration<Rep, std::milli> {
|
||||
// this may overflow and/or the result may not fit in the
|
||||
// target type.
|
||||
// This may overflow and/or the result may not fit in the target type.
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
using CommonSecondsType =
|
||||
using common_seconds_type =
|
||||
typename std::common_type<decltype(d), std::chrono::seconds>::type;
|
||||
const auto d_as_common = detail::duration_cast<CommonSecondsType>(d);
|
||||
const auto d_as_whole_seconds =
|
||||
auto d_as_common = detail::duration_cast<common_seconds_type>(d);
|
||||
auto d_as_whole_seconds =
|
||||
detail::duration_cast<std::chrono::seconds>(d_as_common);
|
||||
// this conversion should be nonproblematic
|
||||
const auto diff = d_as_common - d_as_whole_seconds;
|
||||
const auto ms =
|
||||
detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
|
||||
// This conversion should be nonproblematic.
|
||||
auto diff = d_as_common - d_as_whole_seconds;
|
||||
auto ms = detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
|
||||
return ms;
|
||||
#else
|
||||
auto s = detail::duration_cast<std::chrono::seconds>(d);
|
||||
@ -1693,7 +1687,7 @@ class get_locale {
|
||||
|
||||
template <typename FormatContext, typename OutputIt, typename Rep,
|
||||
typename Period>
|
||||
struct chrono_formatter {
|
||||
struct duration_formatter {
|
||||
FormatContext& context;
|
||||
OutputIt out;
|
||||
int precision;
|
||||
@ -1711,8 +1705,8 @@ struct chrono_formatter {
|
||||
using char_type = typename FormatContext::char_type;
|
||||
using tm_writer_type = tm_writer<OutputIt, char_type>;
|
||||
|
||||
chrono_formatter(FormatContext& ctx, OutputIt o,
|
||||
std::chrono::duration<Rep, Period> d)
|
||||
duration_formatter(FormatContext& ctx, OutputIt o,
|
||||
std::chrono::duration<Rep, Period> d)
|
||||
: context(ctx),
|
||||
out(o),
|
||||
val(static_cast<rep>(d.count())),
|
||||
@ -1730,19 +1724,16 @@ struct chrono_formatter {
|
||||
|
||||
// returns true if nan or inf, writes to out.
|
||||
auto handle_nan_inf() -> bool {
|
||||
if (isfinite(val)) {
|
||||
return false;
|
||||
}
|
||||
if (isfinite(val)) return false;
|
||||
if (isnan(val)) {
|
||||
write_nan();
|
||||
return true;
|
||||
}
|
||||
// must be +-inf
|
||||
if (val > 0) {
|
||||
write_pinf();
|
||||
} else {
|
||||
write_ninf();
|
||||
}
|
||||
if (val > 0)
|
||||
std::copy_n("inf", 3, out);
|
||||
else
|
||||
std::copy_n("-inf", 4, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1770,10 +1761,9 @@ struct chrono_formatter {
|
||||
}
|
||||
|
||||
void write_sign() {
|
||||
if (negative) {
|
||||
*out++ = '-';
|
||||
negative = false;
|
||||
}
|
||||
if (!negative) return;
|
||||
*out++ = '-';
|
||||
negative = false;
|
||||
}
|
||||
|
||||
void write(Rep value, int width, pad_type pad = pad_type::zero) {
|
||||
@ -1789,8 +1779,6 @@ struct chrono_formatter {
|
||||
}
|
||||
|
||||
void write_nan() { std::copy_n("nan", 3, out); }
|
||||
void write_pinf() { std::copy_n("inf", 3, out); }
|
||||
void write_ninf() { std::copy_n("-inf", 4, out); }
|
||||
|
||||
template <typename Callback, typename... Args>
|
||||
void format_tm(const tm& time, Callback cb, Args... args) {
|
||||
@ -1872,9 +1860,8 @@ struct chrono_formatter {
|
||||
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
|
||||
precision);
|
||||
if (negative) *out++ = '-';
|
||||
if (buf.size() < 2 || buf[1] == '.') {
|
||||
if (buf.size() < 2 || buf[1] == '.')
|
||||
out = detail::write_padding(out, pad);
|
||||
}
|
||||
out = copy<char_type>(buf.begin(), buf.end(), out);
|
||||
} else {
|
||||
write(second(), 2, pad);
|
||||
@ -1995,7 +1982,7 @@ class year_month_day {
|
||||
constexpr auto month() const noexcept -> fmt::month { return month_; }
|
||||
constexpr auto day() const noexcept -> fmt::day { return day_; }
|
||||
};
|
||||
#endif
|
||||
#endif // __cpp_lib_chrono >= 201907
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<weekday, Char> : private formatter<std::tm, Char> {
|
||||
@ -2188,9 +2175,9 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
out = detail::format_duration_value<Char>(out, d.count(), precision);
|
||||
detail::format_duration_unit<Char, Period>(out);
|
||||
} else {
|
||||
using chrono_formatter =
|
||||
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>;
|
||||
auto f = chrono_formatter(ctx, out, d);
|
||||
using duration_formatter =
|
||||
detail::duration_formatter<FormatContext, decltype(out), Rep, Period>;
|
||||
auto f = duration_formatter(ctx, out, d);
|
||||
f.precision = precision;
|
||||
f.localized = localized_;
|
||||
detail::parse_chrono_format(begin, end, f);
|
||||
@ -2204,31 +2191,12 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
|
||||
protected:
|
||||
basic_string_view<Char> fmt_ =
|
||||
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
|
||||
|
||||
template <typename Duration, typename FormatContext>
|
||||
auto do_format(const std::tm& tm, FormatContext& ctx,
|
||||
const Duration* subsecs) const -> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
auto out = basic_appender<Char>(buf);
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
|
||||
auto loc_ref = ctx.locale();
|
||||
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
|
||||
auto w =
|
||||
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
|
||||
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
|
||||
return detail::write(
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
|
||||
bool no_timezone = false) -> const Char* {
|
||||
protected:
|
||||
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, bool has_timezone)
|
||||
-> const Char* {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
|
||||
@ -2242,15 +2210,33 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
}
|
||||
|
||||
end = detail::parse_chrono_format(it, end,
|
||||
detail::tm_format_checker(no_timezone));
|
||||
detail::tm_format_checker(has_timezone));
|
||||
// Replace the default format string only if the new spec is not empty.
|
||||
if (end != it) fmt_ = {it, detail::to_unsigned(end - it)};
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename Duration, typename FormatContext>
|
||||
auto do_format(const std::tm& tm, FormatContext& ctx,
|
||||
const Duration* subsecs) const -> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
auto out = basic_appender<Char>(buf);
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
|
||||
auto loc_ref = ctx.locale();
|
||||
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
|
||||
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
|
||||
loc, out, tm, subsecs);
|
||||
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
|
||||
return detail::write(
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return do_parse(ctx);
|
||||
return do_parse(ctx, detail::has_member_data_tm_gmtoff<std::tm>::value);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
@ -2260,8 +2246,13 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
// DEPRECATED! Reversed order of template parameters.
|
||||
template <typename Char, typename Duration>
|
||||
struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return this->do_parse(ctx, true);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(sys_time<Duration> val, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
@ -2281,7 +2272,7 @@ struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
--tm.tm_sec;
|
||||
else
|
||||
tm = gmtime(val - second);
|
||||
subsecs += detail::duration_cast<Duration>(std::chrono::seconds(1));
|
||||
subsecs += second;
|
||||
}
|
||||
return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs);
|
||||
}
|
||||
@ -2299,9 +2290,10 @@ struct formatter<utc_time<Duration>, Char>
|
||||
};
|
||||
|
||||
template <typename Duration, typename Char>
|
||||
struct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
struct formatter<local_time<Duration>, Char>
|
||||
: private formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return this->do_parse(ctx, true);
|
||||
return this->do_parse(ctx, false);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
@ -2310,9 +2302,9 @@ struct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
auto time_since_epoch = val.time_since_epoch();
|
||||
auto seconds_since_epoch =
|
||||
detail::duration_cast<std::chrono::seconds>(time_since_epoch);
|
||||
// Use gmtime to prevent time conversion since local_time has an
|
||||
// Use gmtime to prevent time zone conversion since local_time has an
|
||||
// unspecified time zone.
|
||||
auto t = gmtime(seconds_since_epoch.count());
|
||||
std::tm t = gmtime(seconds_since_epoch.count());
|
||||
using period = typename Duration::period;
|
||||
if (period::num == 1 && period::den == 1 &&
|
||||
!std::is_floating_point<typename Duration::rep>::value) {
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
using fmt::runtime;
|
||||
using fmt::sys_time;
|
||||
using fmt::sys_time;
|
||||
using testing::Contains;
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||
@ -336,9 +335,31 @@ TEST(chrono_test, local_time) {
|
||||
fmt::format_error, "no timezone");
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(fmt::detail::has_member_data_tm_gmtoff<T>::value)>
|
||||
bool set_gmtoff(T& time, long offset) {
|
||||
time.tm_gmtoff = offset;
|
||||
return true;
|
||||
}
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(!fmt::detail::has_member_data_tm_gmtoff<T>::value)>
|
||||
bool set_gmtoff(T&, long) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST(chrono_test, tm) {
|
||||
auto time = fmt::gmtime(290088000);
|
||||
test_time(time);
|
||||
if (set_gmtoff(time, -28800)) {
|
||||
EXPECT_EQ(fmt::format(fmt::runtime("{:%z}"), time), "-0800");
|
||||
EXPECT_EQ(fmt::format(fmt::runtime("{:%Ez}"), time), "-08:00");
|
||||
EXPECT_EQ(fmt::format(fmt::runtime("{:%Oz}"), time), "-08:00");
|
||||
} else {
|
||||
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
|
||||
fmt::format_error, "no timezone");
|
||||
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
|
||||
fmt::format_error, "no timezone");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(chrono_test, daylight_savings_time_end) {
|
||||
@ -740,11 +761,11 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
|
||||
}
|
||||
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(-123456789)), "-00.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(9123456789)), "09.123456789");
|
||||
// Verify that only the seconds part is extracted and printed.
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123456789)), "39.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123000000)), "39.123000000");
|
||||
{
|
||||
// Now the hour is printed, and we also test if negative doubles work.
|
||||
auto dur = nanoseconds_dbl{-99123456789};
|
||||
@ -755,7 +776,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
}
|
||||
// Check that durations with precision greater than std::chrono::seconds have
|
||||
// fixed precision, and print zeros even if there is no fractional part.
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds(7000000)),
|
||||
"07.000000");
|
||||
EXPECT_EQ(fmt::format("{:%S}",
|
||||
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
||||
@ -775,9 +796,9 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
"-05:27.68");
|
||||
|
||||
// Check that floating point seconds with ratio<1,1> are printed.
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}),
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>(1.5)),
|
||||
"01.500000");
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>(-61.25)),
|
||||
"-01:01.250000");
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user