From patchwork Sat Mar 9 00:29:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 1909886 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=hRQVC+1B; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; 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 [IPv6:2620:52:3:1:0:246e:9693:128c]) (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 4Ts3pl4Y3wz1yX6 for ; Sat, 9 Mar 2024 11:30:21 +1100 (AEDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id EA2E6385E009 for ; Sat, 9 Mar 2024 00:30:18 +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 E3129385E456 for ; Sat, 9 Mar 2024 00:29:45 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org E3129385E456 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 E3129385E456 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=1709944188; cv=none; b=xCeL8ZXS1Hbidnm4Y0/59FXFq09MALLbq7/ZOK9q5TNEEdvSNelcHwMucs3M0sNDgYeqjEmjeP5MyMSeFjNu6L2PMFmTqffP7hVnvbxQOH1yTUeUVoufxhyoDLASWR4nrZZWleuVTDIr0YK0DwqBxgXSgQBUjw8OZU2E72UhW54= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1709944188; c=relaxed/simple; bh=4hF5Sn9GCavVxQp35FQqkIB/4oYZ15w1Bq8z9NKXnIY=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=dbnE49IY8fbvh1ce/4I6EJ2W23LuYtCgG4PQ5PSOXJiSkVFHVlMPyl7yqT/sPFVqhNunD5CRj7F9XFpgWSd3c4bn12KOrp8bXAXCM9pwArahUvZslckBYgNUbuBlGCwBLaN24FhJ3NH0eaNqIj7GfztMx+ZZJB86fnmY5cwkWXE= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1709944185; 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=+iokrP4Ljb2bORLHFttp6HrtQygweeUtyJPXMwaRqCU=; b=hRQVC+1BXiBlozPrWFeSRLunXRqU3PF+KNwDtM6plff7Az9EKEkbQdG5RXm7Vqzq7wyf7W s/kv09l6r2CHSosYSzg3W6P1fKtaR3kMSYKuY4fFqhh9XNbSCtaWJtmCacaS0qR2xYG9IN bO2IB3HkQWnB9CXfPyJwBJst7Se/E/k= 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-356-pYew7WhIOiKIFAk7o9SdFA-1; Fri, 08 Mar 2024 19:29:43 -0500 X-MC-Unique: pYew7WhIOiKIFAk7o9SdFA-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) (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 8F0161C54548; Sat, 9 Mar 2024 00:29:43 +0000 (UTC) Received: from localhost (unknown [10.42.28.8]) by smtp.corp.redhat.com (Postfix) with ESMTP id 5CB9D2022EDB; Sat, 9 Mar 2024 00:29:43 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Fix parsing of leap seconds as chrono::utc_time [PR114279] Date: Sat, 9 Mar 2024 00:29:29 +0000 Message-ID: <20240309002942.905089-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.4 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-13.8 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_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE, URI_HEX autolearn=ham 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. -- >8 -- Implementing all chrono::from_stream overloads in terms of chrono::sys_time meant that a leap second time like 23:59:60.001 cannot be parsed, because that cannot be represented in a sys_time. The fix to support parsing leap seconds as utc_time is to convert the parsed date to utc_time and then add the parsed time to that, which allows the result to land in a leap second, rather than doing all the arithmetic with sys_time which doesn't have leap seconds. For local_time we also allow %S to parse a 60s value, because doing otherwise might disallow some valid uses. We can't know all use cases users have for treating times as local_time. For all other clocks, we can reject times that have 60 or 60.nnn as the seconds part, because that cannot occur in a valid UNIX, GPS, or TAI time. Since our chrono::file_clock uses sys_time, it can't occur for that clock either. In order to support this a new _M_is_leap_second member is needed in the _Parser type. This can be added at the end, where most targets currently have padding bytes. Similar to what I did recently for formatter _Spec structs, we can also reserve additional padding bits for future expansion. This also fixes bugs in the from_stream overloads for utc_time, tai_time, gps_time, and file_time, which were not using time_point_cast to explicitly convert to the result type. That's needed because the result type might have lower precision than the value returned from from_sys or from_utc, which has a precision no lower than seconds. libstdc++-v3/ChangeLog: PR libstdc++/114279 * include/bits/chrono_io.h (_Parser::_M_is_leap_second): New data member. (_Parser::_M_reserved): Reserve padding bits for future use. (_Parser::operator()): Set _M_is_leap_second if %S reads 60s. (from_stream): Only allow _M_is_leap_second for utc_time and local_time. Adjust arithmetic for utc_time so that leap seconds are preserved. Use time_point_cast to convert to a possibly lower-precision result type. * testsuite/std/time/parse.cc: Move to ... * testsuite/std/time/parse/parse.cc: ... here. * testsuite/std/time/parse/114279.cc: New test. --- libstdc++-v3/include/bits/chrono_io.h | 74 ++++++++++++++++--- .../testsuite/std/time/parse/114279.cc | 53 +++++++++++++ .../testsuite/std/time/{ => parse}/parse.cc | 0 3 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 libstdc++-v3/testsuite/std/time/parse/114279.cc rename libstdc++-v3/testsuite/std/time/{ => parse}/parse.cc (100%) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 412e8b83fb7..eaa36b8a074 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -2164,6 +2164,8 @@ namespace __detail year_month_day _M_ymd{}; weekday _M_wd{}; __format::_ChronoParts _M_need; + unsigned _M_is_leap_second : 1 {}; + unsigned _M_reserved : 15 {}; template basic_istream<_CharT, _Traits>& @@ -2742,8 +2744,13 @@ namespace __detail __detail::_Parser_t<_Duration> __p(__need); if (__p(__is, __fmt, __abbrev, __offset)) { - auto __st = __p._M_sys_days + __p._M_time - *__offset; - __tp = chrono::time_point_cast<_Duration>(__st); + if (__p._M_is_leap_second) + __is.setstate(ios_base::failbit); + else + { + auto __st = __p._M_sys_days + __p._M_time - *__offset; + __tp = chrono::time_point_cast<_Duration>(__st); + } } return __is; } @@ -2765,9 +2772,21 @@ namespace __detail basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, minutes* __offset = nullptr) { - sys_time<_Duration> __st; - if (chrono::from_stream(__is, __fmt, __st, __abbrev, __offset)) - __tp = utc_clock::from_sys(__st); + minutes __off{}; + if (!__offset) + __offset = &__off; + using __format::_ChronoParts; + auto __need = _ChronoParts::_Year | _ChronoParts::_Month + | _ChronoParts::_Day | _ChronoParts::_TimeOfDay; + __detail::_Parser_t<_Duration> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + { + // Converting to utc_time before adding _M_time is necessary for + // "23:59:60" to correctly produce a time within a leap second. + auto __ut = utc_clock::from_sys(__p._M_sys_days) + __p._M_time + - *__offset; + __tp = chrono::time_point_cast<_Duration>(__ut); + } return __is; } @@ -2788,9 +2807,24 @@ namespace __detail basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, minutes* __offset = nullptr) { - utc_time<_Duration> __ut; - if (chrono::from_stream(__is, __fmt, __ut, __abbrev, __offset)) - __tp = tai_clock::from_utc(__ut); + minutes __off{}; + if (!__offset) + __offset = &__off; + using __format::_ChronoParts; + auto __need = _ChronoParts::_Year | _ChronoParts::_Month + | _ChronoParts::_Day | _ChronoParts::_TimeOfDay; + __detail::_Parser_t<_Duration> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + { + if (__p._M_is_leap_second) + __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)); + __tp = chrono::time_point_cast<_Duration>(__tt); + } + } return __is; } @@ -2811,9 +2845,24 @@ namespace __detail basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, minutes* __offset = nullptr) { - utc_time<_Duration> __ut; - if (chrono::from_stream(__is, __fmt, __ut, __abbrev, __offset)) - __tp = gps_clock::from_utc(__ut); + minutes __off{}; + if (!__offset) + __offset = &__off; + using __format::_ChronoParts; + auto __need = _ChronoParts::_Year | _ChronoParts::_Month + | _ChronoParts::_Day | _ChronoParts::_TimeOfDay; + __detail::_Parser_t<_Duration> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + { + if (__p._M_is_leap_second) + __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); + } + } return __is; } @@ -2836,7 +2885,7 @@ namespace __detail { sys_time<_Duration> __st; if (chrono::from_stream(__is, __fmt, __st, __abbrev, __offset)) - __tp = file_clock::from_sys(__st); + __tp = chrono::time_point_cast<_Duration>(file_clock::from_sys(__st)); return __is; } @@ -4209,6 +4258,7 @@ namespace __detail { __ok = true; __t += __s; + _M_is_leap_second = __s >= seconds(60); } if (__ok) diff --git a/libstdc++-v3/testsuite/std/time/parse/114279.cc b/libstdc++-v3/testsuite/std/time/parse/114279.cc new file mode 100644 index 00000000000..67e5cba23bd --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/parse/114279.cc @@ -0,0 +1,53 @@ +// { dg-do run { target c++20 } } + +#include +#include +#include + +template +void +test_leap_second_parsing() +{ + std::chrono::time_point tp, tp2; + + std::istringstream ss("20161231-23:59:60.05"); + ss >> std::chrono::parse("%Y%m%d-%T", tp); + + if constexpr (std::is_same_v) + VERIFY( ss ); // We allow parsing "23:59:60" as local_time. + else + { + if constexpr (std::is_same_v) + { + // Entire input was consumed. + VERIFY( ss ); + VERIFY( ss.eof() ); + // The parsed value is the leap second inserted on Jan 1 2017. + VERIFY( std::chrono::get_leap_second_info(tp).is_leap_second ); + } + else + VERIFY( !ss ); // Other clocks do not allow "HH:MM:60" + + ss.clear(); + ss.str("20161231-22:59:60.05 -0100"); // Same time at -1h offset. + ss >> std::chrono::parse("%Y%m%d-%T %z", tp2); + + if constexpr (std::is_same_v) + { + VERIFY( ss ); + VERIFY( tp2 == tp ); + } + else + VERIFY( !ss ); + } +} + +int main() +{ + test_leap_second_parsing(); + test_leap_second_parsing(); + test_leap_second_parsing(); + test_leap_second_parsing(); + test_leap_second_parsing(); + test_leap_second_parsing(); +} diff --git a/libstdc++-v3/testsuite/std/time/parse.cc b/libstdc++-v3/testsuite/std/time/parse/parse.cc similarity index 100% rename from libstdc++-v3/testsuite/std/time/parse.cc rename to libstdc++-v3/testsuite/std/time/parse/parse.cc