# TimeZoneConverter: mapping IANA, Windows and Rails time zone names in .NET

> TimeZoneConverter is a small .NET library that converts between the IANA, Windows and Rails time zone identifier schemes, and its value is that the mapping tables are embedded rather than read from the host operating system. The cost is that the tables go stale the moment a new time zone is published, which the project documentation is unusually honest about.

**mattjohnsonpint/TimeZoneConverter** — Lightweight libraries to convert between IANA, Windows, Rails, and POSIX time zones.

- Repository: https://github.com/mattjohnsonpint/TimeZoneConverter
- Stars: 905 · Forks: 86
- Language: C#
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/mattjohnsonpint-timezoneconverter

## The whole problem is four naming schemes for the same place

The library is described as a lightweight library to convert quickly between IANA, Windows and Rails time zone names, and a separate package called TimeZoneConverter.Posix exists for POSIX support.

The problem it solves comes from the fact that the same location has different identifiers depending on which system's conventions you are using. Linux and macOS use the IANA database, so New York is America/New_York. Windows uses its own identifiers, so the same place is Eastern Standard Time. Ruby on Rails uses friendly names derived from the IANA list, so it is simply Eastern Time. A server written on Linux, a desktop client on Windows and a legacy Rails application can each be storing a different string for the same moment and the same place.

The API is a set of static methods on a TZConvert class, and the documentation gives the conversions directly. From IANA to Windows:

```csharp
string tz = TZConvert.IanaToWindows("America/New_York");
// Result:  "Eastern Standard Time"
```

And the reverse, from Windows to IANA:

```csharp
string tz = TZConvert.WindowsToIana("Eastern Standard Time");
// result:  "America/New_York"
```

There is also a country-aware overload of the Windows to IANA conversion, which matters because a Windows identifier is not always one to one with an IANA identifier. The example in the documentation converts Eastern Standard Time with the country code CA and gets America/Toronto rather than America/New_York, because Canada spans several zones that share the Windows identifier. The same Windows name on a machine in the United States is America/New_York, so the country argument is how you resolve the ambiguity rather than getting an arbitrary answer.

Rails identifiers convert too, in both directions:

```csharp
string tz = TZConvert.RailsToIana("Mexico City");
// result:  "America/Mexico_City"
```

And there is a method that returns a TimeZoneInfo object from either scheme, which is the piece that makes the library usable on a Linux server talking to a Windows client.

## Embedded data is the design decision, and it is a trade

The documentation states plainly that TimeZoneConverter has no external data dependencies at runtime, and that all of the data it needs is embedded in the library itself.

That is the whole design, and it is a genuine trade rather than a feature to be admired in the abstract. The alternative is to ask the operating system, which means the answer depends on how current the host's time zone database is. Windows keeps its identifiers in the registry and updates them through Windows Updates, while Linux and macOS get theirs from the IANA database, usually through the tzdata package. A container image that skipped tzdata, or a Windows machine that has not taken a cumulative update in a year, gives you different answers for the same question.

Embedding the tables makes the conversion deterministic and identical on every machine, which is exactly what a service converting user supplied identifiers wants. The cost is that the embedded data ages, and the documentation addresses this head on. It says the data can change whenever new time zones are introduced from any of the sources, recommends using the most current revision, and suggests checking for updates regularly.

The data comes from several places, and the list is informative. The Unicode CLDR project, the IANA time zone data, the Microsoft Windows time zone updates, and the MAPPING data from ActiveSupport::TimeZone in the Rails source code. Alongside those, the documentation mentions the author's own best-informed knowledge and opinions, which it says is usually reserved for edge cases and for newly introduced zones that may not yet have been published to official sources. That last source is the one to be aware of, because it is the part that cannot be audited from outside.

The library also generally does not attempt to check whether the identifiers provided are actually present on the computer where the code is running. The assumption stated in the documentation is that the computer is kept current with time zone updates.

## The 2026 Canadian change is the sharpest edge in the library

The most concrete failure mode the documentation describes involves a specific real change.

Windows periodically introduces new time zone identifiers, and when it does, a machine that has not installed the relevant update cannot resolve the new identifier at all. The documentation's example: converting Africa/Khartoum to a Windows identifier yields Sudan Standard Time, and using that on a Windows machine without the update that created the zone will likely produce a TimeZoneNotFoundException. That is the classic failure, and it is a failure on the Windows side rather than inside the library.

