Formatting utc_time can produce a time ending with ":60" for a leap
second, so we should also be able to read that back using chrono::parse.
We should reject ":60" unless the time really is a leap second, so
compare to the list of known leap seconds. Continue to reject ":60" for
any time point except utc_time.
We need to delay writing to the *abbrev and *offset variables until
after we know if the parse is successful, so after handling the
_M_is_leap_second flag.
libstdc++-v3/ChangeLog:
PR libstdc++/126543
* include/bits/chrono_io.h (from_stream): Delay writing abbrev
and offset until successful or failed parse has been decided.
For utc_time use chrono::get_leap_second_info to check for valid
leap seconds.
(_Parser::operator()) <R>: Do not accept "60" for minutes.
<S>: Accept "60" for seconds.
* testsuite/std/time/parse/114279.cc: Use a real leap second as
the test input. Check parsing whole seconds as well as
fractional seconds. Check that offset isn't written to for
failed parse.
* testsuite/std/time/parse/126543.cc: New test.
---
Tested x86_64-linux.
libstdc++-v3/include/bits/chrono_io.h | 74 ++++++++++++-------
.../testsuite/std/time/parse/114279.cc | 33 +++++++--
.../testsuite/std/time/parse/126543.cc | 40 ++++++++++
3 files changed, 112 insertions(+), 35 deletions(-)
create mode 100644 libstdc++-v3/testsuite/std/time/parse/126543.cc
diff --git a/libstdc++-v3/include/bits/chrono_io.h
b/libstdc++-v3/include/bits/chrono_io.h
index eec12357fdf7..0d6da0eb1bd7 100644
--- a/libstdc++-v3/include/bits/chrono_io.h
+++ b/libstdc++-v3/include/bits/chrono_io.h
@@ -3911,24 +3911,27 @@ namespace __detail
basic_istream<_CharT, _Traits>&
from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt,
sys_time<_Duration>& __tp,
- basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr,
- minutes* __offset = nullptr)
+ basic_string<_CharT, _Traits, _Alloc>* __abbrev_p = nullptr,
+ minutes* __offset_p = nullptr)
{
+ basic_string<_CharT, _Traits, _Alloc> __abbr;
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(__is, __fmt, __abbrev_p ? &__abbr : nullptr, &__off))
{
if (__p._M_is_leap_second)
__is.setstate(ios_base::failbit);
else
{
- auto __st = __p._M_sys_days + __p._M_time - *__offset;
+ auto __st = __p._M_sys_days + __p._M_time - __off;
__tp = __detail::__round<_Duration>(__st);
+ if (__offset_p)
+ *__offset_p = __off;
+ if (__abbrev_p)
+ *__abbrev_p = std::move(__abbr);
}
}
return __is;
@@ -3945,23 +3948,32 @@ namespace __detail
inline basic_istream<_CharT, _Traits>&
from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt,
utc_time<_Duration>& __tp,
- basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr,
- minutes* __offset = nullptr)
+ basic_string<_CharT, _Traits, _Alloc>* __abbrev_p = nullptr,
+ minutes* __offset_p = nullptr)
{
+ basic_string<_CharT, _Traits, _Alloc> __abbr;
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(__is, __fmt, __abbrev_p ? &__abbr : nullptr, &__off))
{
// 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 = __detail::__round<_Duration>(__ut);
+ - __off;
+ if (__p._M_is_leap_second
+ && !chrono::get_leap_second_info(__ut).is_leap_second)
+ __is.setstate(ios_base::failbit);
+ else
+ {
+ __tp = __detail::__round<_Duration>(__ut);
+ if (__offset_p)
+ *__offset_p = __off;
+ if (__abbrev_p)
+ *__abbrev_p = std::move(__abbr);
+ }
}
return __is;
}
@@ -3977,26 +3989,29 @@ namespace __detail
inline basic_istream<_CharT, _Traits>&
from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt,
tai_time<_Duration>& __tp,
- basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr,
- minutes* __offset = nullptr)
+ basic_string<_CharT, _Traits, _Alloc>* __abbrev_p = nullptr,
+ minutes* __offset_p = nullptr)
{
+ basic_string<_CharT, _Traits, _Alloc> __abbr;
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(__is, __fmt, __abbrev_p ? &__abbr : nullptr, &__off))
{
if (__p._M_is_leap_second)
__is.setstate(ios_base::failbit);
else
{
constexpr sys_days __epoch(-days(4383)); // 1958y/1/1
- auto __d = __p._M_sys_days - __epoch + __p._M_time - *__offset;
+ auto __d = __p._M_sys_days - __epoch + __p._M_time - __off;
tai_time<common_type_t<_Duration, seconds>> __tt(__d);
__tp = __detail::__round<_Duration>(__tt);
+ if (__offset_p)
+ *__offset_p = __off;
+ if (__abbrev_p)
+ *__abbrev_p = std::move(__abbr);
}
}
return __is;
@@ -4013,25 +4028,28 @@ namespace __detail
inline basic_istream<_CharT, _Traits>&
from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt,
gps_time<_Duration>& __tp,
- basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr,
- minutes* __offset = nullptr)
+ basic_string<_CharT, _Traits, _Alloc>* __abbrev_p = nullptr,
+ minutes* __offset_p = nullptr)
{
+ basic_string<_CharT, _Traits, _Alloc> __abbr;
minutes __off{};
- if (!__offset)
- __offset = &__off;
using __format::_ChronoParts;
auto __need = _ChronoParts::_YearMonthDay | _ChronoParts::_TimeOfDay;
__detail::_Parser_t<_Duration> __p(__need);
- if (__p(__is, __fmt, __abbrev, __offset))
+ if (__p(__is, __fmt, __abbrev_p ? &__abbr : nullptr, &__off))
{
if (__p._M_is_leap_second)
__is.setstate(ios_base::failbit);
else
{
constexpr sys_days __epoch(days(3657)); // 1980y/1/Sunday[1]
- auto __d = __p._M_sys_days - __epoch + __p._M_time - *__offset;
+ auto __d = __p._M_sys_days - __epoch + __p._M_time - __off;
gps_time<common_type_t<_Duration, seconds>> __gt(__d);
__tp = __detail::__round<_Duration>(__gt);
+ if (__offset_p)
+ *__offset_p = __off;
+ if (__abbrev_p)
+ *__abbrev_p = std::move(__abbr);
}
}
return __is;
@@ -4769,7 +4787,7 @@ namespace __detail
__h = hours(__val);
__val = __read_unsigned(2);
- if (__val == -1 || __val > 60) [[unlikely]]
+ if (__val == -1 || __val > 59) [[unlikely]]
if ((_M_need & _ChronoParts::_TimeOfDay) != 0)
{
__err |= ios_base::failbit;
@@ -4802,10 +4820,12 @@ namespace __detail
&& !__is_floating)
{
auto __val = __read_unsigned(__num ? __num : 2);
- if (0 <= __val && __val <= 59) [[likely]]
+ if (0 <= __val && __val <= 60) [[likely]]
__s = seconds(__val);
else
{
+ // Not a valid second, but we only fail if we're
+ // parsing into a type that cares about that.
if ((_M_need & _ChronoParts::_TimeOfDay) != 0)
__err |= ios_base::failbit;
break;
diff --git a/libstdc++-v3/testsuite/std/time/parse/114279.cc
b/libstdc++-v3/testsuite/std/time/parse/114279.cc
index 67e5cba23bd8..ff9a1f18112a 100644
--- a/libstdc++-v3/testsuite/std/time/parse/114279.cc
+++ b/libstdc++-v3/testsuite/std/time/parse/114279.cc
@@ -4,14 +4,18 @@
#include <sstream>
#include <testsuite_hooks.h>
-template<class Clock>
+template<class Clock, class Dur>
void
-test_leap_second_parsing()
+do_test_leap_second_parsing()
{
- std::chrono::time_point<Clock, std::chrono::milliseconds> tp, tp2;
+ std::chrono::time_point<Clock, Dur> tp, tp2;
- std::istringstream ss("20161231-23:59:60.05");
- ss >> std::chrono::parse("%Y%m%d-%T", tp);
+ std::string subs;
+ if constexpr (std::ratio_less_v<typename Dur::period, std::ratio<1, 1>>)
+ subs = ".05";
+
+ std::istringstream ss("20081231-23:59:60" + subs + " ");
+ ss >> std::chrono::parse("%Y%m%d-%T ", tp);
if constexpr (std::is_same_v<Clock, std::chrono::local_t>)
VERIFY( ss ); // We allow parsing "23:59:60" as local_time.
@@ -28,20 +32,33 @@ test_leap_second_parsing()
else
VERIFY( !ss ); // Other clocks do not allow "HH:MM:60"
+ std::chrono::minutes offset(9999);
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);
+ ss.str("20081231-22:59:60" + subs + " -0100"); // Same time at -1h offset.
+ ss >> std::chrono::parse("%Y%m%d-%T %z", tp2, offset);
if constexpr (std::is_same_v<Clock, std::chrono::utc_clock>)
{
VERIFY( ss );
VERIFY( tp2 == tp );
+ VERIFY( offset == std::chrono::minutes(-60) );
}
else
- VERIFY( !ss );
+ {
+ VERIFY( !ss );
+ VERIFY( offset == std::chrono::minutes(9999) ); // unchanged on fail
+ }
}
}
+template<class Clock>
+void
+test_leap_second_parsing()
+{
+ do_test_leap_second_parsing<Clock, std::chrono::milliseconds>();
+ do_test_leap_second_parsing<Clock, std::chrono::seconds>();
+}
+
int main()
{
test_leap_second_parsing<std::chrono::system_clock>();
diff --git a/libstdc++-v3/testsuite/std/time/parse/126543.cc
b/libstdc++-v3/testsuite/std/time/parse/126543.cc
new file mode 100644
index 000000000000..2d2f633b4b7a
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/parse/126543.cc
@@ -0,0 +1,40 @@
+// { dg-do run { target c++20 } }
+
+// Bug 126543 - chrono::parse should accept "60" seconds for a utc_time
+// that is a valid leap second
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+int main()
+{
+ std::istringstream in;
+ std::chrono::utc_seconds ut;
+ std::chrono::sys_seconds st;
+
+ in.str("2016-12-31 24:59:59"); // invalid hour
+ in >> parse("%F %T", ut);
+ VERIFY(in.fail());
+ in.clear();
+
+ in.str("2016-12-31 23:60:59"); // invalid minute
+ in >> parse("%F %T", ut);
+ VERIFY(in.fail());
+ in.clear();
+
+ in.str("2016-12-31 23:59:60"); // valid leap second
+ in >> parse("%F %T", ut);
+ VERIFY(in.good());
+ in.clear();
+
+ in.str("2016-12-31 23:58:60"); // invalid leap second
+ in >> parse("%F %T", ut);
+ VERIFY(in.fail());
+ in.clear();
+
+ in.str("2016-12-31 23:59:60");
+ in >> parse("%F %T", st);
+ VERIFY(in.fail()); // sys_time doesn't use leap seconds
+ in.clear();
+}
--
2.55.0