Skip to content

Commit b0f5622

Browse files
committed
Fix localization and formatting of timezone names
1 parent bd9554a commit b0f5622

2 files changed

Lines changed: 54 additions & 49 deletions

File tree

include/fmt/chrono.h

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -991,16 +991,28 @@ inline auto tm_mon_short_name(int mon) -> const char* {
991991
}
992992

993993
template <typename T, typename = void>
994-
struct has_member_data_tm_gmtoff : std::false_type {};
994+
struct has_tm_gmtoff : std::false_type {};
995995
template <typename T>
996-
struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>
997-
: std::true_type {};
996+
struct has_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>> : std::true_type {};
998997

999-
template <typename T, typename = void>
1000-
struct has_member_data_tm_zone : std::false_type {};
998+
template <typename T, typename = void> struct has_tm_zone : std::false_type {};
1001999
template <typename T>
1002-
struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>
1003-
: std::true_type {};
1000+
struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {};
1001+
1002+
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
1003+
bool set_tm_zone(T& time, char* tz) {
1004+
time.tm_zone = tz;
1005+
return true;
1006+
}
1007+
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
1008+
bool set_tm_zone(T&, char*) {
1009+
return false;
1010+
}
1011+
1012+
inline char* utc() {
1013+
static char tz[] = "UTC";
1014+
return tz;
1015+
}
10041016

10051017
// Converts value to Int and checks that it's in the range [0, upper).
10061018
template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
@@ -1260,24 +1272,24 @@ class tm_writer {
12601272
write2(static_cast<int>(offset % 60));
12611273
}
12621274

1263-
template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)>
1264-
void format_utc_offset_impl(const T& tm, numeric_system ns) {
1275+
template <typename T, FMT_ENABLE_IF(has_tm_gmtoff<T>::value)>
1276+
void format_utc_offset(const T& tm, numeric_system ns) {
12651277
write_utc_offset(tm.tm_gmtoff, ns);
12661278
}
1267-
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)>
1268-
void format_utc_offset_impl(const T&, numeric_system ns) {
1279+
template <typename T, FMT_ENABLE_IF(!has_tm_gmtoff<T>::value)>
1280+
void format_utc_offset(const T&, numeric_system ns) {
12691281
write_utc_offset(0, ns);
12701282
}
12711283

1272-
template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)>
1273-
void format_tz_name_impl(const T& tm) {
1284+
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
1285+
void format_tz_name(const T& tm) {
12741286
if (is_classic_)
12751287
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
12761288
else
12771289
format_localized('Z');
12781290
}
1279-
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)>
1280-
void format_tz_name_impl(const T&) {
1291+
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
1292+
void format_tz_name(const T&) {
12811293
format_localized('Z');
12821294
}
12831295

@@ -1389,8 +1401,8 @@ class tm_writer {
13891401
out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_);
13901402
}
13911403

1392-
void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); }
1393-
void on_tz_name() { format_tz_name_impl(tm_); }
1404+
void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); }
1405+
void on_tz_name() { format_tz_name(tm_); }
13941406

