Handling timezones with daylight saving in C# - c#

We have an application related to aviation, and specifically flights.
Times have to be stored in local, so I chose to use UTC Time + an offset, but then now I realize it is a bad choice:
By storing the timezone as an offset, I lose track of the original timezone and this has implications when dealing with daylight savings.
For example I can store a time in Alpine, UT as UTC time and a -6 offset and also a time in Phoenix, AZ as UTC time and a -6 offset.
But when daylight saving comes, the time will change in Alpine, but not in Phoenix.
So, I need to store the proper timezone and I have seen that there are also different lists with a different syntax, so I am assuming there are different standards.
In C#, what would be the best option to store a local time with the local time zone to make it work with daylight saving changes?

From the discussion in the question's comments, I understand that you are working with flight time schedules - that is, the time a future flight is intended to depart. This is indeed a case where the local time is more important than the UTC time.
Since you have the local time and location of departure (ex: 5:00 PM in Salt Lake City), then you should be storing in your database of scheduled departure times two values:
17:00 - The relevant local time of the departure
SLC - The location where the time is relevant
If this is a specific occurrence of this flight, then you should store the date as well:
2018-06-01T17:00 - The specific relevant local time of the departure
SLC - The location where the local time is relevent
These are the details that are contextually relevant to your business use case. Do not lose sight of them by converting them to UTC.
That said, you might consider storing them as a DateTimeOffset (2018-06-01T17:00-06:00), which makes converting to UTC trivial for a given instance. However there are two problems with this approach:
It cannot work with recurrences, because the offset may change.
Even for a single instance, the offset might change - if the government controlling the time zone decides to change their standard offset or daylight saving time rules before that occurrence takes effect. If you do take a DateTimeOffset approach, or a UTC-based approach, you must be prepared to recalculate future events in the face of such changes. (For more on this, see my blog articles: On the Timing of Time Zone Changes and Time Zone Chaos Inevitable in Egypt. There are countless other examples.)
With regards to the location - because you are working with data that is contextually applicable to the airline industry, I recommend using IATA airport codes like the SLC that I showed above. In other contexts, one might store an IANA time zones identifier like America/Denver, or a Windows time zone identifier like Mountain Standard Time.
You may find my "Airport Time Zones" gist (code and output table) useful for working with IATA airport codes. You'll have to decide how that data will flow through your system. If you are running on Windows and want to use the TimeZoneInfo class to convert times to different time zones, then use the Windows time zone IDs shown there. If you want to use the IANA time zone IDs instead, consider using Noda Time, or you can use my TimeZoneConverter library. There are several different options here, so explore them all carefully and pick ones that make sense to you.
Noda Time would be a great choice, IMHO. Not only would you get excellent time zone support, but you'd also be able to use types like LocalTime or LocalDateTime which align well with the scenarios described.

