3

If I have some UTC time, how do I get in C# the corresponding local time in Poland? Or in France? Or in Equador? (I understand this doesn't make sense for big countries like Russia where many times are used)

In most the existing examples people use a TimeZoneInfo object:

var zone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time");

The problem is this isn't enough. Many countries have something like "summer time" or "daylight saving time", where they use a different time offset to UTC depending on the day of the year and even exact hour (in the day of time change).

For example, rather than knowing whether I need the Central Daylight Time or Central Standard Time option, I'll only know target time is for Chicago. I want to be able to have a single string that will let the platform decide for me which of those two offsets is correct right now.

Are there any C# built-in functions that deal with this, or do I need to handle all the calculations where summer time begins and ends according to the law of a chosen country myself?

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
RRM
  • 3,371
  • 7
  • 25
  • 40
  • You can use CovertTime, for example. Notice that it takes a Datetime argument: you need a date and a time to properly convert (so it can take into consideration dayligh savings etc.) – C. Gonzalez Jan 10 '20 at 17:34
  • You do not need to convert. If you have two PCs in different timezones they pass the time in UTC. Then based on the local settings of the PC. The time is diplayed in local time. If you changed the timezone of your PC and read the same time it will display different because when you convert the time to a string the timezone settings are applied. – jdweng Jan 10 '20 at 17:36
  • 1
    `I want to be able to pick America/Chicago and let it handle that for me.` a valid request, but the only way to do that is to use NodaTime instead of the built-in types. For starters, Windows doesn't use the IANA timezone names. NodaTime does, and even allows loading a new tzdb to replace the one it was compiled with – Panagiotis Kanavos Jan 10 '20 at 17:38
  • 1
    @jdweng What about on a web site, where you have UTC time on the server, you want to render in the user's preferred time, but you only know their City and Country? – Joel Coehoorn Jan 10 '20 at 17:39
  • Joel, you appear to have added the City/Country requirement to this question. I don't know if that was what the OP was really asking. Usually if you want to show things in the user's time zone, the rendering application needs to know about their time zone. For web applications, it's common to rely on the browser for this, which relies on the Operation System's settings. The server sends UTC time to the browser and lets the conversion happen there. Other applications may need the user to explicitly choose a preferred time zone from a list. – StriplingWarrior Jan 10 '20 at 17:48
  • The HTML Webpage is really running on you local machine. So same applies. HTML you browser is downloading HTML from server and running the html locally. – jdweng Jan 10 '20 at 17:49
  • 1
    @jdweng: That depends on your architecture. Web apps from a decade ago would render HTML for the browser to display, so they had to have a time zone tied to the user that the server could be aware of. More modern web apps tend to use JSON to transfer data, and have client-side JavaScript code that can render templates to produce the HTML-esque DOM. JavaScript has access to local time zone information configured on the user's machine. – StriplingWarrior Jan 10 '20 at 17:53
  • The older Apps were just doing it wrong. – jdweng Jan 10 '20 at 17:55
  • 1
    @jdweng not necessarily. Imagine an app where you're coordinating meetings across time zones, where you're in London but you have an attendee in Paris or Rome. Anyway, the whole premise of the question is the user wants to do this in C#, not javascript. – Joel Coehoorn Jan 10 '20 at 18:01
  • @StriplingWarrior Perhaps I overstepped slightly. I'm confident the OP wants to be able to say something like "I'm in Chicago, so tell me whether CST or CDT is in force", but maybe assuming "America/Chicago" as a correct time zone name is too much. I'll update. – Joel Coehoorn Jan 10 '20 at 18:07
  • Indeed, if one doesn't have an actual IANA time zone identifier like `"America/Chicago"`, but just has something like "Chicago, USA" or perhaps a city that isn't a tzdb reference point like Redmond, WA, then first one needs to figure out what time zone is applicable. A generic solution would need to use a reverse-geocode API or lookup table to get a lat/lon, then use that to look up the time zone ID with [one of these mechanisms](https://stackoverflow.com/q/16086962/634824), and *then* use `TimeZoneInfo.IsDaylightSavingTime` or similar. (Though some APIs will do the last step for you.) – Matt Johnson-Pint Jan 10 '20 at 19:42

3 Answers3

7

If I have some UTC time, how do I get in C# the corresponding local time in Poland? Or in France? Or in Equador?

On Windows:

var poland = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Central European Standard Time");
var france = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Romance Standard Time");
var ecuador1 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "SA Pacific Standard Time"); // (Mainland)
var ecuador2 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Central America Standard Time"); // (Galapagos Is.)

