libstdc++: Fix rounding in chrono::parse
commit5cf26f2569bf007a2c9c058e43ddfe9a5f67da42
authorJonathan Wakely <jwakely@redhat.com>
Fri, 6 Sep 2024 20:41:47 +0000 (6 21:41 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Wed, 2 Oct 2024 10:36:17 +0000 (2 11:36 +0100)
treee451d2345b8e5efcc46b9d333c8ca320262e288d
parent05b7ab86e7d70e6f71a3012b08f5704b056a69fe
libstdc++: Fix rounding in chrono::parse

I noticed that chrono::parse was using duration_cast and time_point_cast
to convert the parsed value to the result. Those functions truncate
towards zero, which is not generally what you want. Especially for
negative times before the epoch, where truncating towards zero rounds
"up" towards the next duration/time_point. Using chrono::round is
typically better, as that rounds to nearest.

However, while testing the fix I realised that rounding to the nearest
can give surprising results in some cases. For example if we parse a
chrono::sys_days using chrono::parse("F %T", "2024-09-22 18:34:56", tp)
then we will round up to the next day, i.e. sys_days(2024y/09/23). That
seems surprising, and I think 2024-09-22 is what most users would
expect.

This change attempts to provide a hybrid rounding heuristic where we use
chrono::round for the general case, but when the result has a period
that is one of minutes, hours, days, weeks, or years then we truncate
towards negative infinity using chrono::floor. This means that we
truncate "2024-09-22 18:34:56" to the start of the current
minute/hour/day/week/year, instead of rounding up to 2024-09-23, or to
18:35, or 17:00. For a period of months chrono::round is used, because
the months duration is defined as a twelfth of a year, which is not
actually the length of any calendar month. We don't want to truncate to
a whole number of "months" if that can actually go from e.g. 2023-03-01
to 2023-01-31, because February is shorter than chrono::months(1).

libstdc++-v3/ChangeLog:

* include/bits/chrono_io.h (__detail::__use_floor): New
function.
(__detail::__round): New function.
(from_stream): Use __detail::__round.
* testsuite/std/time/clock/file/io.cc: Check for expected
rounding in parse.
* testsuite/std/time/clock/gps/io.cc: Likewise.
libstdc++-v3/include/bits/chrono_io.h
libstdc++-v3/testsuite/std/time/clock/file/io.cc
libstdc++-v3/testsuite/std/time/clock/gps/io.cc