Two kinds of time zone ID
Windows names time zones like Central Europe Standard Time (the name stays "Standard" all summer). Linux, macOS, browsers and most databases use IANA IDs like Europe/Belgrade. Since .NET 6, TimeZoneInfo.FindSystemTimeZoneById accepts both kinds on every OS, converting through ICU, and TryConvertWindowsIdToIanaId and TryConvertIanaIdToWindowsId convert explicitly.
One Windows ID usually covers several IANA zones. The conversion picks a default (Budapest for Central Europe Standard Time) unless you pass a region: TryConvertWindowsIdToIanaId("Central Europe Standard Time", "RS", out var id) gives Europe/Belgrade.
The Linux trap: IDs .NET returns but cannot load
For seven Windows IDs, .NET's default IANA mapping is an old alias: India Standard Time becomes Asia/Calcutta, FLE Standard Time becomes Europe/Kiev, and likewise for Argentina, Greenland, Myanmar, Nepal and US Eastern. Ubuntu 24.04 and other Debian-based images now ship those aliases in a separate tzdata-legacy package. Without it, even FindSystemTimeZoneById("India Standard Time") throws TimeZoneNotFoundException, because .NET resolves it through the missing alias. We reproduced this on Ubuntu 24.04 with .NET 8, and installing tzdata-legacy fixed it.
Two ways out: add tzdata-legacy to the image, or use the lookup helper above, which maps each alias to its current name.
Other things that break
- Globalization-invariant mode. With
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true(common in small container images), Windows and IANA IDs no longer convert, and looking up a Windows ID throws. - No tzdata at all. A container without the tzdata package has no time zones; every lookup except UTC throws.
- Similar names. Central Europe Standard Time (Budapest, Belgrade, Prague) and Central European Standard Time (Warsaw, Sarajevo, Zagreb) are different IDs.
- Zones with no Windows ID. A few IANA zones, such as Antarctica/Troll and America/Coyhaique, have no Windows equivalent, so converting them fails.
- DST gaps and overlaps. Converting a local time that a clock change skipped throws
ArgumentException; checkIsInvalidTimefirst. A time that happens twice is treated as standard time without warning. - Historical conversions on Linux. Testing .NET 8 against tzdata showed that for zones whose standard offset has since changed (much of Russia, Turkey, parts of Mexico, Argentina and Kazakhstan, and others),
ConvertTimeToUtccan misplace the gap or overlap of past transitions. Converting from UTC to local time was correct everywhere we checked. The converter above warns when you hit one of these transitions.
FAQ
Why does FindSystemTimeZoneById("India Standard Time") throw on Linux?
.NET maps India Standard Time to Asia/Calcutta, an old alias of Asia/Kolkata. Without the tzdata-legacy package the alias file does not exist, so the lookup throws TimeZoneNotFoundException. Install tzdata-legacy or map the alias to its current name.
Can I use Windows time zone IDs on Linux?
Yes, since .NET 6, as long as ICU is available. In globalization-invariant mode it does not work.
What is the difference between Central Europe Standard Time and Central European Standard Time?
Same offset and rules, different cities. Central Europe Standard Time covers Budapest, Belgrade, Prague, Bratislava, Ljubljana, Tirana and Podgorica; Central European Standard Time covers Warsaw, Sarajevo, Zagreb and Skopje.
What happens when I convert a time that falls in a DST gap?
ConvertTimeToUtc and ConvertTime throw ArgumentException for a skipped local time. Check IsInvalidTime first. For a time that happens twice, they assume standard time.