Arex388.TimeZones 4.0.0

dotnet add package Arex388.TimeZones --version 4.0.0
                    
NuGet\Install-Package Arex388.TimeZones -Version 4.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Arex388.TimeZones" Version="4.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Arex388.TimeZones" Version="4.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Arex388.TimeZones" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Arex388.TimeZones --version 4.0.0
                    
#r "nuget: Arex388.TimeZones, 4.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Arex388.TimeZones@4.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Arex388.TimeZones&version=4.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Arex388.TimeZones&version=4.0.0
                    
Install as a Cake Tool

Arex388.TimeZones

Offline time zone discovery for .NET Standard 2.0 — by coordinate, IANA id, or Windows id — plus the two conversions consumers actually need: AtTimeZone (keep the wall clock, resolve the zone's offset) and InTimeZone (keep the instant, show it in the zone). Everything is computed from TZDB; nothing is read from the operating system, so the answers are the same on Windows, Linux, and invariant-globalization containers. It is a free replacement for the Google Maps Time Zone API.

It is a facade over four packages, each doing one thing: GeoTimeZone turns a coordinate into an IANA id; NodaTime supplies TZDB — offsets, transitions, daylight-savings rules; TimeZoneConverter maps IANA ids to Windows ids and back; TimeZoneNames supplies the localized names and abbreviations from CLDR.

Install and register
dotnet add package Arex388.TimeZones
services.AddTimeZones();

// or, with options
services.AddTimeZones(o => {
	o.DefaultLanguageCode = "de";
});

Then inject ITimeZones. It is a singleton. A registered TimeProvider supplies "now" for the overloads that take no instant; without one, TimeProvider.System does. AddMemoryCache() is no longer required — the library caches only what is static (the per-language catalog of zones and names) and computes offsets per call.

The two operations

Every time zone problem is one of two operations, and they are not interchangeable:

  • AtTimeZone keeps the wall clock and resolves its offset in the zone. Use it when the date and time are what the user meant and only the zone is missing — a shift that starts at 09:00 in New York.
  • InTimeZone keeps the instant and changes the wall clock to the zone's. Use it when the value already identifies a point in time and you want to show it somewhere else.

The same input, on the day US clocks spring forward:

var wallClock = new DateTimeOffset(2026, 3, 8, 9, 0, 0, TimeSpan.Zero);

timeZones.AtTimeZone(wallClock, "America/New_York");   // 2026-03-08 09:00 -04:00 — same clock reading, the zone's offset
timeZones.InTimeZone(wallClock, "America/New_York");   // 2026-03-08 05:00 -04:00 — same instant, the zone's clock reading

AtTimeZone is lenient: a wall clock that does not exist (the spring-forward gap) is shifted forward by the gap, and one that occurs twice (the fall-back hour) resolves to its earlier occurrence. TryAtTimeZone tells you which happened:

timeZones.TryAtTimeZone(new DateTime(2026, 3, 8, 2, 30, 0), "America/New_York", out var result, out var resolution);
// result: 2026-03-08 03:30 -04:00, resolution: TimeZoneResolution.Skipped

timeZones.TryAtTimeZone(new DateTime(2026, 11, 1, 1, 30, 0), "America/New_York", out result, out resolution);
// result: 2026-11-01 01:30 -04:00, resolution: TimeZoneResolution.Ambiguous

Both accept a DateTime (its Kind is ignored) or a DateTimeOffset (its offset is ignored — only the clock reading matters). Nullable-lifting overloads (DateTime?, DateTimeOffset?null in, null out) live in TimeZonesExtensions, along with DateTimeOffset.AtTimeZone(TimeSpan), which swaps the offset and nothing else. All return null for an unknown id.

Lookups
timeZones.GetTimeZoneByCoordinate(40.7128, -74.0060);                  // America/New_York, as of now
timeZones.GetTimeZoneByCoordinate(new Coordinate(40.7128, -74.0060), at);
timeZones.GetTimeZoneByIanaId("America/New_York", at);                 // ids match case-insensitively; aliases such as US/Eastern work
timeZones.GetTimeZoneByWindowsId("Eastern Standard Time");             // the canonical zone — America/New_York
timeZones.GetTimeZoneByWindowsId("Eastern Standard Time", "CA");       // for a territory — America/Toronto
timeZones.GetTimeZonesByWindowsId("Eastern Standard Time");            // every zone that maps to it
timeZones.GetTimeZones(at);                                            // every zone with a Windows mapping, ordered by IANA id

Every lookup has an as-of-now form and an at form taking a DateTimeOffset instant (its offset is irrelevant beyond identifying the instant), and takes an optional language code for the names. Single lookups return null when the id is unknown or the zone has no Windows mapping; open ocean resolves to the nautical zone (Etc/GMT±N; UTC within 7.5° of the prime meridian). Argument errors throw: a null id, a coordinate outside ±90 / ±180 (NaN and infinity included), or a language code TimeZoneNames does not know.

TimeZone is a record:

  • IanaId — as looked up, in TZDB's casing; an alias is preserved.
  • CanonicalIanaId / IsAliasUS/EasternAmerica/New_York.
  • WindowsId — never null; zones without a mapping are excluded.
  • Name — localized and daylight-savings-aware, Eastern Daylight Time; never empty. Use it for display.
  • Abbreviation — the CLDR abbreviation for the current daylight-savings state, EDT; null when CLDR has none, never an IANA id.
  • UtcOffset — a TimeSpan.
  • IsDaylightSavings — from TZDB, normalized so that zones modelled with negative savings (Europe/Dublin, Africa/Casablanca) report summer as daylight savings.
  • At — the instant the snapshot was computed for, in UTC.
Data sources and currency
  • TZDB comes from NodaTime; ITimeZones.TzdbVersion reports the version in use. To pick up a tzdata release before a package update, download the matching .nzd from nodatime.org/tzdb and set TimeZonesOptions.DateTimeZoneProvider to a DateTimeZoneCache over TzdbDateTimeZoneSource.FromStream(...).
  • Windows mappings and localized names come from CLDR via TimeZoneConverter and TimeZoneNames. A zone with no Windows mapping (Antarctica/Troll is the only one today) is excluded from every result.
  • SQL Server's AT TIME ZONE and .NET's TimeZoneInfo read the Windows registry, which is updated by Windows Update on its own schedule, so they can disagree with TZDB briefly after a rule change. This library never reads the registry.
Sample output

The first ten rows of GetTimeZones() at 2026-07-15T12:00:00Z, with the default language, from TZDB: 2026c (mapping: 48.2):

IANA Id Windows Id Name Abbreviation Offset DST
Africa/Abidjan Greenwich Standard Time Greenwich Mean Time GMT +00:00 False
Africa/Accra Greenwich Standard Time Greenwich Mean Time GMT +00:00 False
Africa/Addis_Ababa E. Africa Standard Time East Africa Time EAT +03:00 False
Africa/Algiers W. Central Africa Standard Time Central European Standard Time CET +01:00 False
Africa/Asmara E. Africa Standard Time East Africa Time EAT +03:00 False
Africa/Asmera E. Africa Standard Time East Africa Time EAT +03:00 False
Africa/Bamako Greenwich Standard Time Greenwich Mean Time GMT +00:00 False
Africa/Bangui W. Central Africa Standard Time West Africa Standard Time WAT +01:00 False
Africa/Banjul Greenwich Standard Time Greenwich Mean Time GMT +00:00 False
Africa/Bissau Greenwich Standard Time Greenwich Mean Time GMT +00:00 False
Migrating from 3.x

4.0.0 is a breaking release. Every consumer changes; the table maps each 3.0.1 call to its 4.0.0 equivalent.

3.0.1 4.0.0
value.AtTimeZone(timeZone) (obsolete) timeZones.AtTimeZone(value, ianaId) — and the result is now correct across DST
GetTimeZoneByIanaId(id, at) + value.AtTimeZone(tz.UtcOffsetTs) timeZones.AtTimeZone(value, id)
tz.UtcOffsetTs tz.UtcOffset (TimeSpan)
tz.UtcOffset (string) tz.UtcOffset.ToString(@"hh\:mm") with a sign, or NodaTime OffsetPattern
GetTimeZoneByCoordinate(decimal, decimal) cast to double
GetTimeZones(Instant) GetTimeZones(instant.ToDateTimeOffset())
DateTimeOffset? at-time argument .Value (the parameter is non-nullable)
services.AddMemoryCache().AddTimeZones() services.AddTimeZones()AddMemoryCache() no longer required (harmless)
DateTime.AtTimeZone(...) removed; use DateTimeOffset or timeZones.AtTimeZone(dateTime, ianaId)
IEnumerable<TimeZone> results IReadOnlyList<TimeZone>
Abbreviation always a string string?null when CLDR has no abbreviation; use Name for display

Also: the public extension methods moved from namespace System to Arex388.TimeZones.TimeZonesExtensions, so a using Arex388.TimeZones; is needed where AtTimeZone(TimeSpan) is called, and a consumer's own System.DateTimeOffsetExtensions no longer collides with the library's.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.0.0 88 8/27/2026
3.0.1 248 10/19/2025
3.0.0 201 6/27/2025
2.0.8 191 6/27/2025
2.0.7 203 6/27/2025
2.0.6 258 2/19/2025
2.0.5 230 10/14/2024
2.0.4 246 9/13/2024
2.0.3 513 2/14/2024
2.0.2 257 2/12/2024
2.0.1 275 10/9/2023
2.0.0 295 9/20/2023
1.0.24 257 9/18/2023
1.0.23 391 3/31/2023
1.0.22 419 3/24/2023
1.0.21 439 2/14/2023
1.0.20 513 12/28/2022
1.0.19 546 10/31/2022
1.0.18 597 9/26/2022
1.0.17 615 9/7/2022
Loading failed