On Tue, 13 Oct 2026 at 21:09, Jonathan Wakely <[email protected]> wrote:
>
> A very negative time point will stay negative when info.begin is
> subtracted from it, which means that a negative value will be passed to
> vector::reserve. That will convert to a huge size_t value, and the
> vector will throw length_error. This commit clamps the time point
> argument to be no less than the start of the zone's first info, so that
> `tp - info.begin` will be non-negative.
>
> This commit also changes it to chrono::abs(tp - info.begin). This is
> not strictly necessary now that tp is not less than info.begin, but it
> makes the code more robust. Even if a later change makes it possible for
> the value to be negative again, we will not pass a negative value to
> vector::reserve.
>
> libstdc++-v3/ChangeLog:
>
> PR libstdc++/127815
> * src/c++20/tzdb.cc (time_zone::_M_get_sys_info): Clamp
> parameter to start of first info. Use chrono::abs for argument
> to vector::reserve.
> * testsuite/std/time/time_zone/127815.cc: New test.
> ---
>
> Tested x86_64-linux.
>
> libstdc++-v3/src/c++20/tzdb.cc | 8 +++-
> .../testsuite/std/time/time_zone/127815.cc | 40 +++++++++++++++++++
> 2 files changed, 46 insertions(+), 2 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/127815.cc
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
> index fddfa38b32d7..67a80074381a 100644
> --- a/libstdc++-v3/src/c++20/tzdb.cc
> +++ b/libstdc++-v3/src/c++20/tzdb.cc
> @@ -1085,7 +1085,11 @@ namespace std::chrono
> sys_info info;
>
> if (i == infos.begin())
> - info.begin = sys_days(year::min()/January/1);
> + {
> + info.begin = sys_days(year::min()/January/1);
> + if (tp < info.begin)
> + tp = info.begin;
> + }
> else
> info.begin = i[-1].until();
>
> @@ -1106,7 +1110,7 @@ namespace std::chrono
> __throw_runtime_error("std::chrono::time_zone::get_info: invalid
> data");
>
> vector<pair<sys_info, string_view>> new_infos;
> - if (int n = ceil<years>(tp - info.begin).count())
> + if (int n = ceil<years>(chrono::abs(tp - info.begin)).count())
> new_infos.reserve(std::min(100, n * 2));
> long result_index = -1;
> int num_after = 4; // number of sys_info to generate past tp.
> diff --git a/libstdc++-v3/testsuite/std/time/time_zone/127815.cc
> b/libstdc++-v3/testsuite/std/time/time_zone/127815.cc
> new file mode 100644
> index 000000000000..043b2affc15d
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/time/time_zone/127815.cc
> @@ -0,0 +1,40 @@
> +// { dg-do run { target c++20 } }
> +// { dg-require-effective-target tzdb }
> +
> +#include <chrono>
> +#include <fstream>
> +#include <testsuite_hooks.h>
> +
> +namespace __gnu_cxx
> +{
> + // libstdc++ hook: read tzdata.zi from this directory instead of the
> system's.
> + const char* zoneinfo_dir_override() { return "."; }
> +}
> +
> +static void
> +test_pr127815()
> +{
> + using namespace std::chrono;
> + const sys_seconds ymin{sys_days{year::min() / January / 1}};
> +
> + static int version = 0;
> +
> + for (const char* name : {"Plain", "Test"})
> + for (sys_seconds t : {sys_seconds::min(), ymin - 1s})
> + {
> + std::ofstream("tzdata.zi")
> + << "# version test" << ++version << "\n"
> + "R X 2000 ma - Mar lastSu 2 1 D\n"
> + "R X 2000 ma - O lastSu 2 0 S\n"
> + "Z Test -6 X T%sT\n"
> + "Z Plain -6 - PST\n";
> +
> + sys_info info = locate_zone(name)->get_info(t);
Oops, this was supposed to be:
sys_info info = reload_tzdb().locate_zone(name)->get_info(t);
so that we get a new tzdb on each iteration, with no infos expanded
yet. The static variable ensures the library sees a new tzdata.zi file
each time and re-loads it.
> + VERIFY(info.abbrev == name[0] + std::string("ST"));
> + }
> +}
> +
> +int main()
> +{
> + test_pr127815();
> +}
> --
> 2.56.0
>