As I wrote in my comment, do not store local dates. Instead, store datetime values as UTC, and convert to local datetime when you need to display it.
You can use the ConvertTimeFromUtc method of the TimeZoneInfo class for that.
This means you will have to also keep a list of locations and whatever TimeZoneInfo they are associated in - For example,
Jerusalem would be associated with Israel Standard Time,
Rome with W. Europe Standard Time,
Hawaii with Hawaiian Standard Time
and so on. (I'll bet you can find such a list online somewhere.)
Please note that the ConvertTimeFromUtc method handles the daylight savings problem for you as well.
Then you can do something like this to get the local time by location:
DateTime GetLocalDateByCityName(DateTime utc, string cityName)
{
var timeZoneInfoId = GetTimeZoneInfoIdByCityName(string cityName);
return TimeZoneInfo.ConvertTimeFromUtc(utc, TimeZoneInfo.FindSystemTimeZoneById(timeZoneInfoId);
}
And of course, in GetTimeZoneInfoIdByCityName you get the TimeZoneInfoId for the specific city.

You should use TimeZoneInfo.ConvertTime to convert DateTime between time zones if you need to handle Daylight Savings.
TimeZoneInfo.ConvertTime(myDateTime, timeZone);
TimeZoneInfo.ConvertTime(myDateTime, fromTimeZone, toTimeZone);

Related

Convert UTC to different time zones without day light savings

Currently I have an UTC date which I have to convert to different time zone based on where my client time zone is.
This is the code am currently using:
var timezoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timezone);
var clientTime = TimeZoneInfo.ConvertTimeFromUtc(createDateTime, timezoneInfo);
I know this automatically uses the daylight saving time and provide me the current time based on time zone.
I have a boolean which determines whether is daylight saving time is enabled or not. The above method doesn't work well based on my boolean value.
Is there a inbuilt method or code that converts UTC to different time zone without daylight saving time?
It is generally a bad idea to provide a boolean daylight saving time flag in your application at all. Time zones are all about the actual local time in a particular area, and that always includes whether the government that controls that time zone has decided if daylight saving time applies or not. In other words - it's not up to the user, it's up to their government. Microsoft and the IANA time zone community go out of their way to ensure the time zones accurately reflect those governmental decisions.
If you really must do this, you could consider either using the TimeZoneInfo.BaseUtcOffset property, or you could inspect the result of TimeZoneInfo.IsDaylightSavingTime to decide whether to subtract time from the result. However, both approaches suffer from edge cases that will give you errors in cases where the standard time offset has changed unrelated to daylight saving time. Additionally, not all time zones shift by a full hour for DST, there's one that shifts by 30 minutes.
Ultimately, my recommendation would be to keep your code as-is and not try to use that boolean flag. Instead, go back to the UX or wherever that flag originates and remove it.

Postgres timestamp and time zone for raw UTC values

I'm hesitant to revisit this overly discussed topic, but I created a set of tables to store data and many of the tables included a field named "createdate" designated as "timestamp without time zone". The date/time values fed to these by the code base are always in UTC. It is intended that the UI will be able to control how the data is presented to the user, so some setting would dictate to convert to time zone in the UI.
Some of these timestamps will be for reports, so to display info for the end user. Other times, the values will be used to determine nightly jobs run against the data.
This is a typical is a multi-tenant cloud hosted system with clients across different time zones.
The server should never be moved, although I suppose changing hosting zones is a very remote possibility.
It was written on the .net platform. Not using noda time, just the built in DateTime stuff (for now).
The docs are pretty clear how the timestamp with time zone stores the info:
https://www.postgresql.org/docs/current/datatype-datetime.html
This answer also has good background on the difference in the two main timestamp data types:
https://stackoverflow.com/a/14616640/1905693
This answer also has some good info, but is Java-oriented:
What is the most recommended way to store time in PostgreSQL using Java?
And Josh Berkus has a dated article that was helpful:
https://it.toolbox.com/blogs/josh-berkus/zone-of-misunderstanding-092811
It seems most of these recommend the timestamp with time zone, but in my case, is timestamp without time zone appropriate?
If I did want to rely on pg to do the conversion, would the AT TIME ZONE clause be ok?
From an overall system architecture, is relying on the UI to change presentation a common and reasonable approach? (yeah, this one may be too opinion-flavored for SO's format)
Not a moment
As others said, TIMESTAMP WITHOUT TIME ZONE is the wrong data type.
That type holds only a date with time-of-day, for example noon on January 21st of 2021. But we cannot know if that means noon in Tokyo Japan, noon in Toulouse France, or noon in Toledo Ohio US — three very different moments, several hours apart from one another. So this type cannot represent a moment, is not a specific moment on the timeline.
The TIMESTAMP WITHOUT TIME ZONE type is good for three kinds of use-cases:
Representing multiple moments all known the same in their locality. For example, Acme Corp orders the manager at each factory in Delhi, Düsseldorf, and Detroit, to make an announcement in two days at their local time of noon.
Representing a date and time-of-day where the intended time zone is unknown. I consider this faulty data that should be rejected. But if you insist on writing it to the database, this type would be appropriate.
Booking future appointments where we want to keep the time-of-day even if those pesky politicians change the offset of the time zone(s) in their jurisdiction. These political changes happen surprisingly often. So book an appointment using two columns: TIMESTAMP WITHOUT TIME ZONE in one, and the name of the intended time zone in another. Time zones are named with Continent/Region format such as Africa/Tunis. At runtime, when you need a moment for calendaring, apply the time zone to the date and time to dynamically determine a moment according to the now-current time zone rules. In Noda Time, you would retrieve a LocalDateTime and time zone, to produce a ZonedDateTime for calendaring.
Moments
When you care about moments, specific points on the timeline, use TIMESTAMP WITH TIME ZONE.
In Postgres and many other databases, this type has a bit of a misnomer. The time zone is not actually saved with the date and time. Instead, upon submission to the database, any indicator of time zone or offset-from-UTC is used to adjust to UTC (an offset of zero hours-minutes-seconds). That UTC value is what Postgres writes to the database. And UTC is what Postgres always retrieves from the database.
Beware: Some middleware and tooling has the well-intentioned but very confusing anti-feature of dynamically applying a default time zone to the retrieved value, adjusting from UTC to that time zone. This creates the illusion of that time zone having been saved with the data. But, no, not so. A TIMESTAMP WITH TIME ZONE column stores only UTC values.
Use case examples:
Tracking the moment when a database record was created, modified, or deleted.
Logging for debugging or sysadmin work.
Tracking when a critical contract is signed or comes into effect.
Like you have seen repeatedly in the sources you quote, timestamp with time zone (timestamptz) is the more appropriate choice in all cases where different time zones may be involved.
Internally, storage is the same. But with timestamptz, Postgres already knows where timestamp value applies, while it is just oblivious in this regard with timestamp without time zone (timestamp).
Note that the given time zone is not stored. That's a common misunderstanding. It's only used to compute UTC time. See:
Time zone storage in data type "timestamp with time zone"
AT TIME ZONE is the SQL construct to use to get the corresponding time for a given time zone.
SELECT my_timestamptz AT TIME ZONE 'US/Hawaii';
With timestamp it gets awkward:
SELECT my_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'US/Hawaii';
Also, when using timestamp any appended time zone information in literals is ignored, which is typically not what you want.
See:
Ignoring time zones altogether in Rails and PostgreSQL
I usually set the timezone of the role I am using?
ALTER ROLE my_db_user IN DATABASE my_database
SET "TimeZone" TO 'UTC';
Seems to help getting the DateTime correctly.

Get local DateTime in Denmark from server in Ireland

I have a solution in Azure in Ireland. I live in Denmark.
Would it be correct, to save all my times and dates as universal = DateTime.Now.ToUniversalTime() in my database in Ireland?
When I need the danish time, I cannot: universal.ToLocalTime() because I’m still on the server in Ireland. Instead I could write:
danish = TimeZoneInfo.ConvertTimeFromUtc(universal, TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time"));
But I think it’s a long way.
Alternative 1: I convert date to danish, before I save in my database in Ireland.
Alternative 2: There are one timezone between Denmark and Ireland, so I could: danish = dateTime.AddHours(1), but perhaps it could give errors with the summer and winther time.
A few things:
There's nothing wrong with writing DateTime.Now.ToUniversalTime(), but understand that under the hood that is fetching the current UTC time, converting to local time, then converting back to UTC. Thus, it's both shorter and more efficient to simply call DateTime.UtcNow to get the UTC time directly without conversions.
The server on which your code is running is irrelevant (or should be). Additionally, all Azure instances have their local time zone set to UTC anyway, so you will not see any effects from Ireland's local time zone.
Yes, "Romance Standard Time" is the correct Windows identifier for the local time in Denmark. If you are running on a non-Windows platform, you should use "Europe/Copenhagen" instead. If you are writing for multi-platform usage, then use "Europe/Copenhagen" with my TimeZoneConverter library.
If you just wanted to write shorter code, you might be able to do the following:
DateTime danish = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(universal, "Romance Standard Time");
I say might, because this will only be correct if the DateTime you've got in your universal variable has its Kind property set to DateTimeKind.Utc. If you've set that previously using DateTime.SpecifyKind, then this will work. On the other hand, if you have just retrieved this value from a database, then by default the Kind will by DateTimeKind.Unspecified. The key difference being that ConvertTimeFromUtc treats DateTimeKind.Unspecified as if it were DateTimeKind.Utc, while ConvertTimeBySystemTimeZoneId treats DateTimeKind.Unspecified as if it were DateTimeKind.Local.
In other words, if you don't set the Kind explicitly, it would convert from local time to Danish time. However, since Azure runs its local time zone as UTC, you'd get the same result. Just you might get different results when running elsewhere.
You might consider using DateTimeOffset instead of DateTime. This usually results in clearer code and less errors. (DateTimeKind is not used with DateTimeOffset.) Of course it depends on what exactly you're using this data for, but often it's a good idea.
About your proposed alternatives, I suggest neither. Your original code is better. My thoughts on each:
Alternative 1: I convert date to danish, before I save in my database in Ireland.
It depends on what you're representing.
If this is a unique point in time (the time something happened, usually in the past) - then you should keep it as UTC. That allows conversion to any time zone correctly.
On the other hand, if you are representing the time something is scheduled to happen (in the future), then most often it's the local time that matters most. This is especially important for recurring events in time zones with DST, or events in time zones that might be volatile (where the government makes frequent short-notice changes).
Alternative 2: There are one timezone between Denmark and Ireland, so I could: danish = dateTime.AddHours(1), but perhaps it could give errors with the summer and winter time.
You should not add or subtract time to adjust time zones. After all - you're not talking about a different point in time an hour in the future or an hour in the past.
Also, Denmark and Ireland may be similar today, but that doesn't mean they have always been that way, and it doesn't mean they will necessarily stay that way in the future. Ireland has a different time zone identifiers ("GMT Standard Time" on Windows, "Europe/Dublin" on other platforms).
Use server Utc time to save record in database in order to avoid any time differences when converting datetime from and then converting it back. Once the record use Utc datetime then you can easily convert it to any time zone, so for example: system requires to convert datetime to Denmark local time, and in future it might need to show in Germany datetime
DateTime timeUtc = DateTime.UtcNow;
TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);

Daylight Saving Time not working in TimeZoneInfo, when converting from UTC to Local

I'm working on an ASP.NET/C# application, which supports time zones.
First let me explain the flow of the application, I'm storing an object for purchase order. So it has the datetime field in it.
I'm storing that datetime as UTC in Database, and binding it in grid (according to the client's timezone).
In the Page_Init method of the first page, I had used a javascript code which will detect the client's timezone, and return appropriate offset.
In the Page_Load method, I'm getting that javascript return value (offset) and comparing it with the offset of each zone in TimeZoneInfo.GetSystemTimeZones().
When comparing the offset, I'm getting a TimeZoneInfo object (for example) "(UTC-08:00) Baja California", "(UTC +05:30) Chennai,Kolkata"
Using that particular TimeZoneInfo object, I'm converting the UTC datetime (which is stored in DB) to the client's timezone.
As by the above flow, the application works fine for some timezones.
The problem is, if when I change timezone of client machine to (UTC -8:00), the client machine shows the timezone name as "(UTC-08:00) Pacific Time (US & Canada)" but in application the timezone differs from the client system it shows as "(UTC-08:00) Baja California".
And importantly DST changes are not reflecting when I convert the UTC to Local. TimeZoneInfo.ConvertTimeFromUtc(DateTime, clientTimezone);
Note: I'm not storing Timezone information of client in database or anywhere. So everytime if a user enters the application, the application will recognize the timezone and it will react according to it.
My question is:
Whether the TimeZoneInfo class can work automatically work according to the adjusment rule, when we convert from UTC to Local?
Do we have to detect the DST for particular datetime using the method TimeZoneInfoObject.IsDaylightSavingTime(DateTime) and do conversion?
Is there any other classes in .Net which can sync with windows timezones?
A few things you should understand:
A time zone is not the same as a time zone offset. One cannot just take the number -8 and assume that the time zone should be Pacific time.
Offsets can change within a single time zone. For example, Pacific Time usually uses -8, but switches to -7 when daylight saving time is in effect.
The offsets in the DisplayName property of a TimeZoneInfo are only the standard offset. They match with the BaseOffset property. They do not change to reflect the current offset.
Time zone detection in JavaScript is imperfect. There are only three approaches:
Using the getTimezoneOffset function of the Date class, which should return you the offset of the date it was called on. For example new Date().getTimezoneOffset() gives you the current offset. With this approach, you should also be aware that there is a bug in the ES5 spec that can cause the wrong offset to sometimes be returned when called on older dates.
Using a library such as jsTimezoneDetect, which makes several calls to the getTimezoneOffset to attempt to guess at an IANA time zone identifier. The guess is suitable to set a default time zone when a list of time zones is presented to the user. It is just a guess, and could be wrong. If you want to use it on the back end with .NET, you'll need Noda Time, since TimeZoneInfo doesn't currently support IANA time zones. (You can optionally convert to Windows time zones if desired).
Some newer browsers support the ECMAScript Internationalization API, which has an optionally implemented function to return the time zone. It may work in some browsers, but is not guaranteed to return a valid result everywhere.
Intl.DateTimeFormat().resolvedOptions().timeZone
Again, you'll need Noda Time on the back end.
You said:
The problem is, if when I change timezone of client machine to (UTC -8:00), the client machine shows the timezone name as "(UTC-08:00) Pacific Time (US & Canada)" but in application the timezone differs from the client system it shows as "(UTC-08:00) Baja California".
This is probably related to how you are choosing a time zone in your application code. It sounds to me like you're scanning the list of server time zones and choosing the first one that matches some criteria. Since both of these time zones have the same base offset, you're probably just picking the wrong one, and you shouldn't be doing that anyway. But since you didn't show that part of your code, I can't help you much there.
To answer your specific questions:
Whether the TimeZoneInfo class can work automatically work according to the adjusment rule, when we convert from UTC to Local?
Yes, it can. There's nothing wrong with TimeZoneInfo, it's all about how you're using it. You probably are selecting the wrong time zone.
Do we have to detect the DST for particular datetime using the method TimeZoneInfoObject.IsDaylightSavingTime(DateTime) and do conversion?
No, you should not have to do that just to convert from UTC to a specific time zone. The ConvertTimeFromUtc function will handle that for you.
Is there any other classes in .Net which can sync with windows timezones?
TimeZoneInfo is the only one built in to the .NET Framework. Noda Time is a great alternative that can work with either Windows time zone or IANA time zones.
Lastly, I'll re-iterate what Jon said in comments. If all you're doing is displaying a particular instant in time to an end-user, then forget about time zone detection or working with local time on the server at all. Just send the UTC time to the client, and use either the UTC functions on the JavaScript Date object, or use a library like moment.js. Either can work in both UTC and local, and can convert between them. For example (using moment.js):
var valueFromServer = "2015-07-26T12:00:00Z"; // the Z means UTC
var localTime = moment(valueFromServer).format(); // "2015-07-26T05:00:00-07:00" (Pacific)

Adjusting a DateTime to DST

To keep it short and sweet I have 2 PC's:
PC 1 has DST switched off
PC 2 has DST switched on.
PC 1 sends a DateTime to PC 2 which it takes and uses to set its time.
All the above I have in place but my questions is that when PC 2 receives the DateTime how can I check to see if it needs DST adjustments?
PC's are UK based.
EDIT:- Bit more detail incase there is confusion.
When PC 2 retrieves the time from PC 1 it will change the system's time to this value but I need to ensure that if a +/- 1 hour is required (DTS) then it is applied before setting the system date and time.
I would recommend using UTC for transport and persistance.
Also take care to not use date time as a critical part of your algorithm - it's just data. Dates and time shift and correct themselves. I've seen apps that crash when the PC corrects it's time :)
Here's another post with more information on the topic:
Daylight saving time and time zone best practices
You didn't show any code, but you tagged your question as c#, so I will answer from that perspective.
If you just need to make sure you're talking about the same moment in time, then use a DateTime with .Kind = DateTimeKind.Utc, such as is obtained from DateTime.UtcNow.
If you actually need to know that PC1 thought it was one time while PC2 thought it was another, and you still want to know that those two things represent the same moment in time, then use a DateTimeOffset. This will include the local date and time of the computer, as well as the offset from UTC that the date and time represent.
With either approach, you should use the ISO8601 format for transport and persistence, so that your meaning is clear regardless of culture or time zone of the observer. In .Net, this is obtained with .ToString("o") from either a DateTime or DateTimeOffset.
A UTC DateTime in ISO8601 format would look like this:
2013-03-06T09:00:00Z
A DateTimeOffset in ISO8601 format would look like this:
2013-03-06T10:00:00+01:00
.Net calls this the Round Trip pattern - because it is designed for the exact purpose you described.
For further clarification on DateTimeOffset - see this post.

Categories

Resources