From patchwork Wed Jul 19 11:37:34 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 122564 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:c923:0:b0:3e4:2afc:c1 with SMTP id j3csp2372941vqt; Wed, 19 Jul 2023 04:40:33 -0700 (PDT) X-Google-Smtp-Source: APBJJlGw0kN+lyCQHK5d6a4LH+E8AmxOt8j0OI8ehTL/sZCgfkWaP/gTiX1O0T8rpHtcW6MV1fdB X-Received: by 2002:a05:6512:2088:b0:4fb:90c6:c31a with SMTP id t8-20020a056512208800b004fb90c6c31amr11886704lfr.14.1689766833334; Wed, 19 Jul 2023 04:40:33 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1689766833; cv=none; d=google.com; s=arc-20160816; b=nZ7ZpYHmV4i4D7sHCLEPeXJN5PhmzoBXb7kHDqLB9tslxJ840NxxV7II8yiAt4Qp88 ISKVTeBuBviJcKi1b1wbtsujNTAKM4NGxDkTWqMsk2LjBgS2VVFO63D4VcyIg9Xnd7WB cXEnLarK2TEFJJod8DgTEoExadg0Uf+UiZSB9HQGqqo7acqqguhYJpgOFQDRgMzNJlQu SlQ5YytzHmDB+24Pcp/SXo1PZcyjTm7F6yFfGNDyUM4qcHRgf1w2PRJD3PyKqzCQEW4x xJyeYDs8KBWSqsbs6/AfjgShAQUEzICVhYKGkfl/F4r+jE5GXxjPkyP6FV8RNsrzg/++ Jngw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:reply-to:from:list-subscribe:list-help:list-post :list-archive:list-unsubscribe:list-id:precedence :content-transfer-encoding:mime-version:message-id:date:subject:to :dmarc-filter:delivered-to:dkim-signature:dkim-filter; bh=RZw5a0iAoNV4cVgQn624ijaEE9mTF08pqOpTpziiubE=; fh=sC8lnOfyH6gL5FYlMbZ1VDz6qVPMj/3ViLJWKNUCUgQ=; b=dv2q44K4nO0ZyIVsagmD7up9s5HoNIH6qr7qsycmpkXniDj9Cq9jQzkrm0qksg4rla GrOFeesKAu+0hPVjJUqqoTxtXCIcXWiw6gxFB1FeJ0ScT9w4GeElPCKSJua6vQzxBd1s YhTYdLuPC1GbicijydUKvooTgVTnGV5fKSKq+vwRZF+7MIu4UCPB06bVc5sRu1fl2Nsl EsAkW+tg/L4Nuq5SSba5A+jEBF26juwJErfuG+D3M8MpirlQHov1dXR2lW9cpYhAuuXA OxL5BahYdMvGYe+vKNykghkjO8zKn5yNCSng6EPik49EahByB4gVNtHpI5O5wNDjG8yK 57ZA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gcc.gnu.org header.s=default header.b=CpwK6kT6; spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gnu.org Received: from server2.sourceware.org (server2.sourceware.org. [2620:52:3:1:0:246e:9693:128c]) by mx.google.com with ESMTPS id j7-20020aa7c407000000b0051a5ea200c9si2900128edq.343.2023.07.19.04.40.32 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Jul 2023 04:40:33 -0700 (PDT) Received-SPF: pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) client-ip=2620:52:3:1:0:246e:9693:128c; Authentication-Results: mx.google.com; dkim=pass header.i=@gcc.gnu.org header.s=default header.b=CpwK6kT6; spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gnu.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id B0432386F821 for ; Wed, 19 Jul 2023 11:39:11 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B0432386F821 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1689766751; bh=RZw5a0iAoNV4cVgQn624ijaEE9mTF08pqOpTpziiubE=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=CpwK6kT6XrkrYNhTiNF3NRoQD32/ddJIcjZfAreuSVLo1G0Z0Qm9EV49ZAb7itFnJ RZQe605iAtGrXVGsCVJiYGcqkiTPY0c+JFIVHAYnv1n+bNNfW1ipCtu4omqsNhae0G tozQfu0Ucwo8sdVN1n6jy9J+Wqh3+oZbbGd4gOUI= X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id D73793856961 for ; Wed, 19 Jul 2023 11:38:20 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org D73793856961 Received: from mimecast-mx02.redhat.com (66.187.233.73 [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-613-MzlFRn6kN-SlF1dd8uNdVA-1; Wed, 19 Jul 2023 07:38:17 -0400 X-MC-Unique: MzlFRn6kN-SlF1dd8uNdVA-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id EC75D3C1350D; Wed, 19 Jul 2023 11:38:16 +0000 (UTC) Received: from localhost (unknown [10.42.28.140]) by smtp.corp.redhat.com (Postfix) with ESMTP id 93793200AD6E; Wed, 19 Jul 2023 11:38:16 +0000 (UTC) To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Implement correct locale-specific chrono formatting [PR110719] Date: Wed, 19 Jul 2023 12:37:34 +0100 Message-ID: <20230719113815.2510151-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.4 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.1 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, LIKELY_SPAM_BODY, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Jonathan Wakely via Gcc-patches From: Jonathan Wakely Reply-To: Jonathan Wakely Errors-To: gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org Sender: "Gcc-patches" X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1771848947125193756 X-GMAIL-MSGID: 1771848947125193756 Tested x86_64-linux. Pushed to trunk. I'll backport it to gcc-13 some time after the 13.2 release. -- >8 -- This fixes some TODOs in the C++20 format support, where the locale-specific output was incorrect or unimplemented. The approach taken here is to either use the formatting locale's std::time_put facet to do the formatting, or to remove subsecond precision from time points so that locale-specific formats don't print fractional seconds. This ensures that we are consistent with what the std::time_put facet would print (which never includes fractional seconds) even if we actually reimplement the formatting by hand instead of using the facet. This also fixes a misplaced statement that allowed modifiers for %Z which should have been on %z instead. There was also some ill-formed code in an untested branch for formatting time zone names to wide characters. A new test for zoned_time I/O has been added to exercise that code properly. libstdc++-v3/ChangeLog: PR libstdc++/110719 * include/bits/chrono_io.h (__formatter_chrono::_M_parse): Fix allowed modifiers for %z and %Z. Fix -Wparentheses and -Wnarrowing warnings. (__formatter_chrono::_M_format): Call new functions for %d, %e, %H, %I, %m and %M. (__formatter_chrono::_M_c): Use _S_floor_seconds to remove subsecond precision. (__formatter_chrono::_M_C_y_Y): Use _M_locale_fmt to handle modifiers. (__formatter_chrono::_M_e): Replace with _M_d_e and use _M_locale_fmt. (__formatter_chrono::_M_I): Replace with _M_H_I and use _M_locale_fmt. (__formatter_chrono::_M_m): New function. (__formatter_chrono::_M_M): New function. (__formatter_chrono::_M_r): Use _M_locale_fmt. (__formatter_chrono::_M_S): Likewise. (__formatter_chrono::_M_u_w): Likewise. (__formatter_chrono::_M_U_V_W): Likewise. (__formatter_chrono::_M_X): Use _S_floor_seconds. (__formatter_chrono::_M_Z): Fix untested branch for wchar_t. (__formatter_chrono::_S_altnum): Remove function. (__formatter_chrono::_S_dd_zero_fill): Remove function. (__formatter_chrono::_S_floor_seconds): New function. (__formatter_chrono::_M_locale_fmt): New function. * testsuite/std/time/clock/system/io.cc: Adjust expected output for locale-specific formats and check modified formats. * testsuite/std/time/clock/utc/io.cc: Likewise. * testsuite/std/time/zoned_time/io.cc: New test. --- libstdc++-v3/include/bits/chrono_io.h | 295 +++++++++++------- .../testsuite/std/time/clock/system/io.cc | 20 +- .../testsuite/std/time/clock/utc/io.cc | 12 +- .../testsuite/std/time/zoned_time/io.cc | 64 ++++ 4 files changed, 272 insertions(+), 119 deletions(-) create mode 100644 libstdc++-v3/testsuite/std/time/zoned_time/io.cc diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 5f06a6d76b4..43eeab42869 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -414,11 +414,10 @@ namespace __format break; case 'z': __needed = _TimeZone; - __allowed_mods = _Mod_E; + __allowed_mods = _Mod_E_O; break; case 'Z': __needed = _TimeZone; - __allowed_mods = _Mod_E_O; break; case 'n': case 't': @@ -439,7 +438,7 @@ namespace __format } if ((__mod == 'E' && !(__allowed_mods & _Mod_E)) - || __mod == 'O' && !(__allowed_mods & _Mod_O)) + || (__mod == 'O' && !(__allowed_mods & _Mod_O))) __throw_format_error("chrono format error: invalid " " modifier in chrono-specs"); __mod = _CharT(); @@ -471,7 +470,8 @@ namespace __format "chrono-specs"); _M_spec = __spec; - _M_spec._M_chrono_specs = {__chrono_specs, __first - __chrono_specs}; + _M_spec._M_chrono_specs + = __string_view(__chrono_specs, __first - __chrono_specs); return __first; } @@ -551,18 +551,12 @@ namespace __format __out = _M_C_y_Y(__t, std::move(__out), __fc, __c, __mod); break; case 'd': - // %d The day of month as a decimal number. - // %Od Locale's alternative representation. - __out = _S_dd_zero_fill((unsigned)_S_day(__t), - std::move(__out), - __fc, __mod == 'O'); + case 'e': + __out = _M_d_e(__t, std::move(__out), __fc, __c, __mod == 'O'); break; case 'D': __out = _M_D(__t, std::move(__out), __fc); break; - case 'e': - __out = _M_e(__t, std::move(__out), __fc, __mod == 'O'); - break; case 'F': __out = _M_F(__t, std::move(__out), __fc); break; @@ -571,29 +565,17 @@ namespace __format __out = _M_g_G(__t, std::move(__out), __fc, __c == 'G'); break; case 'H': - // %H The hour (24-hour clock) as a decimal number. - // %OH Locale's alternative representation. - __out = _S_dd_zero_fill(_S_hms(__t).hours().count(), - __print_sign(), __fc, __mod == 'O'); - break; case 'I': - __out = _M_I(__t, __print_sign(), __fc, __mod == 'O'); + __out = _M_H_I(__t, __print_sign(), __fc, __c, __mod == 'O'); break; case 'j': __out = _M_j(__t, __print_sign(), __fc); break; case 'm': - // %m month as a decimal number. - // %Om Locale's alternative representation. - __out = _S_dd_zero_fill((unsigned)_S_month(__t), - std::move(__out), __fc, - __mod == 'O'); + __out = _M_m(__t, std::move(__out), __fc, __mod == 'O'); break; case 'M': - // %M The minute as a decimal number. - // %OM Locale's alternative representation. - __out = _S_dd_zero_fill(_S_hms(__t).minutes().count(), - __print_sign(), __fc, __mod == 'O'); + __out = _M_M(__t, __print_sign(), __fc, __mod == 'O'); break; case 'p': __out = _M_p(__t, std::move(__out), __fc); @@ -790,12 +772,13 @@ namespace __format template typename _FormatContext::iterator - _M_c(const _Tp& __t, typename _FormatContext::iterator __out, + _M_c(const _Tp& __tt, typename _FormatContext::iterator __out, _FormatContext& __ctx, bool __mod = false) const { // %c Locale's date and time representation. // %Ec Locale's alternate date and time representation. + auto __t = _S_floor_seconds(__tt); locale __loc = _M_locale(__ctx); const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); const _CharT* __formats[2]; @@ -813,22 +796,24 @@ namespace __format template typename _FormatContext::iterator _M_C_y_Y(const _Tp& __t, typename _FormatContext::iterator __out, - _FormatContext& __ctx, char __conv, char __mod = 0) const + _FormatContext& __ctx, _CharT __conv, _CharT __mod = 0) const { // %C Year divided by 100 using floored division. // %EC Locale's alternative preresentation of the century (era name). // %y Last two decimal digits of the year. - // %OY Locale's alternative represenation. + // %Oy Locale's alternative representation. // %Ey Locale's alternative representation of offset from %EC. // %Y Year as a decimal number. - // %EY Locale's alternative full year represenation. + // %EY Locale's alternative full year representation. chrono::year __y = _S_year(__t); - if (__mod == 'E') + if (__mod) [[unlikely]] { - // TODO: %EC, %Ey or %EY - // return __out; + struct tm __tm{}; + __tm.tm_year = (int)__y - 1900; + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + __conv, __mod); } basic_string<_CharT> __s; @@ -852,9 +837,6 @@ namespace __format if (__conv == 'Y' || __conv == 'y') __s += _S_two_digits(__yi % 100); - if (__mod == 'O') // %OY - _S_altnum(_M_locale(__ctx), __s, __is_neg); - return __format::__write(std::move(__out), __string_view(__s)); } @@ -878,19 +860,33 @@ namespace __format template typename _FormatContext::iterator - _M_e(const _Tp& __t, typename _FormatContext::iterator __out, - _FormatContext& __ctx, bool __mod = false) const + _M_d_e(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, _CharT __conv, bool __mod = false) const { + // %d The day of month as a decimal number. + // %Od Locale's alternative representation. // %e Day of month as decimal number, padded with space. // %Oe Locale's alternative digits. + chrono::day __d = _S_day(__t); unsigned __i = (unsigned)__d; + + if (__mod) [[unlikely]] + { + struct tm __tm{}; + __tm.tm_mday = __i; + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + (char)__conv, 'O'); + } + auto __sv = _S_two_digits(__i); - basic_string<_CharT> __s; - if (__mod) - __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); - if (__i < 10) - __sv = __s = {_S_space, __sv[1]}; + _CharT __buf[2]; + if (__conv == _CharT('e') && __i < 10) + { + __buf[0] = _S_space; + __buf[1] = __sv[1]; + __sv = {__buf, 2}; + } return __format::__write(std::move(__out), __sv); } @@ -933,26 +929,39 @@ namespace __format template typename _FormatContext::iterator - _M_I(const _Tp& __t, typename _FormatContext::iterator __out, - _FormatContext& __ctx, bool __mod = false) const + _M_H_I(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, _CharT __conv, bool __mod = false) const { - auto __hms = _S_hms(__t); + // %H The hour (24-hour clock) as a decimal number. + // %OH Locale's alternative representation. + // %I The hour (12-hour clock) as a decimal number. + // %OI Locale's alternative representation. + + const auto __hms = _S_hms(__t); int __i = __hms.hours().count(); - if (__i == 0) - __i = 12; - else if (__i > 12) - __i -= 12; - auto __sv = _S_two_digits(__i); - basic_string<_CharT> __s; - if (__mod) - __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); - return __format::__write(std::move(__out), __sv); + + if (__mod) [[unlikely]] + { + struct tm __tm{}; + __tm.tm_hour = __i; + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + (char)__conv, 'O'); + } + + if (__conv == _CharT('I')) + { + if (__i == 0) + __i = 12; + else if (__i > 12) + __i -= 12; + } + return __format::__write(std::move(__out), _S_two_digits(__i)); } template typename _FormatContext::iterator _M_j(const _Tp& __t, typename _FormatContext::iterator __out, - _FormatContext& __ctx) const + _FormatContext&) const { if constexpr (chrono::__is_duration_v<_Tp>) { @@ -978,6 +987,50 @@ namespace __format } } + template + typename _FormatContext::iterator + _M_m(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod) const + { + // %m month as a decimal number. + // %Om Locale's alternative representation. + + auto __m = _S_month(__t); + auto __i = (unsigned)__m; + + if (__mod) [[unlikely]] // %Om + { + struct tm __tm{}; + __tm.tm_mon = __i - 1; + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + 'm', 'O'); + } + + return __format::__write(std::move(__out), _S_two_digits(__i)); + } + + template + typename _FormatContext::iterator + _M_M(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod) const + { + // %M The minute as a decimal number. + // %OM Locale's alternative representation. + + auto __m = _S_hms(__t).minutes(); + auto __i = __m.count(); + + if (__mod) [[unlikely]] // %OM + { + struct tm __tm{}; + __tm.tm_min = __i; + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + 'M', 'O'); + } + + return __format::__write(std::move(__out), _S_two_digits(__i)); + } + template typename _FormatContext::iterator _M_p(const _Tp& __t, typename _FormatContext::iterator __out, @@ -995,7 +1048,7 @@ namespace __format template typename _FormatContext::iterator - _M_q(const _Tp& __t, typename _FormatContext::iterator __out, + _M_q(const _Tp&, typename _FormatContext::iterator __out, _FormatContext& __ctx) const { // %q The duration's unit suffix @@ -1025,12 +1078,15 @@ namespace __format } } + // %Q handled in _M_format + template typename _FormatContext::iterator - _M_r(const _Tp& __t, typename _FormatContext::iterator __out, + _M_r(const _Tp& __tt, typename _FormatContext::iterator __out, _FormatContext& __ctx) const { // %r locale's 12-hour clock time. + auto __t = _S_floor_seconds(__tt); locale __loc = _M_locale(__ctx); const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); const _CharT* __ampm_fmt; @@ -1075,10 +1131,19 @@ namespace __format _FormatContext& __ctx, bool __mod = false) const { // %S Seconds as a decimal number. - // %OS (TODO) The locale's alternative representation. + // %OS The locale's alternative representation. auto __hms = _S_hms(__t); - __out = _S_dd_zero_fill(__hms.seconds().count(), - std::move(__out), __ctx, __mod); + + if (__mod) [[unlikely]] // %OS + { + struct tm __tm{}; + __tm.tm_sec = (int)__hms.seconds().count(); + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + 'S', 'O'); + } + + __out = __format::__write(std::move(__out), + _S_two_digits(__hms.seconds().count())); using rep = typename decltype(__hms)::precision::rep; if constexpr (__hms.fractional_width != 0) { @@ -1126,14 +1191,21 @@ namespace __format // %Ou Locale's alternative numeric rep. // %w Weekday as a decimal number (0-6), where Sunday is 0. // %Ow Locale's alternative numeric rep. + chrono::weekday __wd = _S_weekday(__t); + + if (__mod) [[unlikely]] + { + struct tm __tm{}; + __tm.tm_wday = __wd.c_encoding(); + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + (char)__conv, 'O'); + } + unsigned __wdi = __conv == 'u' ? __wd.iso_encoding() : __wd.c_encoding(); - basic_string<_CharT> __s(1, _S_digit(__wdi)); - if (__mod) - _S_altnum(_M_locale(__ctx), __s); - return __format::__write(std::move(__out), __string_view(__s)); - return __out; + const _CharT __d = _S_digit(__wdi); + return __format::__write(std::move(__out), __string_view(&__d, 1)); } template @@ -1151,6 +1223,18 @@ namespace __format auto __d = _S_days(__t); using _TDays = decltype(__d); // Either sys_days or local_days. + if (__mod) [[unlikely]] + { + const year_month_day __ymd(__d); + const year __y = __ymd.year(); + struct tm __tm{}; + __tm.tm_year = (int)__y - 1900; + __tm.tm_yday = (__d - _TDays(__y/January/1)).count(); + __tm.tm_wday = weekday(__d).c_encoding(); + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + (char)__conv, 'O'); + } + _TDays __first; // First day of week 1. if (__conv == 'V') // W01 begins on Monday before first Thursday. { @@ -1172,9 +1256,6 @@ namespace __format } auto __weeks = chrono::floor(__d - __first); __string_view __sv = _S_two_digits(__weeks.count() + 1); - basic_string<_CharT> __s; - if (__mod) - __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); return __format::__write(std::move(__out), __sv); } @@ -1202,11 +1283,12 @@ namespace __format template typename _FormatContext::iterator - _M_X(const _Tp& __t, typename _FormatContext::iterator __out, + _M_X(const _Tp& __tt, typename _FormatContext::iterator __out, _FormatContext& __ctx, bool __mod = false) const { // %X Locale's time rep // %EX Locale's alternative time representation. + auto __t = _S_floor_seconds(__tt); locale __loc = _M_locale(__ctx); const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); const _CharT* __time_reps[2]; @@ -1225,7 +1307,7 @@ namespace __format template typename _FormatContext::iterator _M_z(const _Tp& __t, typename _FormatContext::iterator __out, - _FormatContext& __ctx, bool __mod = false) const + _FormatContext&, bool __mod = false) const { using ::std::chrono::__detail::__utc_leap_second; using ::std::chrono::__detail::__local_time_fmt; @@ -1289,9 +1371,9 @@ namespace __format else { string_view __sv = *__t._M_abbrev; - basic_string<_CharT> __ws(__sv.size()); + basic_string<_CharT> __ws(__sv.size(), _CharT()); auto& __ct = use_facet>(_M_locale(__ctx)); - __ct.widen(__sv.data(), __sv.size(), __ws.data()); + __ct.widen(__sv.begin(), __sv.end(), __ws.data()); __wsv = __ws; } return __format::__write(std::move(__out), __wsv); @@ -1329,39 +1411,6 @@ namespace __format }; } - // Convert a numeric string to the locale's alternative numeric symbols. - static basic_string_view<_CharT> - _S_altnum(const locale& __loc, basic_string<_CharT>& __s, - bool __is_neg = false) - { - if (__loc == locale::classic()) - return __s; - -#if 0 // TODO how can we access numpunct_cache?! Need to go via std::time_put? - auto& __np = use_facet<__numpunct_cache<_CharT>>(__loc); - auto __nums = __np._M_atoms_out; // alts for "-+xX01234..." - if (__is_neg) - __s[0] = __nums[0]; - __nums += 4; // now points to alternate digits - for (int __i = __is_neg; __i < __s.size(); ++__i) - __s[__i] = __nums[__s[__i] - '0']; -#endif - return __s; - } - - // Write two digits, zero-filled. - template - typename _FormatContext::iterator - _S_dd_zero_fill(int __val, typename _FormatContext::iterator __out, - _FormatContext& __ctx, bool __alt_num) const - { - auto __sv = _S_two_digits(__val); - basic_string<_CharT> __s; - if (__alt_num) - __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); - return __format::__write(std::move(__out), __sv); - } - // Accessors for the components of chrono types: // Returns a hh_mm_ss. @@ -1490,6 +1539,38 @@ namespace __format else return weekday(_S_days(__t)); } + + // Remove subsecond precision from a time_point. + template + static auto + _S_floor_seconds(const _Tp& __t) + { + using chrono::__detail::__local_time_fmt; + if constexpr (chrono::__is_time_point_v<_Tp>) + if constexpr (_Tp::period::den != 1) + return chrono::floor(__t); + else + return __t; + else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>) + return _S_floor_seconds(__t._M_time); + else + return __t; + } + + // Use the formatting locale's std::time_put facet to produce + // a locale-specific representation. + template + _Iter + _M_locale_fmt(_Iter __out, const locale& __loc, const struct tm& __tm, + char __fmt, char __mod) const + { + basic_ostringstream<_CharT> __os; + const auto& __tp = use_facet>(__loc); + __tp.put(__os, __os, _S_space, &__tm, __fmt, __mod); + if (__os) + __out = __format::__write(std::move(__out), __os.view()); + return __out; + } }; } // namespace __format diff --git a/libstdc++-v3/testsuite/std/time/clock/system/io.cc b/libstdc++-v3/testsuite/std/time/clock/system/io.cc index e7feebc9a3c..7bb6851c7de 100644 --- a/libstdc++-v3/testsuite/std/time/clock/system/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/system/io.cc @@ -47,22 +47,30 @@ test_format() " | %H | %I | %j | %m | %M | %p | %r | %R" " | %S | %T | %u | %U | %V | %w | %W | %x" " | %X | %y | %Y | %z | %Z}", t); - VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25 2022" " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" - " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25 PM | 17:26" " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" - " | 17:26:25.708 | 22 | 2022 | +0000 | UTC" ); + " | 17:26:25 | 22 | 2022 | +0000 | UTC" ); std::wstring ws = std::format(L"{:%a | %A | %b | %B | %c" " | %C | %d | %D | %e | %F | %g | %G | %h" " | %H | %I | %j | %m | %M | %p | %r | %R" " | %S | %T | %u | %U | %V | %w | %W | %x" " | %X | %y | %Y | %z | %Z}", t); - VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25 2022" " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" - " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25 PM | 17:26" " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" - " | 17:26:25.708 | 22 | 2022 | +0000 | UTC" ); + " | 17:26:25 | 22 | 2022 | +0000 | UTC" ); + + auto loc = std::locale::classic(); + auto smod = std::format(loc, "{:%Ec %EC %Od %Oe %OH %OI %Om %OM %OS %Ou %OU" + " %Ow %OW %Ex %EX %Oy %Ey %EY %Ez %Oz}", t); + s = std::format("{:%c %C %d %e %H %I %m %M %S %u %U" + " %w %W %x %X %y %y %Y +00:00 +00:00}", + std::chrono::time_point_cast(t)); + VERIFY( smod == s ); } int main() diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc index 933cba65f44..977643f1147 100644 --- a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc @@ -89,18 +89,18 @@ test_format() } std::string s = ss.str(); - VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25 2022" " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" - " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25 PM | 17:26" " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" - " | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " ); + " | 17:26:25 | 22 | 2022 | +0000 | UTC | " ); std::wstring ws = wss.str(); - VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25 2022" " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" - " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25 PM | 17:26" " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" - " | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " ); + " | 17:26:25 | 22 | 2022 | +0000 | UTC | " ); std::chrono::utc_seconds leap(1483228800s + 26s); // 1 Jan 2017 s = std::format("{:%T}", leap - 1s); diff --git a/libstdc++-v3/testsuite/std/time/zoned_time/io.cc b/libstdc++-v3/testsuite/std/time/zoned_time/io.cc new file mode 100644 index 00000000000..ad85122e85d --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/zoned_time/io.cc @@ -0,0 +1,64 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } +// { dg-require-effective-target cxx11_abi } + +#include +#include +#include + +void +test_ostream() +{ + using namespace std::chrono; + std::stringstream ss; + zoned_time zt("America/New_York", sys_seconds{946'706'523s}); + ss << zt; + VERIFY( ss.str() == "2000-01-01 01:02:03 EST" ); +} + +void +test_format() +{ + using namespace std::chrono; + sys_time t(1671470785708ms); + auto zone = "America/New_York"; + zoned_time zt(zone, t); + + // Every conversion specifier is valid for a sys_time except %q and %Q. + + std::string s = std::format("{:%a | %A | %b | %B | %c" + " | %C | %d | %D | %e | %F | %g | %G | %h" + " | %H | %I | %j | %m | %M | %p | %r | %R" + " | %S | %T | %u | %U | %V | %w | %W | %x" + " | %X | %y | %Y | %z | %Z}", zt); + VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 12:26:25 2022" + " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" + " | 12 | 12 | 353 | 12 | 26 | PM | 12:26:25 PM | 12:26" + " | 25.708 | 12:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" + " | 12:26:25 | 22 | 2022 | -0500 | EST" ); + + std::wstring ws = std::format(L"{:%a | %A | %b | %B | %c" + " | %C | %d | %D | %e | %F | %g | %G | %h" + " | %H | %I | %j | %m | %M | %p | %r | %R" + " | %S | %T | %u | %U | %V | %w | %W | %x" + " | %X | %y | %Y | %z | %Z}", zt); + VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 12:26:25 2022" + " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" + " | 12 | 12 | 353 | 12 | 26 | PM | 12:26:25 PM | 12:26" + " | 25.708 | 12:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" + " | 12:26:25 | 22 | 2022 | -0500 | EST" ); + + auto loc = std::locale::classic(); + auto smod = std::format(loc, "{:%Ec %EC %Od %Oe %OH %OI %Om %OM %OS %Ou %OU" + " %Ow %OW %Ex %EX %Oy %Ey %EY %Ez %Oz}", zt); + s = std::format("{:%c %C %d %e %H %I %m %M %S %u %U" + " %w %W %x %X %y %y %Y -05:00 -05:00}", + zoned_time(zone, time_point_cast(t))); + VERIFY( smod == s ); +} + +int main() +{ + test_ostream(); + test_format(); +}