Skip to content

Commit

Permalink
[libc++][TZDB] Improves time zone format specifiers. (llvm#85797)
Browse files Browse the repository at this point in the history
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 llvm#78184
  • Loading branch information
mordante authored Apr 17, 2024
1 parent fca2a49 commit a6fcbcc
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 72 deletions.
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

0 comments on commit a6fcbcc

Please sign in to comment.