Skip to content

Conversation

@yukawa
Copy link
Contributor

@yukawa yukawa commented Aug 27, 2025

Inspired by the following message in README.md, I actually created a full implementation of cctz::TimeZoneIf by using Windows time APIs (actually their source data stored in the Windows registry).

They will also work on Windows if you install the zoneinfo files. We are interested, though, in an implementation of the cctz::TimeZoneIf interface that calls the Windows time APIs instead. Please contact us if you're interested in contributing.

So I'd like to hear whether the team is still interested in such an implementation.

Background and Motivation

Here's a quick recap of how time zone libraries would work on modern Windows:

While it is possible to use the system icu.dll to implement cctz::TimeZoneIf for Windows in CCTZ like MS STL does, I started from making it compatible with Windows time APIs mainly because registry entries used for Windows time APIs are updated in a more timely manner than the system icu.dll.

For instance, Microsoft released a new time zone info for Paraguay 2025 Time Zone update with roughly 1 months ahead of the cancelled standard time transition, to customers from Windows 7 SP1 to Windows 11, version 24H2.

At the same time, icu.dll available on my local Windows 11 24H2 still uses tzdb 2022b, which basically misses all the recent updates since 2022 including this one for Paraguay. As a result MS STL's <chrono> currently gives wrong local time for America/Asuncion in my local environment, while Windows time APIs do return the correct local time.

Implementation Note

Windows uses per-year time zone information as follows (source):

struct REG_TZI_FORMAT {
  // Base offset in minutes, where UTC == local time + Bias.
  LONG Bias;
  // Additional offset in minutes applied to standard time.
  LONG StandardBias;
  // Additional offset in minutes applied to DST.
  LONG DaylightBias;
  // Localtime (in the previous offset) when the standard time begins.
  SYSTEMTIME StandardDate;
  // Localtime (in the previous offset) when the DST begins.
  SYSTEMTIME DaylightDate;
};

For each time zone ID, one default REG_TZI_FORMAT entry and optional year-keyed REG_TZI_FORMAT entries are stored under the following registry keys:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\<zone_id>

Years that are not explicitly covered by a year-keyed entry are governed by the default entry or the earliest year-keyed entry, respectively.

SYSTEMTIME is defined as follows:

struct SYSTEMTIME {
  WORD wYear;
  WORD wMonth;
  WORD wDayOfWeek;
  WORD wDay;
  WORD wHour;
  WORD wMinute;
  WORD wSecond;
  WORD wMilliseconds;
};

There are special rules of SYSTEMTIME used in REG_TZI_FORMAT:

  • If wMonth is zero, the transition does not occur.
  • If wYear is not zero, the transition date is absolute.
  • If wYear is zero and wMonth is not zero, the transition rule is recurring every year based on wDayOfWeek. wDay indicates the occurrence of the day of the week within the month. wDay == 5 means the final occurrence during the month.
  • All fields are in the local time in the previous offset rather than the new offset.
  • (wHour, wMinute, wSecond, wMilliseconds) == (23, 59, 59, 999) is a special case that indicates the transition occurs at the end of the day (i.e., 00:00:00.000 of the next day) (source).

With above, we should be able to implement the TimeZoneIf interface.

Note that only the data loading steps from the Windows Registry need to be Windows-specific. By making rest of the implementation platform-independent, we can take advantage of fuzzing tools and code analysis tools that may or may not be available on Windows.

How to build

To actually use it as the fallback mechanism, CCTZ_USE_WIN_REGISTRY_FALLBACK macro needs to be defined when building cctz on Windows.

bazelisk run :time_tool   --cxxopt=/DCCTZ_USE_WIN_REGISTRY_FALLBACK

How to test

To run the basic tests, run the following command.

  bazelisk test //:time_zone_win_test

To run time_zone_lookup_test with this fallback, run the following command.

  bazelisk test //:time_zone_lookup_test --cxxopt=/DCCTZ_USE_WIN_REGISTRY_FALLBACK

As for time_zone_lookup_test, there are still failing tests due to the discrepancy between Windows time zone information and IANA zonefiles. These failures are expected.

@devbww
Copy link
Contributor

devbww commented Sep 5, 2025

Inspired by the ... message in README.md, I ... created a full implementation of cctz::TimeZoneIf by using Windows time APIs (actually their source data stored in the Windows registry).

So I'd like to hear whether the team is still interested in such an implementation.

Hi Yohei,

Sorry for being tardy in getting back to you.

I would say we're still generally interested, but the size of the PR is a little daunting. I guess I had hoped that the standard Windows libraries would provide interfaces that were much closer to TimeZoneIf, instead of having to read raw data from the registry ourselves. That would mean less Windows-specific code within CCTZ.

So, in response, I now wonder whether a better solution might be a WindowsRegistryZoneInfoSource, where the registry data is simply mapped into "TZif2" format, leaving the interpretation of that data to the existing "time_zone_info" code. What do you think?