13951407
void on_year(numeric_system ns, pad_type pad) {
13961408
if (is_classic_ || ns == numeric_system::standard)
@@ -1987,16 +1999,14 @@ class year_month_day {
19871999
template <typename Char>
19882000
struct formatter<weekday, Char> : private formatter<std::tm, Char> {
19892001
private:
1990-
bool localized_ = false;
19912002
bool use_tm_formatter_ = false;
19922003

19932004
public:
19942005
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
19952006
auto it = ctx.begin(), end = ctx.end();
19962007
if (it != end && *it == 'L') {
19972008
++it;
1998-
localized_ = true;
1999-
return it;
2009+
this->set_localized();
20002010
}
20012011
use_tm_formatter_ = it != end && *it != '}';
20022012
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@@ -2007,7 +2017,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
20072017
auto time = std::tm();
20082018
time.tm_wday = static_cast<int>(wd.c_encoding());
20092019
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
2010-
detail::get_locale loc(localized_, ctx.locale());
2020+
detail::get_locale loc(this->localized(), ctx.locale());
20112021
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
20122022
w.on_abbr_weekday();
20132023
return w.out();
@@ -2041,16 +2051,14 @@ struct formatter<day, Char> : private formatter<std::tm, Char> {
20412051
template <typename Char>
20422052
struct formatter<month, Char> : private formatter<std::tm, Char> {
20432053
private:
2044-
bool localized_ = false;
20452054
bool use_tm_formatter_ = false;
20462055

20472056
public:
20482057
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
20492058
auto it = ctx.begin(), end = ctx.end();
20502059
if (it != end && *it == 'L') {
20512060
++it;
2052-
localized_ = true;
2053-
return it;
2061+
this->set_localized();
20542062
}
20552063
use_tm_formatter_ = it != end && *it != '}';
20562064
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@@ -2061,7 +2069,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
20612069
auto time = std::tm();
20622070
time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1;
20632071
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
2064-
detail::get_locale loc(localized_, ctx.locale());
2072+
detail::get_locale loc(this->localized(), ctx.locale());
20652073
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
20662074
w.on_abbr_month();
20672075
return w.out();
@@ -2195,6 +2203,9 @@ template <typename Char> struct formatter<std::tm, Char> {
21952203
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
21962204

21972205
protected:
2206+
bool localized() const { return specs_.localized(); }
2207+
void set_localized() { specs_.set_localized(); }
2208+
21982209
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, bool has_timezone)
21992210
-> const Char* {
22002211
auto it = ctx.begin(), end = ctx.end();
@@ -2209,6 +2220,11 @@ template <typename Char> struct formatter<std::tm, Char> {
22092220
if (it == end) return it;
22102221
}
22112222

2223+
if (*it == 'L') {
2224+
specs_.set_localized();
2225+
++it;
2226+
}
2227+
22122228
end = detail::parse_chrono_format(it, end,
22132229
detail::tm_format_checker(has_timezone));
22142230
// Replace the default format string only if the new spec is not empty.
@@ -2225,7 +2241,7 @@ template <typename Char> struct formatter<std::tm, Char> {
22252241
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
22262242
ctx);
22272243

2228-
auto loc_ref = ctx.locale();
2244+
auto loc_ref = specs.localized() ? ctx.locale() : detail::locale_ref();
22292245
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
22302246
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
22312247
loc, out, tm, subsecs);
@@ -2236,7 +2252,7 @@ template <typename Char> struct formatter<std::tm, Char> {
22362252

22372253
public:
22382254
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
2239-
return do_parse(ctx, detail::has_member_data_tm_gmtoff<std::tm>::value);
2255+
return do_parse(ctx, detail::has_tm_gmtoff<std::tm>::value);
22402256
}
22412257

22422258
template <typename FormatContext>
@@ -2261,17 +2277,20 @@ struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
22612277
if (detail::const_check(
22622278
period::num == 1 && period::den == 1 &&
22632279
!std::is_floating_point<typename Duration::rep>::value)) {
2280+
detail::set_tm_zone(tm, detail::utc());
22642281
return formatter<std::tm, Char>::format(tm, ctx);
22652282
}
22662283
Duration epoch = val.time_since_epoch();
22672284
Duration subsecs = detail::duration_cast<Duration>(
22682285
epoch - detail::duration_cast<std::chrono::seconds>(epoch));
22692286
if (subsecs.count() < 0) {
22702287
auto second = detail::duration_cast<Duration>(std::chrono::seconds(1));
2271-
if (tm.tm_sec != 0)
2288+
if (tm.tm_sec != 0) {
22722289
--tm.tm_sec;
2273-
else
2290+
} else {
22742291
tm = gmtime(val - second);
2292+
detail::set_tm_zone(tm, detail::utc());
2293+
}
22752294
subsecs += second;
22762295
}
22772296
return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs);

test/chrono-test.cc

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -335,30 +335,16 @@ TEST(chrono_test, local_time) {
335335
fmt::format_error, "no timezone");
336336
}
337337

338-
template <typename T,
339-
FMT_ENABLE_IF(fmt::detail::has_member_data_tm_gmtoff<T>::value)>
338+
template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)>
340339
bool set_tm_gmtoff(T& time, long offset) {
341340
time.tm_gmtoff = offset;
342341
return true;
343342
}
344-
template <typename T,
345-
FMT_ENABLE_IF(!fmt::detail::has_member_data_tm_gmtoff<T>::value)>
343+
template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)>
346344
bool set_tm_gmtoff(T&, long) {
347345
return false;
348346
}
349347

350-
template <typename T,
351-
FMT_ENABLE_IF(fmt::detail::has_member_data_tm_zone<T>::value)>
352-
bool set_tm_zone(T& time, char* tz) {
353-
time.tm_zone = tz;
354-
return true;
355-
}
356-
template <typename T,
357-
FMT_ENABLE_IF(!fmt::detail::has_member_data_tm_zone<T>::value)>
358-
bool set_tm_zone(T&, char*) {
359-
return false;
360-
}
361-
362348
TEST(chrono_test, tm) {
363349
auto time = fmt::gmtime(290088000);
364350
test_time(time);
@@ -371,7 +357,7 @@ TEST(chrono_test, tm) {
371357
fmt::format_error, "no timezone");
372358
}
373359
char tz[] = "EET";
374-
if (set_tm_zone(time, tz)) {
360+
if (fmt::detail::set_tm_zone(time, tz)) {
375361
EXPECT_EQ(fmt::format(fmt::runtime("{:%Z}"), time), "EET");
376362
} else {
377363
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
@@ -727,8 +713,8 @@ TEST(chrono_test, weekday) {
727713
if (loc != std::locale::classic()) {
728714
auto saturdays = std::vector<std::string>{"sáb", "sá.", "sáb."};
729715
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat)));
730-
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat)));
731-
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm)));
716+
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", sat)));
717+
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", tm)));
732718
}
733719
}
734720

@@ -1032,6 +1018,6 @@ TEST(chrono_test, year_month_day) {
10321018
if (loc != std::locale::classic()) {
10331019
auto months = std::vector<std::string>{"ene.", "ene"};
10341020
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month)));
1035-
EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month)));
1021+
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L%b}", month)));
10361022
}
10371023
}

0 commit comments

Comments
 (0)