The library makes an exception for the 2026 Canadian time zone changes, and the reason given is timing: the window between the new Windows identifiers becoming available and the changes taking effect was very short. The workaround allows users to upgrade the library in advance while keeping the previous mappings until the Windows updates are installed. When running on Windows, the IANA to Windows and Rails to Windows conversions automatically use the previous mapping if the new Windows identifier is not installed.

The fallback table is given in the documentation:

| New Windows ID | Fallback ID |
| --- | --- |
| `British Columbia Standard Time` | `Pacific Standard Time` |
| `Alberta Standard Time` | `Mountain Standard Time` |
| `Manitoba Standard Time` | `Central Standard Time` |

And there is a warning attached to it that deserves to be read carefully. The fallbacks preserve the previous library behaviour, but their seasonal rules are incorrect after the November 1, 2026 permanent DST transition. Installing the Windows updates is what gives correct offsets, and the fallback is not a replacement for those updates. So a deployment that installs the library early and defers the Windows update is running with wrong offsets after that date, and the library will not tell you.

One more operational detail: availability is cached when system time zones are first loaded, so applications must be restarted after installing Windows time zone updates to pick up the new identifiers. Non-Windows systems always return the current mappings, and the embedded mappings, known-zone lists and territory lists stay current regardless of what is installed on the machine.

## GetTimeZoneInfo still needs the operating system underneath it

The conversion methods are self-contained, but the method that hands you a TimeZoneInfo object is not, and the documentation is careful to separate the two.

The note on operating system data dependencies says that functions such as TZConvert.GetTimeZoneInfo rely on the underlying TimeZoneInfo object having access to time zone data of the operating system. On Windows that data comes from the registry and is maintained through Windows Updates. On macOS and Linux it comes from a distribution of the IANA time zone database, usually via the tzdata package. If the environment does not have tzdata installed, GetTimeZoneInfo will not work correctly.

The example in the documentation is the useful part. Either identifier works on any platform:

```csharp
// Either of these will work on any platform:
TimeZoneInfo tzi = TZConvert.GetTimeZoneInfo("Eastern Standard Time");
TimeZoneInfo tzi = TZConvert.GetTimeZoneInfo("America/New_York");
```

The first line is the interesting one. A Windows identifier normally cannot be resolved on Linux, because the host database has no such name. This call translates it first, then returns the TimeZoneInfo for the equivalent IANA zone, which is the behaviour a cross-platform service needs.

The container case is the practical trap. The documentation notes that the Alpine Linux Docker images for .NET Core no longer ship with tzdata, and links to the dotnet/docker issue for instructions on adding it. An Alpine based container that does not add the package will fail on this specific call while the pure string conversions continue to work, which makes for a confusing bug report. The fix is a package in the image, not a code change, and the documentation points to the issue rather than describing the steps itself.

## Unmappable zones exist and one of them is a research station

The library documents a failure mode that most conversion libraries hide, which is that some zones have no logical equivalent in another scheme.

The term used is unmappable, meaning there is no logical equivalent from one type of time zone to another. The documentation states that currently there is only one IANA zone that is unmappable to Windows, and it is Antarctica/Troll. The reasoning given is that there is no correct time zone for Windows users who may happen to be stationed at Troll Station, Antarctica, so attempting to convert it to Windows produces a TimeZoneNotFoundException.

That single case is a good illustration of the general point. A zone is unmappable when the two schemes disagree about what a time zone is. Windows time zones are political entities with a fixed set of rules per region, while IANA zones are geographic identifiers, and the two do not always line up. A place can be in a IANA zone that no Windows region has ever claimed.

The Rails direction has far more unmappable zones, and the documentation says there are many, with the complete list in the unit test code. That makes sense, because the Rails names are a friendly subset of the IANA list rather than a complete second scheme, so most Rails names do not exist and a good number of the IANA zones have no friendly name at all.

For an application this is a feature of the interface rather than a defect. A conversion that returns an exception, or a null, for a zone it cannot map, is more honest than one that returns a nearby zone and quietly shifts the offset. The documentation is explicit that a TimeZoneNotFoundException is the expected outcome, which means callers are meant to handle it rather than treat it as a crash.