Anyway, I realize that some of the code here concerns determining the local timezone on Windows, so I guess the first thing to do is to discuss #329, where that has been separated. I'll have some comments on that, so in the meantime I'll see you over there.

Bradley

@yukawa
Copy link
Contributor Author

yukawa commented Sep 5, 2025

So, in response, I now wonder whether a better solution might be a WindowsRegistryZoneInfoSource, where the registry data is simply mapped into "TZif2" format, leaving the interpretation of that data to the existing "time_zone_info" code. What do you think?

Yeah, I think it's doable and worth experimenting. Before creating this pull request, I have actually created such a prototype that implements ZoneInfoSource rather than TimeZoneIf. While that prototype was built on top of ICU APIs, porting it to use Windows time zone database shouldn't be that difficult. Hopefully I can upload it soon after #329 is done.

One open question in WindowsRegistryZoneInfoSource would be how to make sure convert works for past years to a reasonable extent when the earliest available yearly data is weekday-based DST. For future transitions my understanding is that we can rely on TZ string in the footer section for future transitions, but for past transitions we may end up having to generate transitions to a certain year, say, 1970, to make existing "time_zone_info" work. Having to pick up some threshold year is something we do not need to have to do when directly impelementing TimeZoneIf.

@yukawa yukawa force-pushed the TimeZoneIfWin branch 2 times, most recently from f060e36 to 4f74910 Compare November 26, 2025 07:27
This commit introduces a new implementation of the TimeZoneIf interface
that is compatible with Windows Time APIs.

Background:
Here's a quick comparison of how time zone libraries are implemented on
modern Windows:

 * <chrono> in Microsoft STL
     -> Uses system "icu.dll" [1] if available [2].
 * System.TimeZoneInfo in .NET
     -> Uses Windows Registry as the source of time zone information.
 * CCTZ
     -> Uses zoneinfo if found in TZDIR or ZoneInfoSourceFactory.

This commit brings what .NET does to CCTZ. The major advantages of using
Windows Registry instead of "icu.dll" are:

 * It gives the full consistency with the Windows Time APIs.
 * The data has proven to be updated via Windows Update in a good
   cadence [3]. For instance, tzdb 2022b is still used in "icu.dll"
   shipped with Windows 11 24H2.

Implementation Note:

Windows uses per-year time zone information as follows [4]:

  struct REG_TZI_FORMAT {
    // Base offset in minutes, where UTC == local time + Bias.
    LONG Bias;
    // Additional offset in minutes applied to standard time.
    LONG StandardBias;
    // Additional offset in minutes applied to DST.
    LONG DaylightBias;
    // Localtime (in the previous offset) when the standard time begins.
    SYSTEMTIME StandardDate;
    // Localtime (in the previous offset) when the DST begins.
    SYSTEMTIME DaylightDate;
  };

For each time zone ID, one default REG_TZI_FORMAT entry and optional
year-keyed REG_TZI_FORMAT entries are stored under the following
registry keys:

  HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\<zone_id>

Years that are not explicitly covered by a year-keyed entry are governed
by the default entry or the earliest year-keyed entry, respectively.

SYSTEMTIME is defined as follows:

  struct SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
  };

There are special rules of SYSTEMTIME used in REG_TZI_FORMAT [4]:

 * If wMonth is zero, the transition does not occur.
 * If wYear is not zero, the transition date is absolute.
 * If wYear is zero and wMonth is not zero, the transition rule is
   recurring every year based on wDayOfWeek. wDay indicates the
   occurrence of the day of the week within the month. wDay == 5 means
   the final occurrence during the month.
 * All fields are in the local time in the previous offset rather than
   the new offset.
 * (wHour, wMinute, wSecond, wMilliseconds) == (23, 59, 59, 999) is a
   special case that indicates the transition occurs at the end of the
   day (i.e., 00:00:00.000 of the next day) [5].

With above, we should be able to implement the TimeZoneIf interface.

Note that only the data loading steps from the Windows Registry need to
be Windows-specific. The rest of the implementation is designed to be
platform-independent in case we want to perform further testing and code
analysis as needed. To run the basic tests, run the following command.

  bazelisk test //:time_zone_win_test

How to build:

To actually use it as the fallback mechanism,

  CCTZ_USE_WIN_REGISTRY_FALLBACK

macro needs to be defined when building cctz on Windows.

  bazelisk run :time_tool  \
           --cxxopt=/DCCTZ_USE_WIN_REGISTRY_FALLBACK

  bazelisk test //:time_zone_lookup_test  \
           --cxxopt=/DCCTZ_USE_WIN_REGISTRY_FALLBACK

 [1]: https://learn.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-
 [2]: Put PR link to MS STL 1789 here.
 [3]: https://techcommunity.microsoft.com/category/windows/blog/dstblog
 [4]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-time_zone_information
 [5]: https://stackoverflow.com/a/47106207
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants