https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127815
Bug ID: 127815
Summary: std::chrono::time_zone::get_info throws for an instant
before year::min() when the zone's first period
follows a rule set
Product: gcc
Version: 15.3.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: libstdc++
Assignee: unassigned at gcc dot gnu.org
Reporter: liweifriends at gmail dot com
Target Milestone: ---
Since tzdata 2026d, CST6CDT, EST5EDT, MST7MDT and PST8PDT are no longer links
to America/Chicago and co. but Zones whose only line follows the US rules from
the beginning of time:
Z CST6CDT -6 US C%sT
On such a zone, get_info(sys_seconds::min()) throws
std::length_error("vector::reserve"), and an instant less than a year before
January 1 of year::min() throws std::runtime_error("time_zone::get_info").
A zone whose first period has no rules ("-") answers the same queries
normally (America/Chicago returns its LMT period).
time_zone::_M_get_sys_info starts a zone's first period at January 1 of
year::min(). When tp is earlier than that and the period follows a rule set,
the year count from the period start to tp is negative; it is stored in an
int (truncating the int64 count: -292276991853 becomes -219215725 for
sys_seconds::min()), and std::min(100, n * 2) is passed to reserve(), which
converts the negative value to a huge size_t. When the count rounds to 0,
reserve() is skipped, but the expansion never yields a period containing tp,
so the function throws at the end.
libstdc++-v3/src/c++20/tzdb.cc, master at 080bfd8da2e (2026-10-07):
lines 1088, 1109-1110, 1251; releases/gcc-15 at a3b110420ad: lines 816,
837-838, 1033. The releases/gcc-16 branch has the same code (lines
1098-1099 at 7866d2f8e06).
The standard puts no lower bound on get_info, and zones without rules already
answer such queries, so a rule-based first period could do the same, e.g. by
clamping tp to the period's start before expanding.
The reproducer below does not depend on the system tzdata. Note that get_info
caches the expanded periods in the zone, so querying a later instant first
makes a following min() + 1s query appear to succeed; the reproducer queries
the failing instants first.
Reproduced with GCC 15.3.0 (Debian 13, gcc:15 Docker image) and GCC 15.3.1
(Fedora 43):
$ g++ -std=c++20 repro.cpp && ./a.out && ./a.out plain
Test: threw vector::reserve
Test: threw time_zone::get_info
Plain: ok, abbrev=CST
Plain: ok, abbrev=CST
----- repro.cpp -----
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <fstream>
namespace __gnu_cxx
{
// libstdc++ hook: read tzdata.zi from this directory instead of the
system's.
const char* zoneinfo_dir_override() { return "/tmp/pr-tzdb-repro"; }
}
int main(int argc, char**)
{
std::filesystem::create_directories("/tmp/pr-tzdb-repro");
std::ofstream("/tmp/pr-tzdb-repro/tzdata.zi")
<< "# version test\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 C%sT\n"
"Z Plain -6 - CST\n";
using namespace std::chrono;
// One zone per run, so no earlier call has expanded (and cached) it.
const char* name = argc > 1 ? "Plain" : "Test";
const sys_seconds ymin{sys_days{year::min() / January / 1}};
for (sys_seconds t : {sys_seconds::min(), ymin - 1s})
try
{
sys_info i = locate_zone(name)->get_info(t);
std::printf("%s: ok, abbrev=%s\n", name, i.abbrev.c_str());
}
catch (const std::exception& e)
{
std::printf("%s: threw %s\n", name, e.what());
}
}