Skip to content

Commit 1b2c401

Browse files
committed
Fix timezone handling in tm
1 parent f10b6dd commit 1b2c401

2 files changed

Lines changed: 104 additions & 91 deletions

File tree

include/fmt/chrono.h

Lines changed: 75 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -420,14 +420,11 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
420420
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
421421
}
422422

423-
template <typename Rep1, typename Rep2>
424-
struct is_same_arithmetic_type
425-
: public std::integral_constant<bool,
426-
(std::is_integral<Rep1>::value &&
427-
std::is_integral<Rep2>::value) ||
428-
(std::is_floating_point<Rep1>::value &&
429-
std::is_floating_point<Rep2>::value)> {
430-
};
423+
template <typename T, typename U>
424+
using is_similar_arithmetic_type =
425+
bool_constant<(std::is_integral<T>::value && std::is_integral<U>::value) ||
426+
(std::is_floating_point<T>::value &&
427+
std::is_floating_point<U>::value)>;
431428

432429
FMT_NORETURN inline void throw_duration_error() {
433430
FMT_THROW(format_error("cannot format duration"));
@@ -486,9 +483,9 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
486483
#endif
487484
}
488485

489-
template <
490-
typename To, typename FromRep, typename FromPeriod,
491-
FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
486+
template <typename To, typename FromRep, typename FromPeriod,
487+
FMT_ENABLE_IF(
488+
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
492489
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
493490
// Mixed integer <-> float cast is not supported by safe_duration_cast.
494491
return std::chrono::duration_cast<To>(from);
@@ -520,6 +517,7 @@ template <typename... T> auto current_zone(T...) -> time_zone* {
520517
template <typename... T> void _tzset(T...) {}
521518
} // namespace tz
522519

520+
// DEPRECATED!
523521
inline void tzset_once() {
524522
static bool init = []() {
525523
using namespace tz;
@@ -915,11 +913,11 @@ template <typename Derived> struct null_chrono_spec_handler {
915913

916914
class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
917915
private:
918-
bool no_timezone_ = false;
916+
bool has_timezone_ = false;
919917

920918
public:
921-
constexpr explicit tm_format_checker(bool no_timezone = false)
922-
: no_timezone_(no_timezone) {}
919+
constexpr explicit tm_format_checker(bool has_timezone)
920+
: has_timezone_(has_timezone) {}
923921

924922
FMT_NORETURN inline void unsupported() {
925923
FMT_THROW(format_error("no format"));
@@ -959,10 +957,10 @@ class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
959957
FMT_CONSTEXPR void on_iso_time() {}
960958
FMT_CONSTEXPR void on_am_pm() {}
961959
FMT_CONSTEXPR void on_utc_offset(numeric_system) {
962-
if (no_timezone_) FMT_THROW(format_error("no timezone"));
960+
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
963961
}
964962
FMT_CONSTEXPR void on_tz_name() {
965-
if (no_timezone_) FMT_THROW(format_error("no timezone"));
963+
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
966964
}
967965
};
968966

@@ -1128,7 +1126,7 @@ class tm_writer {
11281126
static constexpr int days_per_week = 7;
11291127

11301128
const std::locale& loc_;
1131-
const bool is_classic_;
1129+
bool is_classic_;
11321130
OutputIt out_;
11331131
const Duration* subsecs_;
11341132
const std::tm& tm_;
@@ -1164,8 +1162,8 @@ class tm_writer {
11641162
}
11651163

11661164
auto tm_hour12() const noexcept -> int {
1167-
const auto h = tm_hour();
1168-
const auto z = h < 12 ? h : h - 12;
1165+
auto h = tm_hour();
1166+
auto z = h < 12 ? h : h - 12;
11691167
return z == 0 ? 12 : z;
11701168
}
11711169

@@ -1181,11 +1179,11 @@ class tm_writer {
11811179

11821180
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
11831181
auto iso_year_weeks(long long curr_year) const noexcept -> int {
1184-
const auto prev_year = curr_year - 1;
1185-
const auto curr_p =
1182+
auto prev_year = curr_year - 1;
1183+
auto curr_p =
11861184
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
11871185
days_per_week;
1188-
const auto prev_p =
1186+
auto prev_p =
11891187
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
11901188
days_per_week;
11911189
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
@@ -1195,15 +1193,15 @@ class tm_writer {
11951193
days_per_week;
11961194
}
11971195
auto tm_iso_week_year() const noexcept -> long long {
1198-
const auto year = tm_year();
1199-
const auto w = iso_week_num(tm_yday(), tm_wday());
1196+
auto year = tm_year();
1197+
auto w = iso_week_num(tm_yday(), tm_wday());
12001198
if (w < 1) return year - 1;
12011199
if (w > iso_year_weeks(year)) return year + 1;
12021200
return year;
12031201
}
12041202
auto tm_iso_week_of_year() const noexcept -> int {
1205-
const auto year = tm_year();
1206-
const auto w = iso_week_num(tm_yday(), tm_wday());
1203+
auto year = tm_year();
1204+
auto w = iso_week_num(tm_yday(), tm_wday());
12071205
if (w < 1) return iso_year_weeks(year - 1);
12081206
if (w > iso_year_weeks(year)) return 1;
12091207
return w;
@@ -1240,9 +1238,8 @@ class tm_writer {
12401238
uint32_or_64_or_128_t<long long> n = to_unsigned(year);
12411239
const int num_digits = count_digits(n);
12421240
if (negative && pad == pad_type::zero) *out_++ = '-';
1243-
if (width > num_digits) {
1241+
if (width > num_digits)
12441242
out_ = detail::write_padding(out_, pad, width - num_digits);
1245-
}
12461243
if (negative && pad != pad_type::zero) *out_++ = '-';
12471244
out_ = format_decimal<Char>(out_, n, num_digits);
12481245
}
@@ -1467,11 +1464,10 @@ class tm_writer {
14671464
void on_day_of_year(pad_type pad) {
14681465
auto yday = tm_yday() + 1;
14691466
auto digit1 = yday / 100;
1470-
if (digit1 != 0) {
1467+
if (digit1 != 0)
14711468
write1(digit1);
1472-
} else {
1469+
else
14731470
out_ = detail::write_padding(out_, pad);
1474-
}
14751471
write2(yday % 100, pad);
14761472
}
14771473

@@ -1608,18 +1604,16 @@ template <typename Rep, typename Period,
16081604
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
16091605
inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
16101606
-> std::chrono::duration<Rep, std::milli> {
1611-
// this may overflow and/or the result may not fit in the
1612-
// target type.
1607+
// This may overflow and/or the result may not fit in the target type.
16131608
#if FMT_SAFE_DURATION_CAST
1614-
using CommonSecondsType =
1609+
using common_seconds_type =
16151610
typename std::common_type<decltype(d), std::chrono::seconds>::type;
1616-
const auto d_as_common = detail::duration_cast<CommonSecondsType>(d);
1617-
const auto d_as_whole_seconds =
1611+
auto d_as_common = detail::duration_cast<common_seconds_type>(d);
1612+
auto d_as_whole_seconds =
16181613
detail::duration_cast<std::chrono::seconds>(d_as_common);
1619-
// this conversion should be nonproblematic
1620-
const auto diff = d_as_common - d_as_whole_seconds;
1621-
const auto ms =
1622-
detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
1614+
// This conversion should be nonproblematic.
1615+
auto diff = d_as_common - d_as_whole_seconds;
1616+
auto ms = detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
16231617
return ms;
16241618
#else
16251619
auto s = detail::duration_cast<std::chrono::seconds>(d);
@@ -1730,19 +1724,16 @@ struct chrono_formatter {
17301724

17311725
// returns true if nan or inf, writes to out.
17321726
auto handle_nan_inf() -> bool {
1733-
if (isfinite(val)) {
1734-
return false;
1735-
}
1727+
if (isfinite(val)) return false;
17361728
if (isnan(val)) {
17371729
write_nan();
17381730
return true;
17391731
}
17401732
// must be +-inf
1741-
if (val > 0) {
1742-
write_pinf();
1743-
} else {
1744-
write_ninf();
1745-
}
1733+
if (val > 0)
1734+
std::copy_n("inf", 3, out);
1735+
else
1736+
std::copy_n("-inf", 4, out);
17461737
return true;
17471738
}
17481739

@@ -1770,10 +1761,9 @@ struct chrono_formatter {
17701761
}
17711762

17721763
void write_sign() {
1773-
if (negative) {
1774-
*out++ = '-';
1775-
negative = false;
1776-
}
1764+
if (!negative) return;
1765+
*out++ = '-';
1766+
negative = false;
17771767
}
17781768

17791769
void write(Rep value, int width, pad_type pad = pad_type::zero) {
@@ -1789,8 +1779,6 @@ struct chrono_formatter {
17891779
}
17901780

17911781
void write_nan() { std::copy_n("nan", 3, out); }
1792-
void write_pinf() { std::copy_n("inf", 3, out); }
1793-
void write_ninf() { std::copy_n("-inf", 4, out); }
17941782

17951783
template <typename Callback, typename... Args>
17961784
void format_tm(const tm& time, Callback cb, Args... args) {
@@ -1872,9 +1860,8 @@ struct chrono_formatter {
18721860
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
18731861
precision);
18741862
if (negative) *out++ = '-';
1875-
if (buf.size() < 2 || buf[1] == '.') {
1863+
if (buf.size() < 2 || buf[1] == '.')
18761864
out = detail::write_padding(out, pad);
1877-
}
18781865
out = copy<char_type>(buf.begin(), buf.end(), out);
18791866
} else {
18801867
write(second(), 2, pad);
@@ -1995,7 +1982,7 @@ class year_month_day {
19951982
constexpr auto month() const noexcept -> fmt::month { return month_; }
19961983
constexpr auto day() const noexcept -> fmt::day { return day_; }
19971984
};
1998-
#endif
1985+
#endif // __cpp_lib_chrono >= 201907
19991986

20001987
template <typename Char>
20011988
struct formatter<weekday, Char> : private formatter<std::tm, Char> {
@@ -2204,31 +2191,12 @@ template <typename Char> struct formatter<std::tm, Char> {
22042191
private:
22052192
format_specs specs_;
22062193
detail::arg_ref<Char> width_ref_;
2207-
2208-
protected:
22092194
basic_string_view<Char> fmt_ =
22102195
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
22112196

2212-
template <typename Duration, typename FormatContext>
2213-
auto do_format(const std::tm& tm, FormatContext& ctx,
2214-
const Duration* subsecs) const -> decltype(ctx.out()) {
2215-
auto specs = specs_;
2216-
auto buf = basic_memory_buffer<Char>();
2217-
auto out = basic_appender<Char>(buf);
2218-
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
2219-
ctx);
2220-
2221-
auto loc_ref = ctx.locale();
2222-
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
2223-
auto w =
2224-
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
2225-
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
2226-
return detail::write(
2227-
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
2228-
}
2229-
2197+
protected:
22302198
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
2231-
bool no_timezone = false) -> const Char* {
2199+
bool has_timezone) -> const Char* {
22322200
auto it = ctx.begin(), end = ctx.end();
22332201
if (it == end || *it == '}') return it;
22342202

@@ -2242,15 +2210,33 @@ template <typename Char> struct formatter<std::tm, Char> {
22422210
}
22432211

22442212
end = detail::parse_chrono_format(it, end,
2245-
detail::tm_format_checker(no_timezone));
2213+
detail::tm_format_checker(has_timezone));
22462214
// Replace the default format string only if the new spec is not empty.
22472215
if (end != it) fmt_ = {it, detail::to_unsigned(end - it)};
22482216
return end;
22492217
}
22502218

2219+
template <typename Duration, typename FormatContext>
2220+
auto do_format(const std::tm& tm, FormatContext& ctx,
2221+
const Duration* subsecs) const -> decltype(ctx.out()) {
2222+
auto specs = specs_;
2223+
auto buf = basic_memory_buffer<Char>();
2224+
auto out = basic_appender<Char>(buf);
2225+
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
2226+
ctx);
2227+
2228+
auto loc_ref = ctx.locale();
2229+
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
2230+
auto w =
2231+
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
2232+
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
2233+
return detail::write(
2234+
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
2235+
}
2236+
22512237
public:
22522238
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
2253-
return do_parse(ctx);
2239+
return do_parse(ctx, detail::has_member_data_tm_gmtoff<std::tm>::value);
22542240
}
22552241

22562242
template <typename FormatContext>
@@ -2260,8 +2246,13 @@ template <typename Char> struct formatter<std::tm, Char> {
22602246
}
22612247
};
22622248

2249+
// DEPRECATED! The order of template parameters should be reversed.
22632250
template <typename Char, typename Duration>
2264-
struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
2251+
struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
2252+
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
2253+
return this->do_parse(ctx, true);
2254+
}
2255+
22652256
template <typename FormatContext>
22662257
auto format(sys_time<Duration> val, FormatContext& ctx) const
22672258
-> decltype(ctx.out()) {
@@ -2299,9 +2290,10 @@ struct formatter<utc_time<Duration>, Char>
22992290
};
23002291

23012292
template <typename Duration, typename Char>
2302-
struct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> {
2293+
struct formatter<local_time<Duration>, Char>
2294+
: private formatter<std::tm, Char> {
23032295
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
2304-
return this->do_parse(ctx, true);
2296+
return this->do_parse(ctx, false);
23052297
}
23062298

23072299
template <typename FormatContext>

0 commit comments

Comments
 (0)