Incorrect transition times in std::chrono::time_zone::get_info()
Jonathan Wakely
jwakely@redhat.com
Fri Jul 26 11:11:57 GMT 2024
On Fri, 26 Jul 2024 at 11:04, Edward Welbourne <edward.welbourne@qt.io> wrote:
>
> Hi libstdc++ folks,
>
> I'm the Qt Project's maintainer of dates, times and localization. We're
> currently adding a backend to QTimeZone that uses std::chrono::tzdb and
> friends. In the course of testing this I've hit some issues with the
> implementation in libstdc++. I'm currently using the version GCC 14
> uses by default on Debian/testing (but dpkg -S doesn't admit to which
> actual package supplies /lib/x86_64-linux-gnu/libstdc++.so.6 so I don't
> know which that version is).
>
> One issue is that the supported range of times is rather limited; Qt
> uses a 64-bit signed count of milliseconds from the UTC start of 1970,
> which does admittedly have an insanely wide range, but (I am happy to
> report) glibc's time_t functions seem to cope with that whole range just
> fine. However, the std::chrono::time_zone::get_info() range is
> considerably narrower (I haven't yet pinned down its limits). That's
> bearable, since the out-of-range times are of course only ever seen in
> artificial test-cases; and get_info() raises a std::runtime_error for
> out-of-range values, so we can catch that and cope by other kludges
> (that we need on other platforms anyway).
>
> The main issue, however, is that the times of many zone-transitions are
> wrong. Specifically, there are many transitions where the IANA DB, as
> digested into the contents of /usr/{share,lib}/zoneinfo/ as part of the
> standard Linux tzdata package, gives different answers for the time of a
> transition to those coming out of get_info() as the begin and end
> values. Here's my test program:
>
> #include <chrono>
>
> #include <iostream>
> #include <algorithm>
>
> template <typename R, typename T>
> static auto *binary_search_name(R &&r, const T &t)
> {
> using std::begin;
> using std::end;
> auto before = [](const auto &e, const T &t) { return e.name() < t; };
> auto it = std::lower_bound(begin(r), end(r), t, before);
Or just:
auto it = std::ranges::lower_bound(r, t, {}, [](auto& v) { return v.name(); });
:-)
> return it != end(r) && it->name() == t ? &(*it) : nullptr;
> }
>
> static const std::chrono::time_zone *idToZone(std::string_view id) noexcept
> {
> auto &tz = std::chrono::get_tzdb();
> if (auto zone = binary_search_name(tz.zones, id))
> return zone;
> if (auto link = binary_search_name(tz.links, id))
> return binary_search_name(tz.zones, link->name());
> return nullptr;
> }
Isn't this exactly what std::chrono::locate_zone(id) does?
>
> int main(int argc, const char *args[])
> {
> using namespace std::chrono;
> year_month_day ymd(year(2014), month(12), day(21));
> const sys_days date = ymd;
> for (int i = 1; i < argc; ++i) {
> const time_zone *zone = idToZone(args[i]);
> if (zone) {
> const sys_info info = zone->get_info(date);
> const zoned_time local(zone, info.begin);
> std::cout << '\n' << args[i] << " time at its transition was " << local;
> }
> }
> std::cout << '\n' << __GLIBCXX__ << '\n';
This macro is not very useful, see
https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html#abi.versioning.__GLIBCXX__
> return 0;
> }
>
> If I've misunderstood what some of the API should be doing, please let
> me know. Assuming it does what I intended, this reports the last
> transition before 2014-12-21; pass the resulting program some zone names
> on the command-line, and it reports on each. I have a script that runs
> a program for each zone known to the tzdata package; so have seen its
> results for all. Many are correct, but many are not. Observed results
> include:
>
> Europe/Kaliningrad : 2014-10-26 04:00:00 EET (offset +2)
The relevant Zone lines for this zone are:
2 R EE%sT 2011 Mar 27 2s
3 - +03 2014 O 26 2s
This says that it was 2 hours ahead of UTC until October 26 2014, then
switched to 3 hours ahead at "2s" i.e. 02:00 standard time. It looks
like I'm doing the transition at 02:00 UTC.
As I suspected, this is the bug I mentioned in the handling of the
UNTIL field, which should not be interpreted as UTC.
> Europe/Moscow : 2014-10-26 05:00:00 MSK (offset +3)
> Asia/Bishkek : 2005-08-12 06:00:00 +06
> Asia/Bangkok : 1920-04-01 07:00:00 +07
> Asia/Almaty : 2004-10-31 08:00:00 +06
> Asia/Pyongyang : 1945-08-24 09:00:00 KST (offset +9)
> Pacific/Bougainville: 1945-08-21 10:00:00 +10
> Pacific/Kosrae : 1999-01-01 11:00:00 +11
> Pacific/Kwajalein : 1993-08-20 12:00:00 +12
> Pacific/Kanton : 1994-12-31 13:00:00 +13
> Pacific/Honolulu : 1947-06-07 14:00:00 HST (was 02:00, offset -10)
> Pacific/Gambier : 1912-09-30 15:00:00 -09
> America/Metlakatla : 1983-10-29 16:00:00 PST (was 1983-10-30 02:00, offset -8)
> America/Phoenix : 1968-03-20 17:00:00 MST (was 1983-03-21, offset -7)
> America/Regina : 1960-03-30 18:00:00 CST (was 1960-04024 02:00, offset -6)
> America/Jamaica : 1983-12-31 19:00:00 EST (was 1983-10-30 02:00, offset -5)
> America/Boa_Vista : 2000-10-14 20:00:00 -04 (was 2000-10-15)
> America/Araguaina : 2013-08-31 21:00:00 -03 (was 2013 Sep)
> America/Noronha : 2002-09-30 22:00:00 -02 (was 2002-10-01)
> Asia/Karachi : 2009-10-31 23:00:00 PKT (was 2009-11-01 00:00, offset +5)
>
> Transitions in the middle of the day are not a thing that zones really
> do. Some of those near the start and end of this list are almost
> plausible, but the ones I've checked are different in tzdata.
>
> Many of the above look suspiciously like UTC's start of day, or in some
> cases its 02:00 (the default in the IANA DB's runic format), converted
> to the relevant zone, so I have to conclude that libstdc++ is
> misunderstanding Olson's arcane grammar - either that or I'm misusing
> the APIs above, in some way.
>
> So I first want to check whether I'm using the API right and, if so, to
> know where to report the bug. Available evidence of internet searches
> points to the general GCC bug-reporting, but I wanted to first check in
> case you have a separate bug-tracker for libstdc++,
>
> Eddy.
>
>
More information about the Libstdc++
mailing list