On Wed, 9 Jul 2025 at 20:21, Björn Schäpers wrote: > > Am 09.07.2025 um 16:16 schrieb Jonathan Wakely: > > On Wed, 9 Jul 2025 at 15:13, Jonathan Wakely <jwak...@redhat.com> wrote: > >> > >> On Tue, 8 Jul 2025 at 21:47, Björn Schäpers <g...@hazardy.de> wrote: > >>> index 9923d14b7a7..bfbba077b92 100644 > >>> --- a/libstdc++-v3/src/c++20/tzdb.cc > >>> +++ b/libstdc++-v3/src/c++20/tzdb.cc > >>> @@ -48,6 +48,8 @@ > >>> # define WIN32_LEAN_AND_MEAN > >>> # include <windows.h> > >>> # include <psapi.h> > >>> + > >>> +# include <array> > >>> #endif > >>> > >>> #if defined __GTHREADS && ATOMIC_POINTER_LOCK_FREE == 2 > >>> @@ -1768,6 +1770,98 @@ namespace std::chrono > >>> > >>> return nullptr; // not found > >>> } > >>> + > >>> +#ifdef _GLIBCXX_HAVE_WINDOWS_H > >>> + string_view > >>> + detect_windows_zone() noexcept > >>> + { > >>> + DYNAMIC_TIME_ZONE_INFORMATION information{}; > >>> + if (GetDynamicTimeZoneInformation(&information) == > >>> TIME_ZONE_ID_INVALID) > >>> + return {}; > >>> + > >>> + constexpr SYSTEMTIME all_zero_time{}; > >>> + const wstring_view zone_name{ information.TimeZoneKeyName }; > >>> + auto equal = [](const SYSTEMTIME &lhs, const SYSTEMTIME &rhs) > >>> noexcept > >>> + { return memcmp(&lhs, &rhs, sizeof(SYSTEMTIME)) == 0; }; > >>> + // The logic is copied from icu, couldn't find the source. > >>> + // Detect if DST is disabled. > >>> + if (information.DynamicDaylightTimeDisabled > >>> + && equal(information.StandardDate, information.DaylightDate) > >>> + && ((!zone_name.empty() > >>> + && equal(information.StandardDate, all_zero_time)) > >>> + || (zone_name.empty() > >>> + && !equal(information.StandardDate, all_zero_time)))) > >>> + { > >>> + if (information.Bias == 0) > >>> + return "Etc/UTC"; > >>> + > >>> + if (information.Bias % 60 != 0) > >>> + // If the offset is not in full hours, we can't do anything > >>> really. > >>> + return {}; > >>> + > >>> + const auto raw_index = information.Bias / 60; > >>> + > >>> + // The bias added to the local time equals UTC. And GMT+X > >>> corrosponds > >>> + // to UTC-X, the sign is negated. Thus we can use the hourly > >>> bias as > >>> + // an index into an array. > >>> + if (raw_index < 0 && raw_index >= -14) > >>> + { > >>> + static array<string_view, 14> table{ > >>> + "Etc/GMT-1", "Etc/GMT-2", "Etc/GMT-3", "Etc/GMT-4", > >>> + "Etc/GMT-5", "Etc/GMT-6", "Etc/GMT-7", "Etc/GMT-8", > >>> + "Etc/GMT-9", "Etc/GMT-10", "Etc/GMT-11", "Etc/GMT-12", > >>> + "Etc/GMT-13", "Etc/GMT-14" > >>> + }; > >>> + return table[-raw_index - 1]; > >>> + } > >>> + else if (raw_index > 0 && raw_index <= 12) > >>> + { > >>> + static array<string_view, 14> table{ > >>> + "Etc/GMT+1", "Etc/GMT+2", "Etc/GMT+3", "Etc/GMT+4", > >>> + "Etc/GMT+5", "Etc/GMT+6", "Etc/GMT+7", "Etc/GMT+8", > >>> + "Etc/GMT+9", "Etc/GMT+10", "Etc/GMT+11", "Etc/GMT+12" > >>> + }; > >>> + return table[raw_index - 1]; > >>> + } > >>> + return {}; > >>> + } > >>> + > >>> +#define _GLIBCXX_GET_WINDOWS_ZONES_MAP > >>> +#include <bits/windows_zones-map.h> > >>> +#ifdef _GLIBCXX_GET_WINDOWS_ZONES_MAP > >>> +# error "Invalid windows_zones map" > >>> +#endif > >>> + > >>> + const auto zone_range > >>> + = ranges::equal_range(windows_zone_map, zone_name, {}, > >>> + &windows_zone_map_entry::windows_name); > >>> + > >>> + const auto size = ranges::size(zone_range); > >>> + if (size == 0) > >>> + // Unknown zone, we can't detect anything. > >>> + return {}; > >>> + > >>> + if (size == 1) > >>> + // Some zones have only one territory, use the quick path. > >>> + return zone_range.front().iana_name; > >>> + > >>> + const auto geo_id = GetUserGeoID(GEOCLASS_NATION); > >>> + wstring territory; > >>> + territory.resize(2); // The terminating zero is always added on > >>> top. > >>> + if (GetGeoInfoW(geo_id, GEO_ISO2, territory.data(), 3, 0) == 0) > >> > >> I'm confused here. The buffer is 2 wchar_t values, but the cchData > >> argument is 3. The docs at > >> https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getgeoinfoa > >> say that value should be "Size of the buffer indicated by lpGeoData. > >> The size is the number of bytes for the ANSI version of the function, > >> or the number of words for the Unicode version." I utterly despise > >> this idiotic convention in MS docs of saying "ANSI" to mean ... who > >> knows ... not something actually defined by any ANSI standard ... is > >> GetGeoInfoA the "ANSI" version and GetGeoInfoAW the "Unicode" version? > >> Why not call them that? Or narrow and wide? This has nothing to do > >> with ANSI. > >> > >> Either way, the buffer is two wchar_t so four bytes, but we tell > >> GetGeoInfoW there are three ... bytes? or "words"? Where "word" means > >> 16-bit value?! > >> > >> is the resize call supposed to be resize(3) ? > >> Ah, I think what the comment is saying is that resize(2) creates a > >> string with space for two wchar_t plus a null-terminator ... it wasn't > >> clear to me what "on top" meant. > >> > >> So we have a wstring buffer which can store three wchar_t including a > >> null, so the argument to GetGeoInfoW is 3. OK. > >> > >> But the map in the generated header includes wide strings with three > >> characters, e.g. L"001", can those entries never reach this code? Do > >> they always have zone_range.size() == 1? Or is there some other reason > >> they won't get here? > > > > Ah, GEO_ISO2 means you want a 2-letter country code, so it will never > > write L"001" to the buffer, and so GetGeoInfoW will fail, returning 0, > > and so we return the entry with territory L"001". So we don't need to > > match that string, it's our fallback. > > All the territory codes in the mapping are the 2-letter country code, just > "001" > for a general zone, this is as far as I understand never returned by > GetGeoInfoW.
OK, got it. So for the "001" cases GetGeoInfoW returns 0 and then we return the "001" entry from the map. I was thinking of the case where it returns 0 as a failure, but actually it's also a correct fallback case for "not a 2-letter country code". > From the xml mapping to the generated header I remove those entries which > have > the same IANA zone as "001" (e.g. DE for germany -> Europe/Berlin), to reduce > the size of the array. Ah nice, I haven't reviewed the Python script yet so I didn't notice that, sorry. > With the current version the number of entries goes from > 500 to 361, which reduces the binary size and should speed up the equal_range > a > bit. Also it enables for more Windows time zones the optimization of only one > territory and skipping the whole GetGeoInfoW step. Nice. > > >> > >> And why use a wstring here at all, can't it just be an array? > >> wchar_t territory[3]; > >> > >> The comparison iter->territory == territory will still work with > >> wchar_t[3] as the right operand, as long as it's null terminated. > > Initially I assigned L"001" to the string to reach the fallback, that's why it > is a string. But yes, now I can just change that, will do. Thanks. By the way, your emails to the mailing list are getting bounced by dozens of recipients because you of the DMARC policy for hazardy.de, and then those recipients are getting kicked off the list for too many bounces. 550 5.7.509 Access denied, sending domain [HAZARDY.DE] does not pass DMARC verification and has a DMARC policy of reject.