From patchwork Sun Jan 21 22:26:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 189858 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a05:7301:2bc4:b0:101:a8e8:374 with SMTP id hx4csp2279618dyb; Sun, 21 Jan 2024 15:31:34 -0800 (PST) X-Google-Smtp-Source: AGHT+IEq3Hg/rmVS5JCilTg/glxsRwxVQLbX56qpF6VdrNoYl6CXrSp3P5EMLCxRtvNPSyvkMvzV X-Received: by 2002:ac8:5984:0:b0:42a:3093:1791 with SMTP id e4-20020ac85984000000b0042a30931791mr5595453qte.97.1705879893898; Sun, 21 Jan 2024 15:31:33 -0800 (PST) ARC-Seal: i=2; a=rsa-sha256; t=1705879893; cv=pass; d=google.com; s=arc-20160816; b=AdQhyze6K7g4o7l1CIgAVgNvTsivHWmte1uZF/UIIBYIWZrqu0A0NqKY16msVu5/XO CrJOIYvoOG9xXytp0jayupkn3ecz08h0hWtPKlTU+8Wn/fZ+XL4NiHlneeWJPdG8l/Gq r7rsyKlQUAMviMByeDQwD2Sy8ogsk/5PTLjVCfhrXiWt07wzyZMcAGZQQqryis8OlG6/ uHsyAdOPTYT6IIdiRfyRfqm4OUnb/ju84jMcj8wiC5AtnLLtoPjJ03v540aZWyNWmwFg tiC+rasWCdptFKezI3TAWd6GckraITcym8JQ0MSKYVXhrh6ZJ0t6tSMVAs/uOfyINfuR bOgA== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:list-subscribe:list-help:list-post:list-archive :list-unsubscribe:list-id:precedence:content-transfer-encoding :mime-version:message-id:date:subject:to:from:dkim-signature :arc-filter:dmarc-filter:delivered-to; bh=YLnPsy8Y7kycA8QRSev2vAAsqOUtJmgkKHFiohungoc=; fh=sJ+2/4g29YdyXkoRrFZSpsL2zxijepB7X/1rB0LDDh8=; b=HtKRRwiKkAYBeoptHjAFHkxzDEkyPdowAaGktv9deg7xcCwBBu6mQT5S5L2jVfLVDI A9dqmy5uNIyb+bDw6Ox0axlCHQVqWZS4cUgy/Gv83ukNuAh3TmzbpB6g4AUhYCVghDFD HgkxQFG5ygWxAZ2H4PhVweR1FudQi52O++W0JCGlGMkoOlklIMC3Sj9AMCwZWP3NlYWC YY/OaoXkbi1JLLQKjyjTkvcwaFZlr4jej4whE+FDLICQk0SejiaeRHxm3gAHZ/xWWRpL jIjMwbEu/nJLC2RUtElft29Sh2PkPTBKba8rncZGYT8BZKG0pyBb3FxrKzAoy1wDlBBS hgCA== ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@redhat.com header.s=mimecast20190719 header.b=ZUFOj5PC; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=redhat.com Received: from server2.sourceware.org (server2.sourceware.org. [8.43.85.97]) by mx.google.com with ESMTPS id d4-20020ac85ac4000000b0042a2ee384e2si4268286qtd.657.2024.01.21.15.31.33 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 21 Jan 2024 15:31:33 -0800 (PST) Received-SPF: pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) client-ip=8.43.85.97; Authentication-Results: mx.google.com; dkim=pass header.i=@redhat.com header.s=mimecast20190719 header.b=ZUFOj5PC; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=redhat.com Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 7C4803857738 for ; Sun, 21 Jan 2024 22:28:21 +0000 (GMT) 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.133.124]) by sourceware.org (Postfix) with ESMTPS id A4E83385829B for ; Sun, 21 Jan 2024 22:27:06 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A4E83385829B Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org A4E83385829B Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1705876029; cv=none; b=CMFAYo7phdGpxsv3seFOQLmhrU1lbKhElEIlXofnLjIPEQWq5OSqEmqgw7XuJJ6y64yd51IheR5SF7FBZkExNwMs7GBwuBVB3KDXudhrtd//xryWx7EUy0TINeu3vN/L6BAnLCVAl05JVJu6/hMnHClHKWPHogASQwNM6IC7E0A= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1705876029; c=relaxed/simple; bh=W1ud8EL8x9rhw9jDrTh6fhnCpAaNBxau3cBaaYDvSNE=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=gdmq2LdYjvoyJrgjBhlXPPlQdeNwZWkPIG62seC4pu5tyIJybOykYkXpCzV+R0W66EE2zVKh9ptTMIPX94vRPQkq5hrBKpGxE0GfXYzbb27urIuL3vlDZe+4CHGim3OdpbSTpuKGKw+w8dHDNSAjbHGdU5m0D4F6DiYkqgn7bNo= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1705876026; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=YLnPsy8Y7kycA8QRSev2vAAsqOUtJmgkKHFiohungoc=; b=ZUFOj5PCey+TOAcObS0Q6emyaIRbTHC0zSi364meFuUNW6iRAEle6Gj3MpLgI8im+j6lOS f9Ulp7OhAQHYNEkaM8oLZFW+wuoopJ1Ig9S0nmt0NE857eaTjkNXid4vHdlZz/ide0388T YFtdhvLeGh1rxmsDNNLQqhvrJpqg8k8= Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-64-G1hdRFwEOiauIthtwFw9FA-1; Sun, 21 Jan 2024 17:27:02 -0500 X-MC-Unique: G1hdRFwEOiauIthtwFw9FA-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 89D563810797; Sun, 21 Jan 2024 22:27:02 +0000 (UTC) Received: from localhost (unknown [10.42.28.13]) by smtp.corp.redhat.com (Postfix) with ESMTP id 51F0C111E40C; Sun, 21 Jan 2024 22:27:02 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Fix std::format for floating-point chrono::time_point [PR113500] Date: Sun, 21 Jan 2024 22:26:15 +0000 Message-ID: <20240121222701.452264-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.3 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.7 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, 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.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1788744715532371314 X-GMAIL-MSGID: 1788744715532371314 Tested aarch64-linux. Pushed to trunk. Backport to gcc-13 would be good too, I think. -- >8 -- Currently trying to use std::format with certain specializations of std::chrono::time_point is ill-formed, due to one member function of the __formatter_chrono type which tries to write a time_point to an ostream. For sys_time or sys_time with a period greater than days there is no operator<< that can be used. That operator<< is only needed when using an empty chrono-specs in the format string, like "{}", but the ill-formed expression gives an error even if not actually used. This means it's not possible to format some other specializations of chrono::time_point, even when using a non-empty chrono-specs. This fixes it by avoiding using 'os << t' for all chrono::time_point specializations, and instead using std::format("{:L%F %T}", t). So that we continue to reject std::format("{}", sys_time{1.0s}) a check for empty chrono-specs is added to the formatter, C> specialization. While testing this I noticed that the output for %S with a floating-point duration was incorrect, as the subseconds part was being appended to the seconds without a decimal point, and without the correct number of leading zeros. libstdc++-v3/ChangeLog: PR libstdc++/113500 * include/bits/chrono_io.h (__formatter_chrono::_M_S): Fix printing of subseconds with floating-point rep. (__formatter_chrono::_M_format_to_ostream): Do not write time_point specializations directly to the ostream. (formatter, C>::parse): Do not allow an empty chrono-spec if the type fails to meet the constraints for writing to an ostream with operator<<. * testsuite/std/time/clock/file/io.cc: Check formatting non-integral times with empty chrono-specs. * testsuite/std/time/clock/gps/io.cc: Likewise. * testsuite/std/time/clock/utc/io.cc: Likewise. * testsuite/std/time/hh_mm_ss/io.cc: Likewise. --- libstdc++-v3/include/bits/chrono_io.h | 71 +++++++++++++------ .../testsuite/std/time/clock/file/io.cc | 17 +++++ .../testsuite/std/time/clock/gps/io.cc | 27 +++++++ .../testsuite/std/time/clock/utc/io.cc | 4 ++ .../testsuite/std/time/hh_mm_ss/io.cc | 28 +++++++- 5 files changed, 124 insertions(+), 23 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 7b5876b24e6..82f2d39ec44 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -681,6 +681,7 @@ namespace __format return __fc.locale(); } + // Format for empty chrono-specs, e.g. "{}" (C++20 [time.format] p6). // TODO: consider moving body of every operator<< into this function // and use std::format("{}", t) to implement those operators. That // would avoid std::format("{}", t) calling operator<< which calls @@ -702,6 +703,22 @@ namespace __format if constexpr (__is_specialization_of<_Tp, __utc_leap_second>) __os << __t._M_date << ' ' << __t._M_time; + else if constexpr (chrono::__is_time_point_v<_Tp>) + { + // Need to be careful here because not all specializations + // of chrono::sys_time can be written to an ostream. + // For the specializations of time_point that can be + // formatted with an empty chrono-specs, either it's a + // sys_time with period greater or equal to days: + if constexpr (is_convertible_v<_Tp, chrono::sys_days>) + __os << _S_date(__t); + else // Or it's formatted as "{:L%F %T}": + { + auto __days = chrono::floor(__t); + __os << chrono::year_month_day(__days) << ' ' + << chrono::hh_mm_ss(__t - __days); + } + } else { if constexpr (chrono::__is_duration_v<_Tp>) @@ -1122,39 +1139,43 @@ namespace __format 'S', 'O'); } - __out = __format::__write(std::move(__out), - _S_two_digits(__hms.seconds().count())); - if constexpr (__hms.fractional_width != 0) + if constexpr (__hms.fractional_width == 0) + __out = __format::__write(std::move(__out), + _S_two_digits(__hms.seconds().count())); + else { locale __loc = _M_locale(__ctx); + auto __s = __hms.seconds(); auto __ss = __hms.subseconds(); using rep = typename decltype(__ss)::rep; if constexpr (is_floating_point_v) { + chrono::duration __fs = __s + __ss; __out = std::format_to(std::move(__out), __loc, - _GLIBCXX_WIDEN("{:.{}Lg}"), - __ss.count(), - __hms.fractional_width); - } - else if constexpr (is_integral_v) - { - const auto& __np - = use_facet>(__loc); - __out = std::format_to(std::move(__out), - _GLIBCXX_WIDEN("{}{:0{}}"), - __np.decimal_point(), - __ss.count(), + _GLIBCXX_WIDEN("{:#0{}.{}Lf}"), + __fs.count(), + 3 + __hms.fractional_width, __hms.fractional_width); } else { const auto& __np = use_facet>(__loc); + __out = __format::__write(std::move(__out), + _S_two_digits(__s.count())); *__out++ = __np.decimal_point(); - auto __str = std::format(_S_empty_spec, __ss.count()); - __out = std::format_to(_GLIBCXX_WIDEN("{:0>{}s}"), - __str, - __hms.fractional_width); + if constexpr (is_integral_v) + __out = std::format_to(std::move(__out), + _GLIBCXX_WIDEN("{:0{}}"), + __ss.count(), + __hms.fractional_width); + else + { + auto __str = std::format(_S_empty_spec, __ss.count()); + __out = std::format_to(_GLIBCXX_WIDEN("{:0>{}s}"), + __str, + __hms.fractional_width); + } } } return __out; @@ -1911,7 +1932,13 @@ namespace __format template constexpr typename _ParseContext::iterator parse(_ParseContext& __pc) - { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + { + auto __next = _M_f._M_parse(__pc, __format::_ZonedDateTime); + if constexpr (!__stream_insertable) + if (_M_f._M_spec._M_chrono_specs.empty()) + __format::__invalid_chrono_spec(); // chrono-specs can't be empty + return __next; + } template typename _FormatContext::iterator @@ -1920,6 +1947,10 @@ namespace __format { return _M_f._M_format(__t, __fc); } private: + static constexpr bool __stream_insertable + = requires (basic_ostream<_CharT>& __os, + chrono::sys_time<_Duration> __t) { __os << __t; }; + __format::__formatter_chrono<_CharT> _M_f; }; diff --git a/libstdc++-v3/testsuite/std/time/clock/file/io.cc b/libstdc++-v3/testsuite/std/time/clock/file/io.cc index 39eb0dbcf51..3eee6bf5cc2 100644 --- a/libstdc++-v3/testsuite/std/time/clock/file/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/file/io.cc @@ -17,6 +17,23 @@ test_ostream() VERIFY( ss1.str() == ss2.str() ); } +void +test_format() +{ + using namespace std::chrono; + auto t = file_clock::now(); + + auto s = std::format("{}", t); + std::ostringstream ss; + ss << t; + VERIFY( s == ss.str() ); + + // PR libstdc++/113500 + auto ft = clock_cast(sys_days(2024y/January/21)) + 0ms + 2.5s; + s = std::format("{}", ft); + VERIFY( s == "2024-01-17 00:00:02.500"); +} + void test_parse() { diff --git a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc index 6f4544fcd97..5d15713c69d 100644 --- a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc @@ -3,6 +3,7 @@ #include #include +#include #include void @@ -16,6 +17,32 @@ test_ostream() auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, gt); VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:13 GPS" ); + + std::ostringstream ss; + ss << gt; + VERIFY( ss.str() == "2000-01-01 00:00:13" ); + + gps_time> gtf = gt; + ss.str(""); + ss.clear(); + ss << (gps_time>(gt) + 20ms); + VERIFY( ss.str() == "2000-01-01 00:00:13.020" ); +} + +void +test_format() +{ + using std::format; + using namespace std::chrono; + + auto st = sys_days{2000y/January/1}; + auto gt = clock_cast(st); + auto s = std::format("{}", gt); + VERIFY( s == "2000-01-01 00:00:13" ); + + // PR libstdc++/113500 + s = std::format("{}", gt + 150ms + 10.5s); + VERIFY( s == "2000-01-01 00:00:35.650" ); } void diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc index 58e358f3dbf..55c53dc4057 100644 --- a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc @@ -112,6 +112,10 @@ test_format() s = std::format("{:%T}", leap + 1s); VERIFY( s == "00:00:00" ); + + // PR libstdc++/113500 + s = std::format("{}", leap + 100ms + 2.5s); + VERIFY( s == "2017-01-01 00:00:01.600"); } void diff --git a/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc index 8eb80422e6e..d651dfbcdc8 100644 --- a/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc +++ b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc @@ -35,9 +35,31 @@ test_ostream() VERIFY( out.str() == "18:15:45.123" ); } - ostringstream out; - out << hh_mm_ss{65745s}; - VERIFY( out.str() == "18:15:45" ); + { + ostringstream out; + out << hh_mm_ss{65745s}; + VERIFY( out.str() == "18:15:45" ); + } + + { + ostringstream out; + out << hh_mm_ss{0.020s}; + // hh_mm_ss>::fractional_width == 0 so no subseconds: + VERIFY( out.str() == "00:00:00" ); + } + + { + ostringstream out; + out << hh_mm_ss>{0.020s}; + // hh_mm_ss>::fractional_width == 9: + VERIFY( out.str() == "00:00:00.020000000" ); + } + + { + ostringstream out; + out << hh_mm_ss{65745s + 20ms}; + VERIFY( out.str() == "18:15:45.020" ); + } } void