From patchwork Fri Sep 6 20:47:12 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 1982078 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=FxsSamZ1; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4X0pKG0pmRz1y1H for ; Sat, 7 Sep 2024 06:50:39 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2CF77384A422 for ; Fri, 6 Sep 2024 20:50:38 +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 ESMTP id B4A65385841D for ; Fri, 6 Sep 2024 20:49:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B4A65385841D 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 B4A65385841D 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=1725655789; cv=none; b=W2x31w7mclMvp1o4rjj6lq0yQ+hw0Wq3C121dpAAjatp2B1r5QAxQNIsbyDR5sBvG6j0yFDno5o+ZNVZujQacAf4nqXUVv9X79qcKZpVJ9hHONHlLhp+S5tfc0hc3BDRL9dBo9zm+WcocQ7AwDeQo+yB3dJMs+f4qKTuPqW5rgU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1725655789; c=relaxed/simple; bh=RPMWiONGLcrSIi8o2rS8uxukWxtRc2NuNJmpVZ0pSKI=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=quEipI5y6754Jz4E2M4PcbxThVO7+KHMtlrQQ0Nq03Q1AopVTF55w9SX01rI7ZBzXpNZFqgKbh71SB2oNvKTYSizfzeYpYOlAt9xoZTlysrhmY7rEuheqtUxFobpY2dAg1pkTGSvXjP//41Mpmqr2m1igsB0Io5z0cHvRignasw= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1725655785; 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=wcm9sUYOaF9b1JLPlrolUy+n7ZAQ8wVucZ9jIJw4jCY=; b=FxsSamZ1zWEuI9bWzoKXm39unPbKpsyEBekl05YkrkelWEe0SKUJVTA3f1MRKOsflM0E2i 7Jyp7Diei55Wo2YznPYQn+Q+jsNEYy39mNdaONC3oUahbWz8VnQNmfE6IU1ppQlg+eY0ec 39AGyNbqrrpYBF//w9YVhbwSCbYzPHY= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-312-QZ9vigtqMmGQN1EGeWzmEg-1; Fri, 06 Sep 2024 16:49:41 -0400 X-MC-Unique: QZ9vigtqMmGQN1EGeWzmEg-1 Received: from mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.15]) (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 mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id D6FDD1956048; Fri, 6 Sep 2024 20:49:37 +0000 (UTC) Received: from localhost (unknown [10.42.28.101]) by mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id DC3BE1956086; Fri, 6 Sep 2024 20:49:34 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Fix std::chrono::parse for TAI and GPS clocks Date: Fri, 6 Sep 2024 21:47:12 +0100 Message-ID: <20240906204933.3288571-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.15 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-5.5 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FOREIGN_SUBJECT, GIT_PATCH_0, LIKELY_SPAM_BODY, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, RCVD_IN_SBL_CSS, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=no 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~incoming=patchwork.ozlabs.org@gcc.gnu.org Tested x86_64-linux. Pushed to trunk. This should be backported too. I noticed while testing this that all the from_stream overloads for time_point specializations use time_point_cast to convert to the correct result type. That's wrong, but I'll fix that separately, as it affects more than just GPS and TAI clocks. -- >8 -- Howard Hinnant brought to my attention that chrono::parse was giving incorrect values for chrono::gps_clock, because it was applying the offset between the GPS clock and UTC. That's incorrect, because when we parse HH::MM::SS as a GPS time, the result should be that time, not HH:MM:SS+offset. The problem was that I was using clock_cast to convert from sys_time to utc_time and then using clock_time again to convert to gps_time. The solution is to convert the parsed time into an duration representing the time since the GPS clock's epoch, then construct a gps_time directly from that duration. As well as adding tests for correct round tripping of times for all clocks, this also adds some more tests for correct results with std::format. libstdc++-v3/ChangeLog: * include/bits/chrono_io.h (from_stream): Fix conversions in overloads for gps_time and tai_time. * testsuite/std/time/clock/file/io.cc: Test round tripping using chrono::parse. Add additional std::format tests. * testsuite/std/time/clock/gps/io.cc: Likewise. * testsuite/std/time/clock/local/io.cc: Likewise. * testsuite/std/time/clock/tai/io.cc: Likewise. * testsuite/std/time/clock/utc/io.cc: Likewise. --- libstdc++-v3/include/bits/chrono_io.h | 12 +++--- .../testsuite/std/time/clock/file/io.cc | 23 +++++++++++ .../testsuite/std/time/clock/gps/io.cc | 22 ++++++++++- .../testsuite/std/time/clock/local/io.cc | 17 ++++++++ .../testsuite/std/time/clock/tai/io.cc | 39 ++++++++++++++++++- .../testsuite/std/time/clock/utc/io.cc | 22 +++++++++++ 6 files changed, 128 insertions(+), 7 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 38a0b002c81..0e4d23c9bb7 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -2939,8 +2939,9 @@ namespace __detail __is.setstate(ios_base::failbit); else { - auto __st = __p._M_sys_days + __p._M_time - *__offset; - auto __tt = tai_clock::from_utc(utc_clock::from_sys(__st)); + constexpr sys_days __epoch(-days(4383)); // 1958y/1/1 + auto __d = __p._M_sys_days - __epoch + __p._M_time - *__offset; + tai_time> __tt(__d); __tp = chrono::time_point_cast<_Duration>(__tt); } } @@ -2977,9 +2978,10 @@ namespace __detail __is.setstate(ios_base::failbit); else { - auto __st = __p._M_sys_days + __p._M_time - *__offset; - auto __tt = gps_clock::from_utc(utc_clock::from_sys(__st)); - __tp = chrono::time_point_cast<_Duration>(__tt); + constexpr sys_days __epoch(days(3657)); // 1980y/1/Sunday[1] + auto __d = __p._M_sys_days - __epoch + __p._M_time - *__offset; + gps_time> __gt(__d); + __tp = chrono::time_point_cast<_Duration>(__gt); } } return __is; diff --git a/libstdc++-v3/testsuite/std/time/clock/file/io.cc b/libstdc++-v3/testsuite/std/time/clock/file/io.cc index 9ab9f10ec77..9da5019ab78 100644 --- a/libstdc++-v3/testsuite/std/time/clock/file/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/file/io.cc @@ -32,6 +32,14 @@ test_format() auto ft = clock_cast(sys_days(2024y/January/21)) + 0ms + 2.5s; s = std::format("{}", ft); VERIFY( s == "2024-01-21 00:00:02.500"); + + const std::chrono::file_time t0{}; + s = std::format("{:%Z %z %Ez %Oz}", t0); + VERIFY( s == "UTC +0000 +00:00 +00:00" ); + + s = std::format("{}", t0); + // chrono::file_clock epoch is unspecified, so this is libstdc++-specific. + VERIFY( s == "2174-01-01 00:00:00" ); } void @@ -49,6 +57,21 @@ test_parse() VERIFY( tp == clock_cast(expected) ); VERIFY( abbrev == "BST" ); VERIFY( offset == 60min ); + + // Test round trip + std::stringstream ss; + ss << clock_cast(expected) << " 0123456"; + VERIFY( ss >> parse("%F %T %z%Z", tp, abbrev, offset) ); + VERIFY( ss.eof() ); + VERIFY( (tp + offset) == clock_cast(expected) ); + VERIFY( abbrev == "456" ); + VERIFY( offset == (1h + 23min) ); + + ss.str(""); + ss.clear(); + ss << file_time{}; + VERIFY( ss >> parse("%F %T", tp) ); + VERIFY( tp.time_since_epoch() == 0s ); } int main() diff --git a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc index e995d9f3d78..c012520080a 100644 --- a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc @@ -42,13 +42,19 @@ test_format() // PR libstdc++/113500 s = std::format("{}", gt + 150ms + 10.5s); VERIFY( s == "2000-01-01 00:00:23.650" ); + + s = std::format("{:%Z %z %Ez %Oz}", gt); + VERIFY( s == "GPS +0000 +00:00 +00:00" ); + + s = std::format("{}", gps_seconds{}); + VERIFY( s == "1980-01-06 00:00:00" ); } void test_parse() { using namespace std::chrono; - const sys_seconds expected = sys_days(2023y/August/9) + 20h + 44min + 3s; + const sys_seconds expected = sys_days(2023y/August/9) + 20h + 43min + 45s; gps_seconds tp; minutes offset; @@ -59,6 +65,20 @@ test_parse() VERIFY( tp == clock_cast(expected) ); VERIFY( abbrev == "BST" ); VERIFY( offset == 60min ); + + // Test round trip + std::stringstream ss; + ss << clock_cast(expected) << " GPS -1234"; + VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) ); + VERIFY( ! ss.eof() ); + VERIFY( (tp + offset) == clock_cast(expected) ); + VERIFY( abbrev == "GPS" ); + VERIFY( offset == -(12h + 34min) ); + + ss.str(""); + ss << gps_seconds{}; + VERIFY( ss >> parse("%F %T", tp) ); + VERIFY( tp.time_since_epoch() == 0s ); } int main() diff --git a/libstdc++-v3/testsuite/std/time/clock/local/io.cc b/libstdc++-v3/testsuite/std/time/clock/local/io.cc index b6e8b355bd0..b4d562f36d1 100644 --- a/libstdc++-v3/testsuite/std/time/clock/local/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/local/io.cc @@ -86,6 +86,9 @@ test_format() s = std::format("{:%Z %T %F %z %Ez}", ltf); __builtin_puts(s.c_str()); VERIFY( s == "FOO 20:22:02 2024-07-28 -0100 -01:00" ); + + s = std::format("{}", local_seconds{}); + VERIFY( s == "1970-01-01 00:00:00" ); } void @@ -103,6 +106,20 @@ test_parse() VERIFY( tp == local_seconds(expected.time_since_epoch()) ); VERIFY( abbrev == "BST" ); VERIFY( offset == 60min ); + + // Test round trip + std::stringstream ss; + ss << local_seconds{expected.time_since_epoch()} << " X 0123"; + VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) ); + VERIFY( ! ss.eof() ); + VERIFY( tp == local_seconds{expected.time_since_epoch()} ); + VERIFY( abbrev == "X" ); + VERIFY( offset == (1h + 23min) ); + + ss.str(""); + ss << local_seconds{}; + VERIFY( ss >> parse("%F %T", tp) ); + VERIFY( tp.time_since_epoch() == 0s ); } int main() diff --git a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc index dc0c3d26477..5634e41ff02 100644 --- a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc @@ -21,6 +21,28 @@ test_ostream() VERIFY( s == "==2000-01-01 00:00:32" ); } +void +test_format() +{ + using std::format; + using namespace std::chrono; + + auto st = sys_days{2000y/January/1}; + auto tt = clock_cast(st); + auto s = std::format("{}", tt); + VERIFY( s == "2000-01-01 00:00:32" ); + + // PR libstdc++/113500 + s = std::format("{}", tt + 150ms); + VERIFY( s == "2000-01-01 00:00:32.150" ); + + s = std::format("{:%Z %z %Ez %Oz}", tt); + VERIFY( s == "TAI +0000 +00:00 +00:00" ); + + s = std::format("{}", tai_seconds{}); + VERIFY( s == "1958-01-01 00:00:00" ); +} + void test_parse() { @@ -30,16 +52,31 @@ test_parse() minutes offset; std::string abbrev; - std::istringstream is("8/9/23 214403 +1 BST#"); + std::istringstream is("8/9/23 214440 +1 BST#"); VERIFY( is >> parse("%D %2H%2M%2S %Oz %Z", tp, abbrev, offset) ); VERIFY( ! is.eof() ); VERIFY( tp == clock_cast(expected) ); VERIFY( abbrev == "BST" ); VERIFY( offset == 60min ); + + // Test round trip + std::stringstream ss; + ss << clock_cast(expected) << " TAI 0123"; + VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) ); + VERIFY( ! ss.eof() ); + VERIFY( (tp + offset) == clock_cast(expected) ); + VERIFY( abbrev == "TAI" ); + VERIFY( offset == (1h + 23min) ); + + ss.str(""); + ss << tai_seconds{}; + VERIFY( ss >> parse("%F %T", tp) ); + VERIFY( tp.time_since_epoch() == 0s ); } int main() { test_ostream(); + test_format(); test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc index 55c53dc4057..ed1daa7a855 100644 --- a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc @@ -116,6 +116,13 @@ test_format() // PR libstdc++/113500 s = std::format("{}", leap + 100ms + 2.5s); VERIFY( s == "2017-01-01 00:00:01.600"); + + std::chrono::utc_seconds t0{}; + s = std::format("{:%Z %z %Ez %Oz}", t0); + VERIFY( s == "UTC +0000 +00:00 +00:00" ); + + s = std::format("{}", t0); + VERIFY( s == "1970-01-01 00:00:00" ); } void @@ -146,6 +153,21 @@ test_parse() VERIFY( is >> parse("%G-W%V-%u %T", tp) ); VERIFY( ! is.eof() ); VERIFY( tp == clock_cast(expected) ); + + // Test round trip + std::stringstream ss; + ss << clock_cast(expected) << " 0012 UTC"; + VERIFY( ss >> parse("%F %T %z %Z", tp, abbrev, offset) ); + VERIFY( ss.eof() ); + VERIFY( (tp + offset) == clock_cast(expected) ); + VERIFY( abbrev == "UTC" ); + VERIFY( offset == 12min ); + + ss.str(""); + ss.clear(); + ss << utc_seconds{}; + VERIFY( ss >> parse("%F %T", tp) ); + VERIFY( tp.time_since_epoch() == 0s ); } int main()