[libcxx-commits] [libcxx] [libc++][TZDB] Improves time zone format specifiers. (PR #85797)
Mark de Wever via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Apr 16 11:16:24 PDT 2024
https://github.com/mordante updated https://github.com/llvm/llvm-project/pull/85797
>From d39b5b5ed669cb7e06677a86ca98d2fcf42a97ce Mon Sep 17 00:00:00 2001
From: Mark de Wever <koraq at xs4all.nl>
Date: Sun, 10 Mar 2024 17:49:39 +0100
Subject: [PATCH] [libc++][TZDB] Improves time zone format specifiers.
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 https://github.com/llvm/llvm-project/issues/78184
---
libcxx/include/__chrono/formatter.h | 50 ++++++++++++++++++-
.../time.syn/formatter.file_time.pass.cpp | 39 ++-------------
.../time/time.syn/formatter.sys_time.pass.cpp | 39 ++-------------
3 files changed, 56 insertions(+), 72 deletions(-)
diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h
index b64cae529a294d..d932a99f4b9983 100644
--- a/libcxx/include/__chrono/formatter.h
+++ b/libcxx/include/__chrono/formatter.h
@@ -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>
@@ -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('%')) {
@@ -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'):
@@ -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(
diff --git a/libcxx/test/std/time/time.syn/formatter.file_time.pass.cpp b/libcxx/test/std/time/time.syn/formatter.file_time.pass.cpp
index b07282593d759c..f57841cca86293 100644
--- a/libcxx/test/std/time/time.syn/formatter.file_time.pass.cpp
+++ b/libcxx/test/std/time/time.syn/formatter.file_time.pass.cpp
@@ -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}");
@@ -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>
diff --git a/libcxx/test/std/time/time.syn/formatter.sys_time.pass.cpp b/libcxx/test/std/time/time.syn/formatter.sys_time.pass.cpp
index 2fed270cbade72..3a7d6f9a6b01fc 100644
--- a/libcxx/test/std/time/time.syn/formatter.sys_time.pass.cpp
+++ b/libcxx/test/std/time/time.syn/formatter.sys_time.pass.cpp
@@ -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}");
@@ -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>
More information about the libcxx-commits
mailing list