Skip to content

Commit 222ff19

Browse files
committed
libstdc++: Fix std::chrono::parse for TAI and GPS clocks
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.
1 parent 1fa45e7 commit 222ff19

File tree

6 files changed

+128
-7
lines changed

6 files changed

+128
-7
lines changed

libstdc++-v3/include/bits/chrono_io.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,8 +2939,9 @@ namespace __detail
29392939
__is.setstate(ios_base::failbit);
29402940
else
29412941
{
2942-
auto __st = __p._M_sys_days + __p._M_time - *__offset;
2943-
auto __tt = tai_clock::from_utc(utc_clock::from_sys(__st));
2942+
constexpr sys_days __epoch(-days(4383)); // 1958y/1/1
2943+
auto __d = __p._M_sys_days - __epoch + __p._M_time - *__offset;
2944+
tai_time<common_type_t<_Duration, seconds>> __tt(__d);
29442945
__tp = chrono::time_point_cast<_Duration>(__tt);
29452946
}
29462947
}
@@ -2977,9 +2978,10 @@ namespace __detail
29772978
__is.setstate(ios_base::failbit);
29782979
else
29792980
{
2980-
auto __st = __p._M_sys_days + __p._M_time - *__offset;
2981-
auto __tt = gps_clock::from_utc(utc_clock::from_sys(__st));
2982-
__tp = chrono::time_point_cast<_Duration>(__tt);
2981+
constexpr sys_days __epoch(days(3657)); // 1980y/1/Sunday[1]
2982+
auto __d = __p._M_sys_days - __epoch + __p._M_time - *__offset;
2983+
gps_time<common_type_t<_Duration, seconds>> __gt(__d);
2984+
__tp = chrono::time_point_cast<_Duration>(__gt);
29832985
}
29842986
}
29852987
return __is;

libstdc++-v3/testsuite/std/time/clock/file/io.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ test_format()
3232
auto ft = clock_cast<file_clock>(sys_days(2024y/January/21)) + 0ms + 2.5s;
3333
s = std::format("{}", ft);
3434
VERIFY( s == "2024-01-21 00:00:02.500");
35+
36+
const std::chrono::file_time<std::chrono::seconds> t0{};
37+
s = std::format("{:%Z %z %Ez %Oz}", t0);
38+
VERIFY( s == "UTC +0000 +00:00 +00:00" );
39+
40+
s = std::format("{}", t0);
41+
// chrono::file_clock epoch is unspecified, so this is libstdc++-specific.
42+
VERIFY( s == "2174-01-01 00:00:00" );
3543
}
3644

3745
void
@@ -49,6 +57,21 @@ test_parse()
4957
VERIFY( tp == clock_cast<file_clock>(expected) );
5058
VERIFY( abbrev == "BST" );
5159
VERIFY( offset == 60min );
60+
61+
// Test round trip
62+
std::stringstream ss;
63+
ss << clock_cast<file_clock>(expected) << " 0123456";
64+
VERIFY( ss >> parse("%F %T %z%Z", tp, abbrev, offset) );
65+
VERIFY( ss.eof() );
66+
VERIFY( (tp + offset) == clock_cast<file_clock>(expected) );
67+
VERIFY( abbrev == "456" );
68+
VERIFY( offset == (1h + 23min) );
69+
70+
ss.str("");
71+
ss.clear();
72+
ss << file_time<seconds>{};
73+
VERIFY( ss >> parse("%F %T", tp) );
74+
VERIFY( tp.time_since_epoch() == 0s );
5275
}
5376

5477
int main()

libstdc++-v3/testsuite/std/time/clock/gps/io.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,19 @@ test_format()
4242
// PR libstdc++/113500
4343
s = std::format("{}", gt + 150ms + 10.5s);
4444
VERIFY( s == "2000-01-01 00:00:23.650" );
45+
46+
s = std::format("{:%Z %z %Ez %Oz}", gt);
47+
VERIFY( s == "GPS +0000 +00:00 +00:00" );
48+
49+
s = std::format("{}", gps_seconds{});
50+
VERIFY( s == "1980-01-06 00:00:00" );
4551
}
4652

4753
void
4854
test_parse()
4955
{
5056
using namespace std::chrono;
51-
const sys_seconds expected = sys_days(2023y/August/9) + 20h + 44min + 3s;
57+
const sys_seconds expected = sys_days(2023y/August/9) + 20h + 43min + 45s;
5258
gps_seconds tp;
5359

5460
minutes offset;
@@ -59,6 +65,20 @@ test_parse()
5965
VERIFY( tp == clock_cast<gps_clock>(expected) );
6066
VERIFY( abbrev == "BST" );
6167
VERIFY( offset == 60min );
68+
69+
// Test round trip
70+
std::stringstream ss;
71+
ss << clock_cast<gps_clock>(expected) << " GPS -1234";
72+
VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) );
73+
VERIFY( ! ss.eof() );
74+
VERIFY( (tp + offset) == clock_cast<gps_clock>(expected) );
75+
VERIFY( abbrev == "GPS" );
76+
VERIFY( offset == -(12h + 34min) );
77+
78+
ss.str("");
79+
ss << gps_seconds{};
80+
VERIFY( ss >> parse("%F %T", tp) );
81+
VERIFY( tp.time_since_epoch() == 0s );
6282
}
6383

6484
int main()

