Skip to content

Commit a6fcbcc

Browse files
authored
[libc++][TZDB] Improves time zone format specifiers. (#85797)
Per [tab:time.format.spec] %z The offset from UTC as specified in ISO 8601-1:2019, subclause 5.3.4.1. For example -0430 refers to 4 hours 30 minutes behind UTC. If the offset is zero, +0000 is used. The modified commands %Ez and %Oz insert a : between the hours and minutes: -04:30. If the offset information is not available, an exception of type format_error is thrown. Typically the modified versions Oz or Ez would have wording like The modified command %OS produces the locale's alternative representation. In this case the modified version does not depend on the locale. This change is a preparation for formatting sys_info which has time zone information. The function time_put<_CharT>::put() does not have proper time zone support, therefore it's a manual implementation. Fixes #78184
1 parent fca2a49 commit a6fcbcc

File tree

3 files changed

+56
-72
lines changed

3 files changed

+56
-72
lines changed

libcxx/include/__chrono/formatter.h

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#ifndef _LIBCPP___CHRONO_FORMATTER_H
1111
#define _LIBCPP___CHRONO_FORMATTER_H
1212

13+
#include <__algorithm/ranges_copy.h>
1314
#include <__chrono/calendar.h>
1415
#include <__chrono/concepts.h>
1516
#include <__chrono/convert_to_tm.h>
@@ -170,10 +171,45 @@ _LIBCPP_HIDE_FROM_ABI void __format_century(basic_stringstream<_CharT>& __sstr,
170171
__sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02}"), __century);
171172
}
172173

