libstdc++: Fix parsing of fractional seconds [PR114244]

When converting a chrono::duration<long double> to a result type with an
integer representation we should use chrono::round<_Duration> so that we
don't truncate towards zero. Rounding ensures that e.g. 0.001999s
becomes 2ms not 1ms.

We can also remove some redundant uses of chrono::duration_cast to
convert from seconds to _Duration, because the _Parser class template
requires _Duration type to be able to represent seconds without loss of
precision.

This also fixes a bug where no fractional part would be parsed for
chrono::duration<long double> because its period is ratio<1>. We should
also consider treat_as_floating_point<rep> when deciding whether to skip
reading a fractional part.

libstdc++-v3/ChangeLog:

	PR libstdc++/114244
	* include/bits/chrono_io.h (_Parser::operator()): Remove
	redundant uses of duration_cast. Use chrono::round to convert
	long double value to durations with integer representations.
	Check represenation type when deciding whether to skip parsing
	fractional seconds.
	* testsuite/20_util/duration/114244.cc: New test.
	* testsuite/20_util/duration/io.cc: Check that a floating-point
	duration with ratio<1> precision can be parsed.
This commit is contained in:
Jonathan Wakely 2024-03-07 13:15:41 +00:00
parent 9ccd03dee4
commit 5f9d7a5b6c
3 changed files with 60 additions and 6 deletions

View file

@ -3113,6 +3113,9 @@ namespace __detail
unsigned __num = 0; // Non-zero for N modifier.
bool __is_flag = false; // True if we're processing a % flag.
constexpr bool __is_floating
= treat_as_floating_point_v<typename _Duration::rep>;
// If an out-of-range value is extracted (e.g. 61min for %M),
// do not set failbit immediately because we might not need it
// (e.g. parsing chrono::year doesn't care about invalid %M values).
@ -3195,7 +3198,7 @@ namespace __detail
__d = day(__tm.tm_mday);
__h = hours(__tm.tm_hour);
__min = minutes(__tm.tm_min);
__s = duration_cast<_Duration>(seconds(__tm.tm_sec));
__s = seconds(__tm.tm_sec);
}
}
__parts |= _ChronoParts::_DateTime;
@ -3564,8 +3567,8 @@ namespace __detail
if (!__is_failed(__err))
__s = seconds(__tm.tm_sec);
}
else if constexpr (ratio_equal_v<typename _Duration::period,
ratio<1>>)
else if constexpr (_Duration::period::den == 1
&& !__is_floating)
{
auto __val = __read_unsigned(__num ? __num : 2);
if (0 <= __val && __val <= 59) [[likely]]
@ -3577,7 +3580,7 @@ namespace __detail
break;
}
}
else
else // Read fractional seconds
{
basic_stringstream<_CharT> __buf;
auto __digit = _S_try_read_digit(__is, __err);
@ -3626,7 +3629,10 @@ namespace __detail
else
{
duration<long double> __fs(__val);
__s = duration_cast<_Duration>(__fs);
if constexpr (__is_floating)
__s = __fs;
else
__s = chrono::round<_Duration>(__fs);
}
}
}
@ -3737,7 +3743,7 @@ namespace __detail
{
__h = hours(__tm.tm_hour);
__min = minutes(__tm.tm_min);
__s = duration_cast<_Duration>(seconds(__tm.tm_sec));
__s = seconds(__tm.tm_sec);
}
}
__parts |= _ChronoParts::_TimeOfDay;

View file

@ -0,0 +1,36 @@
// { dg-do run { target c++20 } }
// { dg-timeout-factor 2 }
// { dg-require-namedlocale "en_US.ISO8859-1" }
// PR libstdc++/114244 Need to use round when parsing fractional seconds
#include <chrono>
#include <sstream>
#include <testsuite_hooks.h>
void
test_pr114244()
{
using namespace std::chrono;
seconds s;
milliseconds ms;
microseconds us;
std::istringstream is;
is.clear();
is.str("0.002");
VERIFY( is >> parse("%S", ms) );
VERIFY( ms == 2ms ); // not 1ms
is.imbue(std::locale(ISO_8859(1,en_US)));
is.clear();
is.str("0.002");
VERIFY( is >> parse("%S", us) );
VERIFY( us == 2000us ); // not 1999us
}
int main()
{
test_pr114244();
}

View file

@ -200,6 +200,18 @@ test_parse()
VERIFY( is >> parse("%S", us) );
VERIFY( us == 976us );
VERIFY( is.get() == '5' );
is.clear();
is.str("0.5");
std::chrono::duration<double> ds;
VERIFY( is >> parse("%S", ds) );
VERIFY( ds == 0.5s );
is.clear();
is.str("0.125");
std::chrono::duration<double, std::milli> dms;
VERIFY( is >> parse("%S", dms) );
VERIFY( dms == 0.125s );
}
int main()