libstdc++-v3/testsuite/std/time/clock/local/io.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ test_format()
8686
s = std::format("{:%Z %T %F %z %Ez}", ltf);
8787
__builtin_puts(s.c_str());
8888
VERIFY( s == "FOO 20:22:02 2024-07-28 -0100 -01:00" );
89+
90+
s = std::format("{}", local_seconds{});
91+
VERIFY( s == "1970-01-01 00:00:00" );
8992
}
9093

9194
void
@@ -103,6 +106,20 @@ test_parse()
103106
VERIFY( tp == local_seconds(expected.time_since_epoch()) );
104107
VERIFY( abbrev == "BST" );
105108
VERIFY( offset == 60min );
109+
110+
// Test round trip
111+
std::stringstream ss;
112+
ss << local_seconds{expected.time_since_epoch()} << " X 0123";
113+
VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) );
114+
VERIFY( ! ss.eof() );
115+
VERIFY( tp == local_seconds{expected.time_since_epoch()} );
116+
VERIFY( abbrev == "X" );
117+
VERIFY( offset == (1h + 23min) );
118+
119+
ss.str("");
120+
ss << local_seconds{};
121+
VERIFY( ss >> parse("%F %T", tp) );
122+
VERIFY( tp.time_since_epoch() == 0s );
106123
}
107124

108125
int main()

libstdc++-v3/testsuite/std/time/clock/tai/io.cc

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ test_ostream()
2121
VERIFY( s == "==2000-01-01 00:00:32" );
2222
}
2323

24+
void
25+
test_format()
26+
{
27+
using std::format;
28+
using namespace std::chrono;
29+
30+
auto st = sys_days{2000y/January/1};
31+
auto tt = clock_cast<tai_clock>(st);
32+
auto s = std::format("{}", tt);
33+
VERIFY( s == "2000-01-01 00:00:32" );
34+
35+
// PR libstdc++/113500
36+
s = std::format("{}", tt + 150ms);
37+
VERIFY( s == "2000-01-01 00:00:32.150" );
38+
39+
s = std::format("{:%Z %z %Ez %Oz}", tt);
40+
VERIFY( s == "TAI +0000 +00:00 +00:00" );
41+
42+
s = std::format("{}", tai_seconds{});
43+
VERIFY( s == "1958-01-01 00:00:00" );
44+
}
45+
2446
void
2547
test_parse()
2648
{
@@ -30,16 +52,31 @@ test_parse()
3052

3153
minutes offset;
3254
std::string abbrev;
33-
std::istringstream is("8/9/23 214403 +1 BST#");
55+
std::istringstream is("8/9/23 214440 +1 BST#");
3456
VERIFY( is >> parse("%D %2H%2M%2S %Oz %Z", tp, abbrev, offset) );
3557
VERIFY( ! is.eof() );
3658
VERIFY( tp == clock_cast<tai_clock>(expected) );
3759
VERIFY( abbrev == "BST" );
3860
VERIFY( offset == 60min );
61+
62+
// Test round trip
63+
std::stringstream ss;
64+
ss << clock_cast<tai_clock>(expected) << " TAI 0123";
65+
VERIFY( ss >> parse("%F %T %Z %z", tp, abbrev, offset) );
66+
VERIFY( ! ss.eof() );
67+
VERIFY( (tp + offset) == clock_cast<tai_clock>(expected) );
68+
VERIFY( abbrev == "TAI" );
69+
VERIFY( offset == (1h + 23min) );
70+
71+
ss.str("");
72+
ss << tai_seconds{};
73+
VERIFY( ss >> parse("%F %T", tp) );
74+
VERIFY( tp.time_since_epoch() == 0s );
3975
}
4076

4177
int main()
4278
{
4379
test_ostream();
80+
test_format();
4481
test_parse();
4582
}

libstdc++-v3/testsuite/std/time/clock/utc/io.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ test_format()
116116
// PR libstdc++/113500
117117
s = std::format("{}", leap + 100ms + 2.5s);
118118
VERIFY( s == "2017-01-01 00:00:01.600");
119+
120+
std::chrono::utc_seconds t0{};
121+
s = std::format("{:%Z %z %Ez %Oz}", t0);
122+
VERIFY( s == "UTC +0000 +00:00 +00:00" );
123+
124+
s = std::format("{}", t0);
125+
VERIFY( s == "1970-01-01 00:00:00" );
119126
}
120127

121128
void
@@ -146,6 +153,21 @@ test_parse()
146153
VERIFY( is >> parse("%G-W%V-%u %T", tp) );
147154
VERIFY( ! is.eof() );
148155
VERIFY( tp == clock_cast<utc_clock>(expected) );
156+
157+
// Test round trip
158+
std::stringstream ss;
159+
ss << clock_cast<utc_clock>(expected) << " 0012 UTC";
160+
VERIFY( ss >> parse("%F %T %z %Z", tp, abbrev, offset) );
161+
VERIFY( ss.eof() );
162+
VERIFY( (tp + offset) == clock_cast<utc_clock>(expected) );
163+
VERIFY( abbrev == "UTC" );
164+
VERIFY( offset == 12min );
165+
166+
ss.str("");
167+
ss.clear();
168+
ss << utc_seconds{};
169+
VERIFY( ss >> parse("%F %T", tp) );
170+
VERIFY( tp.time_since_epoch() == 0s );
149171
}
150172

151173
int main()

0 commit comments

Comments
 (0)