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<common_type_t<_Duration, seconds>> __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<common_type_t<_Duration, seconds>> __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<file_clock>(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<std::chrono::seconds> 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<file_clock>(expected) ); VERIFY( abbrev == "BST" ); VERIFY( offset == 60min ); + + // Test round trip + std::stringstream ss; + ss << clock_cast<file_clock>(expected) << " 0123456"; + VERIFY( ss >> parse("%F %T %z%Z", tp, abbrev, offset) ); + VERIFY( ss.eof() ); + VERIFY( (tp + offset) == clock_cast<file_clock>(expected) ); + VERIFY( abbrev == "456" ); + VERIFY( offset == (1h + 23min) ); + + ss.str(""); + ss.clear(); + ss << file_time<seconds>{}; + 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<gps_clock>(expected) ); VERIFY( abbrev == "BST" ); VERIFY( offset == 60min ); + + // Test round trip + std::stringstream ss; + ss << clock_cast<gps_clock>(expected) << " GPS -1234"; + VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) ); + VERIFY( ! ss.eof() ); + VERIFY( (tp + offset) == clock_cast<gps_clock>(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<tai_clock>(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<tai_clock>(expected) ); VERIFY( abbrev == "BST" ); VERIFY( offset == 60min ); + + // Test round trip + std::stringstream ss; + ss << clock_cast<tai_clock>(expected) << " TAI 0123"; + VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) ); + VERIFY( ! ss.eof() ); + VERIFY( (tp + offset) == clock_cast<tai_clock>(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<utc_clock>(expected) ); + + // Test round trip + std::stringstream ss; + ss << clock_cast<utc_clock>(expected) << " 0012 UTC"; + VERIFY( ss >> parse("%F %T %z %Z", tp, abbrev, offset) ); + VERIFY( ss.eof() ); + VERIFY( (tp + offset) == clock_cast<utc_clock>(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() -- 2.46.0