TTimeZone.Local versus IANA tzdata wrapper for historic Delphi date conversion?
0 reputation · 19 Apr 2025, 13:34 UTC
0 reputation · 19 Apr 2025, 13:34 UTC
Developers need to convert a UTC timestamp from the past (e.g., 1995‑07‑01 12:00) to the local time of a region that has altered its daylight‑saving rules since that date.
The goal is to obtain an offset that matches the historical IANA timezone database, regardless of whether the host operating system has retained those older rules.
Delphi’s RTL TTimeZone.Local converts using the current OS zone information, which is simple but may reflect only the rules present at runtime. An alternative is to wrap an IANA tzdata source, providing deterministic historic offsets at the cost of an external dependency and the need for periodic updates.
Which approach reliably yields correct historic conversions without requiring OS updates, and can the two be used together safely?
30275 reputation · 20 Apr 2025, 00:24 UTC
Use an IANA tzdata wrapper (such as the TZDB library or ICU bindings) for any conversion that must reflect historical daylight-saving rules. TTimeZone.Local on Windows only applies the current rule set, so a 1995 timestamp in a region that later changed its DST policy will return the wrong offset. On POSIX, TTimeZone delegates to the system zoneinfo file, which is accurate only if the OS package is up to date — something you cannot control at deploy time. The two approaches can coexist safely in the same process provided you keep their identifier spaces separate and map Windows zone IDs to IANA IDs explicitly.
TTimeZone.Local Fails for Historical Dates on WindowsThe RTL calls GetDynamicTimeZoneInformation or GetTimeZoneInformationForYear. Both APIs expose a single rule pair (standard/daylight transition) for the queried year, but Windows does not ship a full transition history in the registry. The registry key HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones stores only the current rule set. Consequently, TTimeZone.Local.GetUtcOffset(EncodeDateTime(1995,7,1,12,0,0,0)) returns \"what the current rules would produce for July 1\", not the offset that was actually in effect in 1995.
Libraries such as Eric Grange's TZDB or ICU4D/ICU via Delphi bindings embed a specific IANA tzdata release (e.g., 2024a). They parse the compiled zoneinfo binary (or the source .zi file) and build complete transition tables for every IANA zone ID (America/Sao_Paulo, Europe/London, etc.). A call like TZDB.GetUtcOffset('America/Sao_Paulo', UTCDateTime) walks the historical transitions and returns the offset that was legally in force at that instant, independent of the host OS.
TTimeZone for \"now\" UI. Displaying the current local time, formatting Now, or getting the OS-configured zone name are legitimate uses.\"E. South America Standard Time\") do not map 1:1 to IANA zones. Load the CLDR windowsZones.xml supplemental data (available from unicode.org) and build a TDictionary for the zones your application actually uses.TTimeZone.TTimeZoneInfo instance to code expecting an IANA zone object. They are incompatible types.Create a test console project targeting the zone in question (e.g., Brazil). Compare:
var
UTC: TDateTime := EncodeDateTime(1995,7,1,12,0,0,0);
WinOffset: Integer := TTimeZone.Local.GetUtcOffset(UTC); // minutes
IANAOffset: Integer := TZDB.GetUtcOffset('America/Sao_Paulo', UTC); // minutes
begin
Writeln('Windows offset: ', WinOffset);
Writeln('IANA offset: ', IANAOffset);
end.
If the two numbers differ, the Windows result is historically incorrect for that date.
Which specific Windows timezone identifier (TTimeZone.Local.Id value) is your application configured to use? The mapping to an IANA zone is not always unique (one Windows zone can cover multiple IANA zones with different histories), so the exact ID determines whether a single IANA zone suffices or whether you need user disambiguation.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.