-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
[libc++][TZDB] Improves time zone format specifiers. #85797
Conversation
@llvm/pr-subscribers-libcxx Author: Mark de Wever (mordante) ChangesPer [tab:time.format.spec] Typically the modified versions Oz or Ez would have wording like The modified command %OS produces the locale's alternative 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 Full diff: https://github.com/llvm/llvm-project/pull/85797.diff 3 Files Affected:
diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h
index b64cae529a294d..8b8592041a1fb9 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 as short and fit in the string's internal
+ // buffer.
+ string __abbrev{"UTC"};
+ chrono::seconds __offset{0};
+};
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const _Tp& __value) {
+ __time_zone __result;
+ return __result;
+}
+
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>
|
1af98d7
to
cb28c85
Compare
6327aee
to
cb87ecc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with minor comments. Thanks!
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
cb87ecc
to
d39b5b5
Compare
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