174+
// Implements the %z format specifier according to [tab:time.format.spec], where
175+
// '__modifier' signals %Oz or %Ez were used. (Both modifiers behave the same,
176+
// so there is no need to distinguish between them.)
177+
template <class _CharT>
178+
_LIBCPP_HIDE_FROM_ABI void
179+
__format_zone_offset(basic_stringstream<_CharT>& __sstr, chrono::seconds __offset, bool __modifier) {
180+
if (__offset < 0s) {
181+
__sstr << _CharT('-');
182+
__offset = -__offset;
183+
} else {
184+
__sstr << _CharT('+');
185+
}
186+
187+
chrono::hh_mm_ss __hms{__offset};
188+
std::ostreambuf_iterator<_CharT> __out_it{__sstr};
189+
if (__modifier)
190+
std::format_to(__out_it, _LIBCPP_STATICALLY_WIDEN(_CharT, "{:%H:%M}"), __hms);
191+
else
192+
std::format_to(__out_it, _LIBCPP_STATICALLY_WIDEN(_CharT, "{:%H%M}"), __hms);
193+
}
194+
195+
// Helper to store the time zone information needed for formatting.
196+
struct _LIBCPP_HIDE_FROM_ABI __time_zone {
197+
// Typically these abbreviations are short and fit in the string's internal
198+
// buffer.
199+
string __abbrev;
200+
chrono::seconds __offset;
201+
};
202+
203+
template <class _Tp>
204+
_LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const _Tp& __value) {
205+
return {"UTC", chrono::seconds{0}};
206+
}
207+
173208
template <class _CharT, class _Tp>
174209
_LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
175210
basic_stringstream<_CharT>& __sstr, const _Tp& __value, basic_string_view<_CharT> __chrono_specs) {
176211
tm __t = std::__convert_to_tm<tm>(__value);
212+
__time_zone __z = __formatter::__convert_to_time_zone(__value);
177213
const auto& __facet = std::use_facet<time_put<_CharT>>(__sstr.getloc());
178214
for (auto __it = __chrono_specs.begin(); __it != __chrono_specs.end(); ++__it) {
179215
if (*__it == _CharT('%')) {
@@ -296,9 +332,13 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
296332
{__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1));
297333
} break;
298334

335+
case _CharT('z'):
336+
__formatter::__format_zone_offset(__sstr, __z.__offset, false);
337+
break;
338+
299339
case _CharT('Z'):
300-
// TODO FMT Add proper timezone support.
301-
__sstr << _LIBCPP_STATICALLY_WIDEN(_CharT, "UTC");
340+
// __abbrev is always a char so the copy may convert.
341+
ranges::copy(__z.__abbrev, std::ostreambuf_iterator<_CharT>{__sstr});
302342
break;
303343

304344
case _CharT('O'):
@@ -314,9 +354,15 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
314354
break;
315355
}
316356
}
357+
358+
// Oz produces the same output as Ez below.
317359
[[fallthrough]];
318360
case _CharT('E'):
319361
++__it;
362+
if (*__it == 'z') {
363+
__formatter::__format_zone_offset(__sstr, __z.__offset, true);
364+
break;
365+
}
320366
[[fallthrough]];
321367
default:
322368
__facet.put(

libcxx/test/std/time/time.syn/formatter.file_time.pass.cpp

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -904,12 +904,6 @@ static void test_valid_values_date_time() {
904904

905905
template <class CharT>
906906
static void test_valid_values_time_zone() {
907-
// The Apple CI gives %z='-0700' %Ez='-0700' %Oz='-0700' %Z='UTC'
908-
// -0700 looks like the local time where the CI happens to reside, therefore
909-
// omit this test on Apple.
910-
// The Windows CI gives %z='-0000', but on local machines set to a different
911-
// timezone, it gives e.g. %z='+0200'.
912-
#if !defined(__APPLE__) && !defined(_WIN32)
913907
using namespace std::literals::chrono_literals;
914908

915909
constexpr std::basic_string_view<CharT> fmt = SV("{:%%z='%z'%t%%Ez='%Ez'%t%%Oz='%Oz'%t%%Z='%Z'%n}");
@@ -918,48 +912,23 @@ static void test_valid_values_time_zone() {
918912
const std::locale loc(LOCALE_ja_JP_UTF_8);
919913
std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
920914

921-
# if defined(_AIX)
922915
// Non localized output using C-locale
923-
check(SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
916+
check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='UTC'\n"),
924917
fmt,
925918
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
926919

927920
// Use the global locale (fr_FR)
928-
check(SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
921+
check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='UTC'\n"),
929922
lfmt,
930923
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
931924

932-
// Use supplied locale (ja_JP). This locale has a different alternate.a
925+
// Use supplied locale (ja_JP).
933926
check(loc,
934-
SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
935-
lfmt,
936-
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
937-
# else // defined(_AIX)
938-
// Non localized output using C-locale
939-
check(SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
940-
fmt,
941-
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
942-
943-
// Use the global locale (fr_FR)
944-
check(SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
927+
SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='UTC'\n"),
945928
lfmt,
946929
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
947930

948-
// Use supplied locale (ja_JP). This locale has a different alternate.a
949-
# if defined(__FreeBSD__)
950-
check(loc,
951-
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
952-
lfmt,
953-
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
954-
# else
955-
check(loc,
956-
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+〇'\t%Z='UTC'\n"),
957-
lfmt,
958-
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
959-
# endif
960-
# endif // defined(_AIX)
961931
std::locale::global(std::locale::classic());
962-
#endif // !defined(__APPLE__) && !defined(_WIN32)
963932
}
964933

965934
template <class CharT>

libcxx/test/std/time/time.syn/formatter.sys_time.pass.cpp

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -900,12 +900,6 @@ static void test_valid_values_date_time() {
900900

901901
template <class CharT>
902902
static void test_valid_values_time_zone() {
903-
// The Apple CI gives %z='-0700' %Ez='-0700' %Oz='-0700' %Z='UTC'
904-
// -0700 looks like the local time where the CI happens to reside, therefore
905-
// omit this test on Apple.
906-
// The Windows CI gives %z='-0000', but on local machines set to a different
907-
// timezone, it gives e.g. %z='+0200'.
908-
#if !defined(__APPLE__) && !defined(_WIN32)
909903
using namespace std::literals::chrono_literals;
910904

911905
constexpr std::basic_string_view<CharT> fmt = SV("{:%%z='%z'%t%%Ez='%Ez'%t%%Oz='%Oz'%t%%Z='%Z'%n}");
@@ -914,48 +908,23 @@ static void test_valid_values_time_zone() {
914908
const std::locale loc(LOCALE_ja_JP_UTF_8);
915909
std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
916910

917-
# if defined(_AIX)
918911
// Non localized output using C-locale
919-
check(SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
912+
check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='UTC'\n"),
920913
fmt,
921914
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
922915

923916
// Use the global locale (fr_FR)
924-
check(SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
917+
check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='UTC'\n"),
925918
lfmt,
926919
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
927920

928-
// Use supplied locale (ja_JP). This locale has a different alternate.a
921+
// Use supplied locale (ja_JP).
929922
check(loc,
930-
SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
931-
lfmt,
932-
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
933-
# else // defined(_AIX)
934-
// Non localized output using C-locale
935-
check(SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
936-
fmt,
937-
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
938-
939-
// Use the global locale (fr_FR)
940-
check(SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
923+
SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='UTC'\n"),
941924
lfmt,
942925
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
943926

944-
// Use supplied locale (ja_JP). This locale has a different alternate.a
945-
# if defined(__FreeBSD__)
946-
check(loc,
947-
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
948-
lfmt,
949-
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
950-
# else
951-
check(loc,
952-
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+〇'\t%Z='UTC'\n"),
953-
lfmt,
954-
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
955-
# endif
956-
# endif // defined(_AIX)
957927
std::locale::global(std::locale::classic());
958-
#endif // !defined(__APPLE__) && !defined(_WIN32)
959928
}
960929

961930
template <class CharT>

0 commit comments

Comments
 (0)