C# TimeZoneInfo
last modified October 18, 2026
In this article we work with time zones in C# using TimeZoneInfo.
A time zone is a geographical region in which the same time is used. For instance, in Bratislava, Prague, or Budapest the Central European Time is used. A zone is more than a fixed offset from UTC: it has a name, a history of changes, and usually rules for daylight saving time (DST), when the offset changes during the year.
TimeZoneInfo is the main class for working with time zones in C#. It is
an improvement over the old, limited TimeZone class. It allows us to
enumerate the time zones available on the system, find a zone by its identifier,
convert times between zones, create custom zones, and serialize a zone for later
use.
The time zone data comes from the operating system. Windows has its own registry
with ids such as Central Europe Standard Time. Linux and macOS use the
IANA time zone database, with ids such as Europe/Bratislava. Since
.NET 6 on Windows, and .NET 8 on Linux and macOS, both kinds of ids are accepted
everywhere (Windows needs the ICU libraries, which are part of the system on
current versions). The examples were run on Linux, so the names and display texts
come from the IANA data and ICU; on Windows they differ.
tzdata package
has no time zone data; TimeZoneInfo.Local is then UTC, and finding any
other zone fails. Install tzdata in such images.
C# TimeZoneInfo.GetSystemTimeZones
The TimeZoneInfo.GetSystemTimeZones method returns a sorted collection
of all the time zones available on the local system.
using System.Collections.ObjectModel;
ReadOnlyCollection<TimeZoneInfo> zones = TimeZoneInfo.GetSystemTimeZones();
Console.WriteLine($"{zones.Count} time zones");
foreach (TimeZoneInfo zone in zones.Take(5))
{
Console.WriteLine($"{zone.Id,-24} {zone.DisplayName}");
}
Console.WriteLine("...");
// the zones with a UTC+01:00 base offset
var plusOne = zones.Where(z => z.BaseUtcOffset == TimeSpan.FromHours(1)).Select(z => z.Id).Take(5);
Console.WriteLine(string.Join(", ", plusOne));
The program counts the available time zones, prints a few of them, and filters the collection with LINQ.
$ dotnet run 419 time zones Pacific/Niue (UTC-11:00) Niue Time Pacific/Midway (UTC-11:00) Samoa Standard Time (Midway) Pacific/Pago_Pago (UTC-11:00) Samoa Standard Time (Pago Pago) Pacific/Rarotonga (UTC-10:00) Cook Islands Standard Time (Rarotonga) America/Adak (UTC-10:00) Hawaii-Aleutian Time (Adak) ... Africa/Algiers, Europe/Amsterdam, Europe/Andorra, Europe/Belgrade, Europe/Berlin
The collection is sorted by the base UTC offset and then by the display name, not by the id. The number of zones depends on the system.
C# TimeZoneInfo.Local
The TimeZoneInfo.Local property returns a TimeZoneInfo
object that represents the local time zone of the machine. TimeZoneInfo.Utc
is the UTC zone.
TimeZoneInfo local = TimeZoneInfo.Local; Console.WriteLine(local.DisplayName); Console.WriteLine(local.Id); Console.WriteLine(local.StandardName); Console.WriteLine(local.DaylightName); Console.WriteLine(local.BaseUtcOffset); Console.WriteLine(local.SupportsDaylightSavingTime); Console.WriteLine(TimeZoneInfo.Utc.Id);
In the program, we print several properties of the local time zone.
Console.WriteLine(local.DisplayName); Console.WriteLine(local.Id);
The DisplayName is a text for the user interface, the
Id identifies the zone in code.
Console.WriteLine(local.StandardName); Console.WriteLine(local.DaylightName); Console.WriteLine(local.BaseUtcOffset);
The StandardName and DaylightName are the names of the
zone in winter and summer. The BaseUtcOffset is the offset from UTC
without DST, so the actual offset in summer is one hour more.
$ dotnet run (UTC+01:00) Central European Time (Bratislava) Europe/Bratislava Central European Standard Time Central European Summer Time 01:00:00 True UTC
On Windows, the same machine would report
(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague and
Central Europe Standard Time.
C# TimeZoneInfo.FindSystemTimeZoneById
The TimeZoneInfo.FindSystemTimeZoneById method retrieves a
TimeZoneInfo object based on the given identifier. It throws a
TimeZoneNotFoundException when the zone does not exist, so for ids
that come from users or configuration, the TryFindSystemTimeZoneById
method is the better choice.
// IANA ids work everywhere with ICU; Windows ids are accepted on Windows and, since .NET 8, elsewhere too
foreach (var id in new[] { "Europe/Moscow", "Russian Standard Time", "Mars/Olympus" })
{
if (TimeZoneInfo.TryFindSystemTimeZoneById(id, out TimeZoneInfo? tzi))
{
Console.WriteLine($"{tzi.Id}: {tzi.DisplayName} | {tzi.StandardName} | DST: {tzi.SupportsDaylightSavingTime}");
}
else
{
Console.WriteLine($"{id}: not found");
}
}
try
{
TimeZoneInfo.FindSystemTimeZoneById("Mars/Olympus");
}
catch (TimeZoneNotFoundException e)
{
Console.WriteLine("TimeZoneNotFoundException: " + e.Message);
}
// converting ids
Console.WriteLine(TimeZoneInfo.TryConvertIanaIdToWindowsId("Europe/Bratislava", out var win) ? win : "?");
Console.WriteLine(TimeZoneInfo.TryConvertWindowsIdToIanaId("Central Europe Standard Time", out var iana) ? iana : "?");
Console.WriteLine(TimeZoneInfo.TryConvertWindowsIdToIanaId("Central Europe Standard Time", "SK", out var sk) ? sk : "?");
In the program, we look up a zone by its IANA id and by its Windows id, and try an id that does not exist. Finally, we convert between the two kinds of ids.
foreach (var id in new[] { "Europe/Moscow", "Russian Standard Time", "Mars/Olympus" })
Both ids of the Moscow zone lead to the same zone. The returned
Id is the one that was asked for.
Console.WriteLine(TimeZoneInfo.TryConvertWindowsIdToIanaId("Central Europe Standard Time", "SK", out var sk) ? sk : "?");
One Windows zone maps to several IANA zones. Without a region, the conversion
returns the representative zone (Europe/Budapest); with a two-letter
region code, it returns the zone for the country.
$ dotnet run Europe/Moscow: (UTC+03:00) Moscow Time | Moscow Standard Time | DST: True Russian Standard Time: (UTC+03:00) Moscow Time | Moscow Standard Time | DST: True Mars/Olympus: not found TimeZoneNotFoundException: The time zone ID 'Mars/Olympus' was not found on the local computer. Central Europe Standard Time Europe/Budapest Europe/Bratislava
Note DST: True for Moscow, although Russia has not used DST since
2014. The next section explains it.
C# TimeZoneInfo.ConvertTime
The TimeZoneInfo.ConvertTime method converts a time to the time in the
given time zone. There are variants for special cases:
ConvertTimeFromUtc, ConvertTimeToUtc, and
ConvertTimeBySystemTimeZoneId.
// a fixed instant makes the output reproducible
var utc = new DateTime(2026, 10, 18, 12, 0, 0, DateTimeKind.Utc);
TimeZoneInfo moscow = TimeZoneInfo.FindSystemTimeZoneById("Europe/Moscow");
TimeZoneInfo local = TimeZoneInfo.Local;
DateTime inLocal = TimeZoneInfo.ConvertTimeFromUtc(utc, local);
DateTime inMoscow = TimeZoneInfo.ConvertTimeFromUtc(utc, moscow);
Console.WriteLine($"UTC: {utc:yyyy-MM-dd HH:mm}");
Console.WriteLine($"Local: {inLocal:yyyy-MM-dd HH:mm} ({local.Id})");
Console.WriteLine($"Moscow: {inMoscow:yyyy-MM-dd HH:mm}");
// from one zone to another
DateTime back = TimeZoneInfo.ConvertTime(inMoscow, moscow, local);
Console.WriteLine($"Moscow to local: {back:yyyy-MM-dd HH:mm}");
// to UTC
DateTime toUtc = TimeZoneInfo.ConvertTimeToUtc(inMoscow, moscow);
Console.WriteLine($"Moscow to UTC: {toUtc:yyyy-MM-dd HH:mm} {toUtc.Kind}");
// by id, without creating TimeZoneInfo objects first
DateTime tokyo = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(utc, "Asia/Tokyo");
Console.WriteLine($"Tokyo: {tokyo:yyyy-MM-dd HH:mm}");
// DateTimeOffset keeps the offset
DateTimeOffset dto = TimeZoneInfo.ConvertTime(new DateTimeOffset(utc), moscow);
Console.WriteLine(dto);
We take a fixed instant in UTC and show it in several zones. A fixed instant makes the output reproducible.
DateTime inMoscow = TimeZoneInfo.ConvertTimeFromUtc(utc, moscow);
ConvertTimeFromUtc requires a source that is UTC (its
Kind must not be Local). The result has the
Unspecified kind, as it is the wall-clock time of the zone.
DateTime back = TimeZoneInfo.ConvertTime(inMoscow, moscow, local);
The three-argument ConvertTime converts from the source zone to the
destination zone. We have to name the source zone, since a bare
DateTime does not know in which zone it was created. The two-argument
overload assumes the source is the local zone when the kind is Local or
Unspecified, and UTC when the kind is Utc.
DateTimeOffset dto = TimeZoneInfo.ConvertTime(new DateTimeOffset(utc), moscow);
With a DateTimeOffset, the result carries the proper offset (+03:00),
which makes it the safest type for passing around a converted time.
$ dotnet run UTC: 2026-10-18 12:00 Local: 2026-10-18 14:00 (Europe/Bratislava) Moscow: 2026-10-18 15:00 Moscow to local: 2026-10-18 14:00 Moscow to UTC: 2026-10-18 12:00 Utc Tokyo: 2026-10-18 21:00 10/18/2026 3:00:00 PM +03:00
C# TimeZoneInfo and daylight saving time
The offset of a zone depends on the date. GetUtcOffset returns the real
offset at a given time, and IsDaylightSavingTime tells whether DST is in
effect.
var zone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Bratislava");
var winter = new DateTime(2026, 1, 15, 12, 0, 0);
var summer = new DateTime(2026, 7, 15, 12, 0, 0);
Console.WriteLine($"base offset: {zone.BaseUtcOffset}");
Console.WriteLine($"winter offset: {zone.GetUtcOffset(winter)} DST: {zone.IsDaylightSavingTime(winter)}");
Console.WriteLine($"summer offset: {zone.GetUtcOffset(summer)} DST: {zone.IsDaylightSavingTime(summer)}");
// the length of a local day around the changes
var zone2 = TimeZoneInfo.FindSystemTimeZoneById("Europe/Bratislava");
foreach (var day in new[] { new DateTime(2026, 3, 29), new DateTime(2026, 6, 1), new DateTime(2026, 10, 25) })
{
DateTime startUtc = TimeZoneInfo.ConvertTimeToUtc(day, zone2);
DateTime nextUtc = TimeZoneInfo.ConvertTimeToUtc(day.AddDays(1), zone2);
Console.WriteLine($"{day:yyyy-MM-dd} lasts {(nextUtc - startUtc).TotalHours} hours");
}
The program shows the offsets of Bratislava in winter and in summer, and the length of three days around the clock changes.
Console.WriteLine($"base offset: {zone.BaseUtcOffset}");
Console.WriteLine($"winter offset: {zone.GetUtcOffset(winter)} DST: {zone.IsDaylightSavingTime(winter)}");
Console.WriteLine($"summer offset: {zone.GetUtcOffset(summer)} DST: {zone.IsDaylightSavingTime(summer)}");
The base offset is always the standard one. In summer, the actual offset is two hours.
DateTime startUtc = TimeZoneInfo.ConvertTimeToUtc(day, zone2); DateTime nextUtc = TimeZoneInfo.ConvertTimeToUtc(day.AddDays(1), zone2);
To measure a local day, we convert its start and the start of the next day to UTC and subtract. Local days are not always 24 hours long: the day when the clocks go forward has 23 hours and the day when they go back has 25.
$ dotnet run base offset: 01:00:00 winter offset: 01:00:00 DST: False summer offset: 02:00:00 DST: True 2026-03-29 lasts 23 hours 2026-06-01 lasts 24 hours 2026-10-25 lasts 25 hours
C# find the DST transitions
The GetAdjustmentRules method describes the DST rules, but their form
depends on the platform and is awkward to use. A robust way to find the moments
when the clocks change is to look for changes of the offset. The
SupportsDaylightSavingTime property says whether a zone has ever had
DST rules, not whether it uses them now.
// find the moments when the clocks change in a year, by scanning the offsets hour by hour
var zone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var t = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var end = new DateTime(2027, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var previous = zone.GetUtcOffset(t);
for (; t < end; t = t.AddHours(1))
{
var offset = zone.GetUtcOffset(t);
if (offset != previous)
{
Console.WriteLine($"{t:yyyy-MM-dd HH:mm} UTC: offset {previous} -> {offset}");
previous = offset;
}
}
// SupportsDaylightSavingTime is true for any zone that ever had DST rules, even if it does not use them now,
// so ask about a concrete date instead
var summer = new DateTime(2026, 7, 1, 12, 0, 0, DateTimeKind.Utc);
var winter = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
foreach (var id in new[] { "Asia/Tokyo", "Asia/Kolkata", "America/Phoenix", "Europe/Berlin", "Australia/Sydney", "UTC" })
{
var tz = TimeZoneInfo.FindSystemTimeZoneById(id);
bool usesDst = tz.GetUtcOffset(summer) != tz.GetUtcOffset(winter);
Console.WriteLine($"{id,-17} uses DST in 2026: {usesDst,-5} Supports: {tz.SupportsDaylightSavingTime}");
}
The program scans the year 2026 hour by hour for New York, and then checks which zones really change their offset.
for (; t < end; t = t.AddHours(1))
{
var offset = zone.GetUtcOffset(t);
if (offset != previous)
{
We ask for the offset at each hour of the year (8,760 calls, which is fast) and print a line whenever it differs from the previous one. The loop reports the two transitions of the United States: on March 8 the clocks go forward, on November 1 they go back. The times are in UTC.
bool usesDst = tz.GetUtcOffset(summer) != tz.GetUtcOffset(winter);
Comparing the offset in July and in January is a simple test for the use of DST.
Tokyo, Kolkata, and Phoenix report Supports: True, because they had
DST rules at some point in the past, but they do not use DST in 2026. The southern
hemisphere zones, such as Sydney, have DST in the other half of the year, but the
offsets of July and January still differ.
$ dotnet run 2026-03-08 07:00 UTC: offset -05:00:00 -> -04:00:00 2026-11-01 06:00 UTC: offset -04:00:00 -> -05:00:00 Asia/Tokyo uses DST in 2026: False Supports: True Asia/Kolkata uses DST in 2026: False Supports: True America/Phoenix uses DST in 2026: False Supports: True Europe/Berlin uses DST in 2026: True Supports: True Australia/Sydney uses DST in 2026: True Supports: True UTC uses DST in 2026: False Supports: False
C# meeting planner
A common task is to show a meeting in the local times of the participants. We store the meeting as a time in the organizer's zone, convert it to UTC, and then to each participant's zone.
// a meeting at 15:00 in Bratislava: what time is it for the others?
var zone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Bratislava");
var meeting = new DateTime(2026, 10, 27, 15, 0, 0); // wall-clock time in Bratislava
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(meeting, zone);
Console.WriteLine($"UTC: {utc:yyyy-MM-dd HH:mm}");
string[] ids = ["Europe/London", "America/New_York", "America/Los_Angeles", "Asia/Kolkata", "Asia/Tokyo", "Australia/Sydney"];
foreach (var id in ids)
{
var other = TimeZoneInfo.FindSystemTimeZoneById(id);
DateTime time = TimeZoneInfo.ConvertTimeFromUtc(utc, other);
string day = time.Date == meeting.Date ? "" : time.Date > meeting.Date ? " (next day)" : " (previous day)";
Console.WriteLine($"{id,-22} {time:HH:mm}{day} UTC{other.GetUtcOffset(utc):hh\\:mm}");
}
// during the weeks when the US and Europe change their clocks on different dates,
// the difference between the two is not constant
foreach (var date in new[] { new DateTime(2026, 3, 20, 12, 0, 0), new DateTime(2026, 3, 30, 12, 0, 0), new DateTime(2026, 11, 2, 12, 0, 0) })
{
var ny = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var diff = zone.GetUtcOffset(date) - ny.GetUtcOffset(date);
Console.WriteLine($"{date:yyyy-MM-dd}: Bratislava is {diff.TotalHours} hours ahead of New York");
}
The meeting is on October 27, 2026, at 15:00 in Bratislava.
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(meeting, zone);
The meeting time is converted to UTC, with the rules of the Bratislava zone for that date. A date in the future depends on the rules that are valid today; if a government changes the rules, a stored UTC time for a future meeting would give the wrong local time. For future events, it is better to store the local time and the zone id and convert when needed.
foreach (var date in new[] { ... })
{
var ny = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
var diff = zone.GetUtcOffset(date) - ny.GetUtcOffset(date);
The last loop shows that the difference between two zones is not constant. The United States change the clocks on the second Sunday of March and the first Sunday of November, while Europe changes on the last Sundays of March and October. In the weeks between, the gap between Bratislava and New York is five hours instead of six.
$ dotnet run UTC: 2026-10-27 14:00 Europe/London 14:00 UTC00:00 America/New_York 10:00 UTC04:00 America/Los_Angeles 07:00 UTC07:00 Asia/Kolkata 19:30 UTC05:30 Asia/Tokyo 23:00 UTC09:00 Australia/Sydney 01:00 (next day) UTC11:00 2026-03-20: Bratislava is 5 hours ahead of New York 2026-03-30: Bratislava is 6 hours ahead of New York 2026-11-02: Bratislava is 6 hours ahead of New York
C# invalid and ambiguous times
When the clocks go forward, some local times do not exist. When they go back, some
local times happen twice. TimeZoneInfo can detect both cases.
// the clocks go back in Central Europe on 2026-10-25 at 03:00 local time
var zone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Bratislava");
foreach (var time in new[]
{
new DateTime(2026, 3, 29, 2, 30, 0), // does not exist
new DateTime(2026, 10, 25, 2, 30, 0), // exists twice
new DateTime(2026, 6, 1, 2, 30, 0) // normal
})
{
Console.Write($"{time:yyyy-MM-dd HH:mm}: ");
if (zone.IsInvalidTime(time))
{
Console.WriteLine("invalid (skipped by the clocks)");
}
else if (zone.IsAmbiguousTime(time))
{
var offsets = zone.GetAmbiguousTimeOffsets(time);
Console.WriteLine("ambiguous: " + string.Join(" or ", offsets.Select(o => $"UTC{o:hh\\:mm}")));
}
else
{
Console.WriteLine($"fine, offset {zone.GetUtcOffset(time)}");
}
}
// converting an invalid time throws
try
{
TimeZoneInfo.ConvertTimeToUtc(new DateTime(2026, 3, 29, 2, 30, 0), zone);
}
catch (ArgumentException e)
{
Console.WriteLine("ArgumentException: " + e.Message);
}
The program classifies three local times in Bratislava.
else if (zone.IsAmbiguousTime(time))
{
var offsets = zone.GetAmbiguousTimeOffsets(time);
For an ambiguous time, GetAmbiguousTimeOffsets returns the possible
offsets. An application must decide which one is meant, or ask the user. When the
choice cannot be made, ConvertTimeToUtc assumes the standard time for
an ambiguous time.
TimeZoneInfo.ConvertTimeToUtc(new DateTime(2026, 3, 29, 2, 30, 0), zone);
Converting an invalid time throws an ArgumentException, so input from
users should be checked with IsInvalidTime first.
$ dotnet run 2026-03-29 02:30: invalid (skipped by the clocks) 2026-10-25 02:30: ambiguous: UTC01:00 or UTC02:00 2026-06-01 02:30: fine, offset 02:00:00 ArgumentException: The supplied DateTime represents an invalid time. For example, when the clock is adjusted forward, any time in the period that is skipped is invalid. (Parameter 'dateTime')
C# custom time zones and serialization
We can create our own zone with CreateCustomTimeZone, for example for a
fixed offset that is not available on the system. A zone can be serialized to a
string and restored, which is useful for storing it with the data.
// a custom zone: a fixed offset with a name
TimeZoneInfo custom = TimeZoneInfo.CreateCustomTimeZone(
id: "Lab Time", baseUtcOffset: TimeSpan.FromHours(5.5), displayName: "(UTC+05:30) Lab Time", standardDisplayName: "Lab Standard Time");
var utc = new DateTime(2026, 10, 18, 12, 0, 0, DateTimeKind.Utc);
Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(utc, custom));
Console.WriteLine(custom.DisplayName);
// serialization of a zone for storage or transfer
string serialized = custom.ToSerializedString();
Console.WriteLine(serialized);
TimeZoneInfo restored = TimeZoneInfo.FromSerializedString(serialized);
Console.WriteLine(restored.Equals(custom));
// comparing zones
var a = TimeZoneInfo.FindSystemTimeZoneById("Europe/Bratislava");
var b = TimeZoneInfo.FindSystemTimeZoneById("Europe/Prague");
Console.WriteLine(a.HasSameRules(b));
Console.WriteLine(a.Equals(b));
The program creates a zone with the offset UTC+05:30, serializes it, and compares two zones.
Console.WriteLine(a.HasSameRules(b)); Console.WriteLine(a.Equals(b));
The Bratislava and Prague zones have the same rules, so HasSameRules is
true, but they are different zones with different ids, so Equals is
false. For most conversions, either zone gives the same result.
$ dotnet run 10/18/2026 5:30:00 PM (UTC+05:30) Lab Time Lab Time;330;(UTC+05:30) Lab Time;Lab Standard Time;Lab Standard Time;; True True False
Source
In this article we have worked with time zones in C# using
TimeZoneInfo.
Author
List all C# tutorials.