diff mbox series

[committed] libstdc++: Fix std::chrono::parse for TAI and GPS clocks

Message ID 20240906204933.3288571-1-jwakely@redhat.com
State New
Headers show
Series [committed] libstdc++: Fix std::chrono::parse for TAI and GPS clocks | expand

Commit Message

Jonathan Wakely Sept. 6, 2024, 8:47 p.m. UTC
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 mbox series

Patch

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()