## The .NET 6 question decides whether you need this at all

The compatibility section states that as of version 8.0.0 the library works with .NET 5 and newer, with dedicated targets for .NET 8, .NET 9 and .NET 10, with .NET Core 2.0 or greater, and with .NET Framework 4.6.2 and greater. .NET Framework versions below 4.6.2 are no longer supported.

The important note is the one about .NET 6 and above. The platform has built-in support for IANA and Windows time zones in a cross-platform manner, and this somewhat reduces the need for the library. The restatement is the practical test: if you are targeting only .NET 6 or higher and have either platform-provided or app-local ICU enabled, you might not need this library at all.

The documentation does not claim the library is obsolete. It says it is still supported, and that many people have found it continues to be useful in certain environments or on specific platforms. That is an honest position rather than a defensive one, and it tells you where the remaining value is.

So the decision rule is fairly simple. If your application only deals with IANA and Windows identifiers on .NET 6 or later, the framework can do this and you carry a dependency for nothing. If you still need Rails names, if you have to support .NET Framework, or if you are in an environment where the platform ICU data is unreliable or trimmed, the library remains the pragmatic answer. And the no-external-data-dependencies property still distinguishes it from the framework, because the platform implementation follows the host's database while this one follows its own embedded tables.

The licensing question also deserves attention before the package goes into a product. The repository carries a LICENSE.txt file and the licence GitHub displays is a custom one that it cannot classify, and the documentation does not summarise its terms. Read the file rather than assuming a permissive grant, and that is a question for your own counsel rather than for a review page.

## Conclusion

TimeZoneConverter fits a .NET application that has to accept time zone identifiers from more than one naming scheme, particularly a service that reads IANA names from a Linux deployment and must hand Windows identifiers to a client, or a product that still stores Rails zone names. It is the wrong choice for a .NET 6 or later application that only ever deals with IANA and Windows identifiers, because the platform now does that work itself, and it is the wrong choice where the identifiers are chosen by the system rather than by users. Before adopting it, read LICENSE.txt, since the licence GitHub displays is a custom one, and pin a version that includes the fallback mappings for the 2026 Canadian time zone changes, because the fallback offsets are wrong after the November 1, 2026 transition and only the Windows updates fix them.

## FAQ

### What does TimeZoneConverter do?

It converts between IANA, Windows and Rails time zone names, and offers a GetTimeZoneInfo method that returns a TimeZoneInfo object from either the Windows or the IANA name regardless of the operating system you are running on. A separate TimeZoneConverter.Posix package covers POSIX names.

### Do I still need TimeZoneConverter on .NET 6 or later?

The documentation says .NET 6 has built-in cross-platform support for IANA and Windows time zones through its ICU integration, so if you target only .NET 6 or higher with platform or app-local ICU enabled, you might not need the library. It remains supported and is still useful in some environments.

### Why does GetTimeZoneInfo fail in an Alpine Linux container?

Because that method relies on the operating system having time zone data, and on Linux and macOS that comes from the tzdata package, which the Alpine Linux images for .NET Core no longer ship. Install tzdata in the image and the call works, while the pure string conversions are unaffected.

### What happens when a time zone cannot be converted?

Some zones are unmappable between schemes, and the conversion throws a TimeZoneNotFoundException. The documentation names Antarctica/Troll as the only IANA zone with no Windows equivalent, and says there are many zones with no Rails equivalent, with the full list in the unit test code.

### How often should TimeZoneConverter be updated?

The documentation recommends using the most current revision and checking for updates regularly, because the embedded data changes whenever new time zones are introduced by the IANA database, CLDR, Windows updates or the Rails source. The 2026 Canadian zone changes are handled with automatic fallbacks, whose seasonal rules are wrong after the November 1, 2026 transition until the Windows updates are installed.

## Sources

- [Issues](https://github.com/mattjohnsonpint/TimeZoneConverter/issues)
- [mattjohnsonpint/TimeZoneConverter on GitHub](https://github.com/mattjohnsonpint/TimeZoneConverter)
- [README](https://github.com/mattjohnsonpint/TimeZoneConverter/blob/main/README.md)
- [Releases](https://github.com/mattjohnsonpint/TimeZoneConverter/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mattjohnsonpint-timezoneconverter
