Skip to content

[libc++][TZDB] Improves time zone format specifiers. #85797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions libcxx/include/__chrono/formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#ifndef _LIBCPP___CHRONO_FORMATTER_H
#define _LIBCPP___CHRONO_FORMATTER_H

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

// Implements the %z format specifier according to [tab:time.format.spec], where
// '__modifier' signals %Oz or %Ez were used. (Both modifiers behave the same,
// so there is no need to distinguish between them.)
template <class _CharT>
_LIBCPP_HIDE_FROM_ABI void
__format_zone_offset(basic_stringstream<_CharT>& __sstr, chrono::seconds __offset, bool __modifier) {
if (__offset < 0s) {
__sstr << _CharT('-');
__offset = -__offset;
} else {
__sstr << _CharT('+');
}

chrono::hh_mm_ss __hms{__offset};
std::ostreambuf_iterator<_CharT> __out_it{__sstr};
if (__modifier)
std::format_to(__out_it, _LIBCPP_STATICALLY_WIDEN(_CharT, "{:%H:%M}"), __hms);
else
std::format_to(__out_it, _LIBCPP_STATICALLY_WIDEN(_CharT, "{:%H%M}"), __hms);
}

// Helper to store the time zone information needed for formatting.
struct _LIBCPP_HIDE_FROM_ABI __time_zone {
// Typically these abbreviations are short and fit in the string's internal
// buffer.
string __abbrev;
chrono::seconds __offset;
};

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const _Tp& __value) {
return {"UTC", chrono::seconds{0}};
}

template <class _CharT, class _Tp>
_LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
basic_stringstream<_CharT>& __sstr, const _Tp& __value, basic_string_view<_CharT> __chrono_specs) {
tm __t = std::__convert_to_tm<tm>(__value);
__time_zone __z = __formatter::__convert_to_time_zone(__value);
const auto& __facet = std::use_facet<time_put<_CharT>>(__sstr.getloc());
for (auto __it = __chrono_specs.begin(); __it != __chrono_specs.end(); ++__it) {
if (*__it == _CharT('%')) {
Expand Down Expand Up @@ -296,9 +332,13 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
{__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1));
} break;

case _CharT('z'):
__formatter::__format_zone_offset(__sstr, __z.__offset, false);
break;

case _CharT('Z'):
// TODO FMT Add proper timezone support.
__sstr << _LIBCPP_STATICALLY_WIDEN(_CharT, "UTC");
// __abbrev is always a char so the copy may convert.
ranges::copy(__z.__abbrev, std::ostreambuf_iterator<_CharT>{__sstr});
break;

case _CharT('O'):
Expand All @@ -314,9 +354,15 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
break;
}
}

// Oz produces the same output as Ez below.
[[fallthrough]];
case _CharT('E'):
++__it;
if (*__it == 'z') {
__formatter::__format_zone_offset(__sstr, __z.__offset, true);
break;
}
[[fallthrough]];
default:
__facet.put(
Expand Down
39 changes: 4 additions & 35 deletions libcxx/test/std/time/time.syn/formatter.file_time.pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -904,12 +904,6 @@ static void test_valid_values_date_time() {

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

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

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

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

// Use supplied locale (ja_JP). This locale has a different alternate.a
// Use supplied locale (ja_JP).
check(loc,
SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
lfmt,
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
# else // defined(_AIX)
// Non localized output using C-locale
check(SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
fmt,
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970

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

// Use supplied locale (ja_JP). This locale has a different alternate.a
# if defined(__FreeBSD__)
check(loc,
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
lfmt,
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
# else
check(loc,
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+〇'\t%Z='UTC'\n"),
lfmt,
file_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
# endif
# endif // defined(_AIX)
std::locale::global(std::locale::classic());
#endif // !defined(__APPLE__) && !defined(_WIN32)
}

template <class CharT>
Expand Down
39 changes: 4 additions & 35 deletions libcxx/test/std/time/time.syn/formatter.sys_time.pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -900,12 +900,6 @@ static void test_valid_values_date_time() {

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

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

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

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

// Use supplied locale (ja_JP). This locale has a different alternate.a
// Use supplied locale (ja_JP).
check(loc,
SV("%z='UTC'\t%Ez='UTC'\t%Oz='UTC'\t%Z='UTC'\n"),
lfmt,
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
# else // defined(_AIX)
// Non localized output using C-locale
check(SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
fmt,
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970

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

// Use supplied locale (ja_JP). This locale has a different alternate.a
# if defined(__FreeBSD__)
check(loc,
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+0000'\t%Z='UTC'\n"),
lfmt,
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
# else
check(loc,
SV("%z='+0000'\t%Ez='+0000'\t%Oz='+〇'\t%Z='UTC'\n"),
lfmt,
std::chrono::sys_seconds(0s)); // 00:00:00 UTC Thursday, 1 January 1970
# endif
# endif // defined(_AIX)
std::locale::global(std::locale::classic());
#endif // !defined(__APPLE__) && !defined(_WIN32)
}

template <class CharT>
Expand Down