On Linux, OSX, and other .NET Core platforms:

var poland = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Europe/Warsaw");
var france = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Europe/Paris");
var ecuador1 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "America/Guayaquil"); // (Mainland)
var ecuador2 = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTimeOffset.UtcNow, "Pacific/Galapagos"); // (Galapagos Is.)

(Note that Ecuador has two time zones, one for the mainland, one for the Galapagos Islands.)

A good reference for figuring out which Windows ID to use is the CLDR windowsZones.xml file, which is the source of truth for converting between Windows and IANA time zone identifiers.

(I understand this doesn't make sense for big countries like Russia where many times are used)

Indeed, Russia has many different time zone identifiers. One would have to know what part of Russia they were interested in.

In most the existing examples people use a TimeZoneInfo object:

var zone = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time");

The problem is that it's not enough. Many countries have something like "summer time" or "daylight saving time", where they use a different time offset to UTC depending on the day of the year and even exact hour (in the day of time change).

For example, rather than knowing whether I need the Central Daylight Time or Central Standard Time option, I want to be able to pick America/Chicago and let it handle that for me.

No, that's not a problem. Likely you're confused about the requirement because the word "Standard" is in the name of the identifier. Despite this, a TimeZoneInfo object represents the entirety of the time zone, including both standard time and daylight/summer time.

To clarify, the TimeZoneInfo.FindSystemTimeZoneById method takes an identifier. The resulting object's Id property will then contain that identifier.

  • "America/Chicago" is an IANA time zone identifer.
  • "Central Standard Time" is both a Windows time zone identifier, and an English localized "standard name" of the same zone (on Windows).
  • "Central Daylight Time" is an English localized "daylight name" of that zone (on Windows). It is not a valid time zone identifier.

Are there any C# built-in functions that deal with this, or do I need to handle all the calculations where summer time begins and ends according to the law of a chosen country myself?

All of the built-in calculations handle this. Only pass valid identifiers, and the framework will determine the correct offset from UTC regardless of whether standard time or daylight/summer time is in effect.

Here is a working example that demonstrates these concepts:

TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time"); // Use "America/Chicago" on non-Windows systems

Console.WriteLine("Id:             " + tzi.Id);
Console.WriteLine("DisplayName:    " + tzi.DisplayName);
Console.WriteLine("StandardName:   " + tzi.StandardName);
Console.WriteLine("DaylightName:   " + tzi.DaylightName);
Console.WriteLine();

DateTime winterDate = new DateTime(2020, 1, 1);
DateTime summerDate = new DateTime(2020, 7, 1);

TimeSpan winterOffset = tzi.GetUtcOffset(winterDate);
TimeSpan summerOffset = tzi.GetUtcOffset(summerDate);

bool winterIsDst = tzi.IsDaylightSavingTime(winterDate);
bool summerIsDst = tzi.IsDaylightSavingTime(summerDate);

Console.WriteLine("Winter Offset:  " + winterOffset + "  IsDST:  " + winterIsDst);
Console.WriteLine("Summer Offset:  " + summerOffset + "  IsDST:  " + summerIsDst);

Output:

Id:             Central Standard Time
DisplayName:    (UTC-06:00) Central Time (US & Canada)
StandardName:   Central Standard Time
DaylightName:   Central Daylight Time

Winter Offset:  -06:00:00  IsDST:  False
Summer Offset:  -05:00:00  IsDST:  True

Lastly, if you want to be able to use either set of identifiers on any operating system, there are two different ways you can handle that:

  • You can retrieve a TimeZoneInfo with my TimeZoneConverter library. For example:

    // Either of these will work on any platform:
    TimeZoneInfo tzi = TZConvert.GetTimeZoneInfo("America/Chicago");
    TimeZoneInfo tzi = TZConvert.GetTimeZoneInfo("Central Standard Time");
    
  • You can use Noda Time instead of TimeZoneInfo, which has ZonedDateTimeProviders for both Bcl (Windows) and Tzdb (IANA) identifiers.

    // This will work on any platform:
    DateTimeZone dtz = DateTimeZoneProviders.Tzdb["America/Chicago"];
    
    // This will work with Windows on .NET Framework only
    DateTimeZone dtz = DateTimeZoneProviders.Bcl["Central Standard Time"];
    

You might also want to read the timezone tag wiki here on Stack Overflow. Especially the section titled "Time Zone Databases".

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • 1
    Russia also changes timezones frequently, which makes NodaTime and tzdb *far* more stable than trying to find out what timezone this or that airport is this year. Should they change with 2 weeks notice the way Egypt did some years ago, you just load the new tzdb into NodaTime – Panagiotis Kanavos Jan 10 '20 at 18:09
  • 1
    Indeed [short notice changes are problematic](https://codeofmatt.com/on-the-timing-of-time-zone-changes/) for both data sets. You're right that Noda Time gives the developer much more control about when to update. With `TimeZoneInfo`, the data comes from the underlying OS, and one isn't always in control of when those udpates are applied (ex. Azure App Service). – Matt Johnson-Pint Jan 10 '20 at 18:14
  • I want to applaud you for providing such a clear and complete answer. – StriplingWarrior Jan 10 '20 at 21:23
  • Thank you for a thorough anwer and also pointing to the Noda Time library. – RRM Jan 13 '20 at 12:53
3

Things like Daylight Savings time change based on the date. So if you use the time zone to convert a time, you have to provide the full DateTime that you're trying to convert, and .NET will handle figuring out whether DST is active and making the appropriate calculations.

The following example highlights this: the states of Arizona and Utah are in "Mountain Time" in the US, but Arizona doesn't observe Daylight Savings Time like most states in the US. So during the winter months, these two time zones have the same offset, but during summer months they have different offsets.

var arizona = TimeZoneInfo.FindSystemTimeZoneById("US Mountain Standard Time");
var utah = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time");
var time1 = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var time2 = new DateTime(2020, 7, 1, 0, 0, 0, DateTimeKind.Utc);
Console.WriteLine(TimeZoneInfo.ConvertTime(time1, arizona).ToString());
Console.WriteLine(TimeZoneInfo.ConvertTime(time1, utah).ToString());
Console.WriteLine(TimeZoneInfo.ConvertTime(time2, arizona).ToString());
Console.WriteLine(TimeZoneInfo.ConvertTime(time2, utah).ToString());

Output:

12/31/2019 5:00:00 PM
12/31/2019 5:00:00 PM
6/30/2020 5:00:00 PM
6/30/2020 6:00:00 PM

Note: Since I first looked at your question, Joel Coehoorn added the following:

For example, rather than knowing whether I need the Central Daylight Time or Central Standard Time option, I want to be able to pick America/Chicago and let it handle that for me.

I don't know if that was the intent of your question. If it was, that's another question entirely.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
1

Are there any C# built-in functions that deal with this?

Unfortunately, no.

Do I need to handle all the calculations where summer time begins and ends according to the law of a chosen country myself?

Thankfully, also no. There is a high-quality open source library you can use called NodaTime, and you can include it in your project via NuGet.

This does require, though, that you be able to find the correct IANA timezone name for each location (see the "Tz Database name" column in the table in the link).

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Can you add some source code to show how NodaTime will solve the problem? – StriplingWarrior Jan 10 '20 at 17:49
  • @StriplingWarrior I was gonna, but the project demonstrates almost this exact thing right on the front page. – Joel Coehoorn Jan 10 '20 at 17:49
  • I think StackOverflow encourages including relevant code samples and information directly in the answer, where practical. – StriplingWarrior Jan 10 '20 at 17:55
  • 3
    The first part of your answer is incorrect. The built-in `TimeZoneInfo` object correctly handles all DST calculations. The confusion is likely on the name of the identifier having the word "Standard" in it, despite it applying to the entire time zone. – Matt Johnson-Pint Jan 10 '20 at 18:01
  • @MattJohnson-Pint It applies to the whole zone, but it also implies a specific UTC offset that's not in force for the whole year. At the time you write the code you'll expect it to run at times of the year when that implied offset is wrong. – Joel Coehoorn Jan 10 '20 at 18:05
  • 1
    Nope. The offset will vary depending on which date is passed to each function from the same `TimeZoneInfo` object. See https://dotnetfiddle.net/rakVrI – Matt Johnson-Pint Jan 10 '20 at 18:10
  • @MattJohnson-Pint Now that sounds like information worth posting as answer. I feel like the answer you already have skips past this. – Joel Coehoorn Jan 10 '20 at 18:10
  • Updated my answer to include those details. Thanks. – Matt Johnson-Pint Jan 10 '20 at 19:35