Convert time data to local time in C# - c#

I've read a few posts about similar subjects but nothing seems to answer this question. My database has the following information about a time
Day of the week (a number between 0-6)
Time (a number of milliseconds since midnight in the users local time)
UTC offset ( number of hours different to UTC )
DST Observed (boolean stating if DST is observed in that time zone)
This data represents opening hours. So there is a time for each day. I want to display that time in the users local time making the assumption that each day is in the future
int dayOffset = availability.Day - (int)now.DayOfWeek
if (dayOffset < 0)
dayOffset += 7;
I'm really struggling to get my head around time zones and handling when one time zone might be observing DST while another maybe DOES observe DST but hasn't yet.
My main issue at the moment is I think I need to create a DateTimeOffset object for the non-local time but I'm not sure how to do that as I don't know if DST is in effect or not.
I hope I'm making myself clear. It really is a mind-bending experience working with dates and time!

As indicated by other answers, the usual solution to handling DateTime across time zones would be to store UTC times.
However, considering that you are not referencing an absolute time at a specific date, but instead are referring to a time at an infinite number of days in a specific time zone; storing the time as an UTC time doesn't make sense anymore, since the UTC time (even if we discard the date) would be different depending on the date, due to DST.
The best way to store the time is fairly close to what you have done already.
Your problem is that the time zone information you are storing at the moment is ambiguous, as it does not refer to a specific time zone, but instead refers to properties of the time zone.
To solve this problem, simply store the time zone identifier instead of the UTC offset and DST boolean.
It is now possible for us to construct the DateTime object and convert it to any time zone by using the TimeZoneInfo class:
int dayOffset = availability.Day - (int)DateTime.Today.DayOfWeek;
if (dayOffset < 0)
{
dayOffset += 7;
}
var openingHourStart = DateTime
.SpecifyKind(DateTime.Today, DateTimeKind.Unspecified)
.AddDays(dayOffset)
.AddMilliseconds(availability.Time);
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(availability.TimeZoneId);
var userTimeZone = TimeZoneInfo.Local;
var convertedOpeningHourStart = TimeZoneInfo.ConvertTime(openingHourStart,
sourceTimeZone,
userTimeZone);

Give a try to Quartz.NET.
It implements evaluation of CronExpressions, and even triggers to fire events at the given time. It can evaluate the next time an event will occur. This may help you out calculating the opening times.
Also, take a look at the cronmaker website: there you can understand the full potential of CronExpressions.
The CronExpressionDescriptor is a DotNET library for transforming CronExpressions into human readable strings.
Another library which I haven't tried yet is [HangFire].(https://www.hangfire.io/)
In this forum post you can find some discussion on how HangFire implements evaluation of RecurringJobs in local timezone with DST, which I believe is a solution for what you are looking for.

A comment to another answer made the problem a little bit more clear.
So, first and foremost, do store only UTC in your database. Really.
Now, since you are not interested in the actual dates, since you are storing working schedules that repeat weekly, the date only becomes relevant once you want to present your times - and when you put them in your database.
So let's first see how you get your times into your database correctly. I'm assuming a user will enter times in their own locale.
Make sure you first create a (localised) DateTime consisting of the current date and the given time (from the user), and transform that to a UTC DateTime (you can keep the current date, it doesn't matter):
var utcDateTime = DateTime.Now.Date
.AddHours(userHours)
.AddMinutes(userMinutes)
.ToUniversalTime();
Now when you are presenting these times to the user, simply go the other way:
var userDateTime = DateTime.Now.Date
.AddHours(utcDateTime.Hour)
.AddMinutes(utcDateTime.Minute)
.ToLocalTime();
And then you can use the userDateTime.Hour and .Minute for display purposes.

You should be leveraging DateTime.ToLocalTime() and TimeZoneInfo.ConvertTimeToUtc() in C# - see https://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime(v=vs.110).aspx.
If you want to store only times that you're open from Monday to Sunday, fine. Have a simple data table to describe only the time for each day (0 = Sunday through 7 = Saturday -- this is .Net's DayOfWeek enumeration). Your lookup table might look like:
0 null
1 08:00:00
2 08:00:00
3 08:00:00
4 08:30:00
5 08:30:00
6 10:30:00
(Use whatever data type works for you--SQL Server 2008+ has a TIME data type, for example. Null can be used for Closed on that day--i.e., no open time.)
When it comes time to display YOUR time to any other user, use must create your UTC time on-the-fly at the moment you are displaying information to the local user.
Conyc provided one approach. My approach uses simple date/time strings. To use my approach, just store time values per day in your database. Then you can look up the open time for any given day. To express that time for another user in any locale, use this code to convert your time to UTC (you can substitute the "08:00:00 AM" string value with a string variable that you populated after looking up the open time in your database):
var StoreOpenTimeInUtc = TimeZoneInfo.ConvertTimeToUtc(Convert.ToDateTime("08:00:00 AM"));
To look up the open time in your database for a particular day in the future, you will need to concatenate the date to your time value, like this:
var StoreOpenTimeInUtc = TimeZoneInfo.ConvertTimeToUtc(Convert.ToDateTime("04/28/2018 08:00:00 AM"));
Once you have an accurate StoreOpenTimeInUtc variable, you can use that as the UTC value on someone else's machine who is anywhere else on planet earth. To convert that UTC value to their local time, use the .NET ToLocalTime() method:
var OpenTimeForLocalUser = StoreOpenTimeInUtc.ToLocalTime();
Note that this approach requires you to store only the open times as shown above. You don't have to worry about dates, local offsets from UTC, or anything else. Just leverage ConvertTimeToUtc() and ToLocalTime() as shown.

Related

C# UTC conversion and Daylight Saving Time in game save

So, I've made a game in which it is required to check the time when saving and loading.
The relevant chunk of loading code:
playerData = save.LoadPlayer();
totalSeconds = playerData.totalSeconds;
System.DateTime stamp = System.DateTime.MinValue;
if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
playerData.timeStamp = System.DateTime.UtcNow.ToString("o");
stamp = System.DateTime.Parse(playerData.timeStamp);
}
stamp = stamp.ToUniversalTime();
loadStamp = System.DateTime.UtcNow;
long elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
if (elapsedSeconds < 0) {
ui.Cheater();
}
Obviously, all this does is check to see if the currently saved timestamp can be parsed - if so, we make sure it's UTC, if not, we set the stamp to the current time and continue. If the elapsed time between the loaded timestamp and current time is negative, we know the player has messed with their clock to exploit the system.
The potential problem arises when the clocks move an hour back for DST.
This is the relevant code in the save function, if it matters:
if (loadStamp == System.DateTime.MinValue) {
loadStamp = System.DateTime.UtcNow;
}
playerData.timeStamp = loadStamp.AddSeconds(sessionSeconds).ToString("o");
My question is:
Will this currently used method potentially cause any problems when the clocks move back and falsely deem players cheaters?
Thank you in advance.
EDIT:
Forgot to add that it seems to not cause any problems on the computer when the time is set to when the clocks move back, but the game is mobile. Again, if that matters at all. Not quite sure. I've not done much with time-based rewards and stuff in games thus far.
Update I have significantly updated this answer in respect of comments made by #theMayer and the fact that while I was wrong, it may have highlighted a bigger issue.
I believe there is an issue here in the fact that the code is reading the UTC time in, converting it to local time, then converting it back to UTC.
The save routine records the value of loadStamp expressed with the Round Trip format specifier o, and as loadStamp is always set from DateTime.UtcNow, the value stored in the file will always be a UTC time with a trailing "Z" indicating UTC time.
For example:
2018-02-18T01:30:00.0000000Z ( = 2018-02-17T23:30:00 in UTC-02:00 )
The issue was reported in the Brazil time zone, with a UTC offset of UTC-02:00 (BRST) until 2018-02-18T02:00:00Z and a UTC offset of UTC-03:00 (BRT) after.
The code reaches this line:
if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
DateTime.TryParse() (which uses the same rules as DateTime.Parse()) will encounter this string. It will then convert the UTC time into a local time, and set stamp to equal:
2018-02-17T23:30:00 DateTimeKind.Local
The code then reaches:
stamp = stamp.ToUniversalTime();
At this point, stamp should represent an Ambiguous time, i.e. one that exists as a valid BRST and a valid BRT time, and MSDN states:
If the date and time instance value is an ambiguous time, this method assumes that it is a standard time. (An ambiguous time is one that can map either to a standard time or to a daylight saving time in the local time zone)
This means that .NET could be changing the UTC value of any ambiguous DateTime values that are converted to Local time and back again.
Although the documentation states this clearly, I have been unable to reproduce this behaviour in the Brazilian time zone. I am still investigating this.
My approach to this type of issue is to use the DateTimeOffset type instead of DateTime. It represents a point-in-time that is irrelevant of local time, time zones, or Daylight Savings.
An alternative approach to closing this hole would be to change:
if (!System.DateTime.TryParse(playerData.timeStamp, out stamp)) {
playerData.timeStamp = System.DateTime.UtcNow.ToString("o");
stamp = System.DateTime.Parse(playerData.timeStamp);
}
stamp = stamp.ToUniversalTime();
to
if (!System.DateTime.TryParse(playerData.timeStamp, null, DateTimeStyles.RoundtripKind, out stamp)) {
stamp = System.DateTime.UtcNow;
playerData.timeStamp = stamp.ToString("o");
}
Again assuming that the saved playerData.timeStamp will always be from a UTC date and therefore be in "Z" timezone, adding the DateTimeStyles.RoundtripKind should mean it gets parsed straight into DateTimeKind.Utc and not converted into Local time as DateTimeKind.Local. It also eliminates the need to call ToUniversalTime() on it to convert it back.
Hope this helps
On the surface, there does not appear to be anything obviously wrong with this code.
When doing DateTime comparison operations, it is important to ensure that the time zone of the compared DateTime's are consistent. The framework only compares the values of the instances, irrespective of whatever time zone you think they are in. It is up to you to ensure consistent time zones between DateTime instances. It looks like that is being done in this case, as times are converted from local to UTC prior to being compared with other UTC times:
stamp = stamp.ToUniversalTime();
elapsedSeconds = (long)(System.DateTime.UtcNow - stamp).TotalSeconds;
Some Caveats
One item that often trips people up is that the time value is repeatedly queried (each call to DateTime.UtcNow) - which could result in different values each time. However, the difference would be infinitesimal, and most of the time zero as this code will execute faster than the resolution of the processor clock.
Another fact, which I brought up in the comments, the "Round Trip Format Specifier" used to write the DateTime to string is intended to preserve time zone information - in this case, it should add a "Z" to the time to denote UTC. Upon conversion back (via TryParse), the parser will convert this time from the UTC to a local time if the Z is present. This can be a significant gotcha as it results in an actual DateTimevalue that is different from the one serialized to the string, and in a way is contrary to every other way that the .NET framework handles DateTime's (which is to ignore time zone info). If you have a case where the "Z" is not present in the incoming string, but the string is otherwise UTC, then you have a problem there because it will be comparing a UTC time whose value has been adjusted a second time (thus making it the time of UTC +2).
It should also be noted that in .Net 1.1, DateTime.ToUniversalTime() is NOT an idempotent function. It will offset the DateTime instance by the difference in time zone between the local time zone and UTC each time it is called. From the documentation:
This method assumes that the current DateTime holds the local time value, and not a UTC time. Therefore, each time it is run, the current method performs the necessary modifications on the DateTime to derive the UTC time, whether the current DateTime holds the local time or not.
Programs using a later version of the framework may or may not have to worry about this, depending on usage.

UTC to local time conversion for previously saved datetimes if rules for timezone change

I'm storing a product in db. All dates (sql server datetime) are UTC and along with the dates I store the time zone id for that product. User enters the dates when product is available "from" and "until" in the listing.
So I do something like:
// Convert user's datetime to UTC
var userEnteredDateTime = DateTime.Parse("11/11/2014 9:00:00");
// TimeZoneInfo id will be stored along with the UTC datetime
var tz = TimeZoneInfo.FindSystemTimeZoneById("FLE Standard Time");
// following produces: 9/11/2014 7:00:00 AM (winter time - 1h back)
var utcDateTime = TimeZoneInfo.ConvertTimeToUtc(userEnteredDateTime, tz);
and save the record. Let's assume user did this on the 1st of August, while his time zone offset to UTC is still +03:00, nevertheless the saved date for the future listing has the correct +02:00 value because conversion took into consideration the "winter" time for that period.
Question is what datetime value will I get if I will attempt to convert that product's "from" and "until" date to product's local time zone on 11/11/2014 if, for example, due to some new rules the transition to winter time was abandoned, thus the time zone is still +03:00 instead of +02:00?
// Convert back
var userLocalTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, tz);
will I get 10AM or correct 9AM because OS/.NET patch will handle this?
Thank you!
P.S.: TimeZoneInfo has ToSerializedString() method, if I rather store this value instead of timezone id, will this guarantee that via UTC datetime + serialized timezoneinfo I will always be able to convert to the user's original datetime input?
In the scenario you describe, you would get 10:00 AM. The time zone conversion function would not have any idea that the value was originally entered as 9:00 AM, because you only saved the UTC time of 7:00 AM.
This illustrates one of the cases where the advice "always store UTC" is flawed. When you're working with future events, it doesn't always work. The problem is that governments change their mind about time zones often. Sometimes they give reasonable notice (ex. United States, 2007) but sometimes they don't (ex. Egypt, 2014).
When you made the original conversion from local time to UTC, you intentionally decided to trust that the time zone rules would not change. In other words, you decided that you would assign the event to the universal timeline based solely on the time zone rules as you knew them at that time.
The way to avoid this is simple: Future events should be scheduled in local time. Now, I don't mean "local to your computer", but rather "local to the user", so you will need to know the user's time zone, and you should also store the ID of the time zone somewhere.
You'll also need to decide what you want to do if the event falls into the spring-forward or fall-back transition for daylight saving time. This is especially important for recurrence patterns.
Ultimately though, you'll need to figure out when to run the event. Or in your case, you'll need to decide if the event has passed or not. There are a few different ways you can accomplish this:
Option 1
You can calculate the corresponding UTC value for each local time and keep it in a separate field.
On some cycle (daily, weekly, etc) you can recalculate upcoming UTC values from their local values and your current understanding of the time zone rules. Or, if you apply time zone updates manually, you can choose to recalculate everything at that time.
Option 2
You can store the values as a DateTimeOffset type instead of a DateTime. It will contain the original local time, and the offset that you calculated based on the time zone rules as you knew them at time of entry.
DateTimeOffset values can easily be coerced back to UTC, so they tend to work very well for this. You can read more in DateTime vs DateTimeOffset.
Just like in option 1, you would revisit the values periodically or after time zone data updates, and adjust the offsets to align with the new time zone data.
This is what I usually recommend, especially if you're using a database that has support for DateTimeOffset types, such as SQL Server or RavenDB.
Option 3
You can store the values as a local DateTime.
When querying, you would calculate the current time in the target time zone and compare against that value.
DateTime now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, targetTZ);
bool passed = now >= eventTime;
The down side of this option is that you may have to make lots queries if you have events in lots of different time zones.
You may also have issues with values close to the fall-back DST transition, so be careful if you use this approach.
I recommend against the idea of serializing the time zone itself. If the time zone has changed, then it has changed. Pretending that it hasn't isn't a good workaround.

Converting datetime to time in C# / ASP.NET

I am trying to insert time on my asp.net project.
RequestUpdateEmployeeDTR requestUpdateEmployeeDTR = new RequestUpdateEmployeeDTR();
requestUpdateEmployeeDTR.AttendanceDeducID = int.Parse(txtAttendanceDeducID.Text);
requestUpdateEmployeeDTR.TimeInChange = txtTimeOutChange.Text;
requestUpdateEmployeeDTR.TimeOutChange = txtTimeOutChange.Text;
TimeInChange and TimeOutChange are DateTime data types. But I am inserting a time data type. How can I convert that into a time data type using C#? Thanks!
The .NET Framework does not have a native Time data type to represent a time of day. You will have to decide between one of the three following options:
Option 1
Use a DateTime type, and ignore the date portion. Pick a date that's outside of a normal range of values for your application. I typically use 0001-01-01, which is conveniently available as DateTime.MinValue.
If you are parsing a time from a string, the easiest way to do this is with the DateTimeStyles.NoCurrentDateDefault option. Without this option, it would use today's date instead of the min date.
DateTime myTime = DateTime.Parse("12:34", CultureInfo.InvariantCulture,
DateTimeStyles.NoCurrentDateDefault);
// Result: 0001-01-01 12:34:00
Of course, if you prefer to use today's date, you can do that. I just think it confuses the issue because you might be looking to apply this to some other date entirely.
Note that once you have a DateTime value, you can use the .TimeOfDay property to get at just the time portion, represented as a TimeSpan, which leads to option 2...
Option 2
Use a TimeSpan type, but be careful in how you interpret it. Understand that TimeSpan is first and foremost a type for representing an elapsed duration of time, not a time of day. That means it can store more than 24 hours, and it can also store negative values to represent moving backwards in time.
When you use it as a time of day, you might be inclined to think of it as "elapsed time since midnight". This, however, will get you into trouble because there are days where midnight does not exist in the local time zone.
For example, October 20th 2013 in Brazil started at 1:00 AM due to daylight saving time. So a TimeSpan of 8:00 on this day would actually have been only 7 hours elapsed since 1:00, not 8 hours elapsed since midnight.
Even in the United States, for locations that use daylight saving time, this value is misleading. For example, November 3rd 2013 in Los Angeles had a duplicated hour for when DST rolled back. So a TimeSpan of 8:00 on this day would actually had 9 hours elapsed since midnight.
So if you use this option, just be careful to treat it as the representative time value that matches a clock, and not as "time elapsed since midnight".
You can get it directly from a string with the following code:
TimeSpan myTime = TimeSpan.Parse("12:34", CultureInfo.InvariantCulture);
Option 3
Use a library that has a true "time of day" type. You'll find this in Noda Time, which offers a much better API for working with date and time in .NET.
The type that represents a "time of day" is called LocalTime, and you can get one from a string like this:
var pattern = LocalTimePattern.CreateWithInvariantCulture("HH:mm");
LocalTime myTime = pattern.Parse("12:34").Value;
Since it appears from your question that you are working with time and attendance data, I strongly suggest you use Noda Time for all your date and time needs. It will force you to put more thought into what you are doing. In the process, you will avoid the pitfalls that can come about with the built-in date/time types.
If you are storing a Time type in your database (such as SQL server), that gets translated as a TimeSpan in .Net. So if you go with this option, you'll need to convert the LocalTime to a TimeSpan as follows:
TimeSpan ts = new TimeSpan(myTime.TickOfDay);

Representation of a DateTime as the local to remote user

Hello!
I was confused in the problem of time zones. I am writing a web application that will contain some news with dates of publication, and I want the client to see the date of publication of the news in the form of corresponding local time. However, I do not know in which time zone the client is located.
I have three questions.
I have to ask just in case: does DateTimeOffset.UtcNow always returns the correct UTC date and time, regardless of whether the server is dependent on daylight savings time? For example, if the first time I get the value of this property for two minutes before daylight savings time (or before the transition from daylight saving time back) and the second time in 2 minutes after the transfer, whether the value of properties in all cases differ by only 4 minutes? Or here require any further logic? (Question #1)
Please see the following example and tell me what you think.
I posted the news on the site. I assume that DateTimeOffset.UtcNow takes into account the time zone of the server and the daylight savings time, and so I immediately get the correct UTC server time when pressing the button "Submit". I write this value to a MS SQL database in the field of type datetime2(0).
Then the user opens a page with news and no matter how long after publication. This may occur even after many years. I did not ask him to enter his time zone. Instead, I get the offset of his current local time from UTC using the javascript function following way:
function GetUserTimezoneOffset()
{
var offset = new Date().getTimezoneOffset();
return offset;
}
Next I make the calculation of the date and time of publication, which will show the user:
public static DateTime Get_Publication_Date_In_User_Local_DateTime(
DateTime Publication_Utc_Date_Time_From_DataBase,
int User_Time_Zone_Offset_Returned_by_Javascript)
{
int userTimezoneOffset = User_Time_Zone_Offset_Returned_by_Javascript; // For
// example Javascript returns a value equal to -300, which means the
// current user's time differs from UTC to 300 minutes. Ie offset
// is UTC +6. In this case, it may be the time zone UTC +5 which
// currently operates summer time or UTC +6 which currently operates the
// standard time.
// Right? (Question #2)
DateTimeOffset utcPublicationDateTime =
new DateTimeOffset(Publication_Utc_Date_Time_From_DataBase,
TimeSpan.Zero); // get an instance of type DateTimeOffset for the
// date and time of publication for further calculations
DateTimeOffset publication_DateTime_In_User_Local_DateTime =
utcPublicationDateTime.ToOffset(new TimeSpan(0, - userTimezoneOffset, 0));
return publication_DateTime_In_User_Local_DateTime.DateTime;// return to user
}
Is the value obtained correct? Is this the right approach to solving this problem? (Question #3)
UPDATED Oct 19 at 6:58 (I tried post it as a comment but it's too long by 668 characters)
Matt Johnson, Thank You for such a detailed answer despite that of the fact that you are doing this not the first time. Thank you for taking the time to explain this particular case, and not just provide links to other posts.
I have read the information that you have provided. Maybe I'm still not fully aware of all the details, but if I understand it right, for the right convertion of DateTime (which was written many years ago in the database) from UTC to the same user's moment, I need to know UTC offset which he had at that moment. And it is difficult taking into account that transfer rules for DST change constantly. And even now, though the platform ".NET" contains some TZDB that is used for the TimeZoneInfo type, I can't take advantage of it without the exact position of the user.
But what if I am only interested in the date and time of starting this year, and only in Russia, where DST was canceled in 2011? As I understand it, this means that if properly configured clock on user's computer, located in Russia, this approach will always give the correct result. And since 2011, the offset to UTC of user's clock should always be the same. Accordingly, the shift indicators in different browsers will not be different for the Russian user's.
Answer to Question 1
... does DateTimeOffset.UtcNow always returns the correct UTC date and time, regardless of whether the server is dependent on daylight savings time?
Yes. As long is your clock is set correctly, UtcNow always refers to the UTC time. The time zone settings of the server will not affect it. The value in your example will always be 4 minutes, regardless of DST.
Answer to Question 2
var offset = new Date().getTimezoneOffset();
Since new Date() returns the current date and time, this will return you the current offset. You then proceed to apply this current offset to some past value, which may or may not be the correct offset for that specific time. Please read the timezone tag wiki, especially the section titled "Time Zone != Offset".
Answer to Question 3
Is the value obtained correct? Is this the right approach to solving this problem?
No. This is not the correct approach. You have a few options:
First Option
Just pass the UTC value to JavaScript without modification.
Send it in ISO8601 format, such as 2013-10-18T12:34:56.000Z. You can get this in .Net easily using yourDateTime.ToString("o").
Be sure the DateTime you are starting with has .Kind == DateTimeKind.Utc, otherwise it won't get the Z on the end, which is essential.
If you are targeting older browsers that can't parse ISO8601, then you will need a library such as moment.js to parse it for you.
Alternatively, you could pass the number of milliseconds since 1/1/1970 UTC as a number and load that into a JavaScript Date instead of parsing.
Now you can just display the Date object using JavaScript. Let the browser convert it from UTC to the users local time.
Warning, with this approach, some conversions might be incorrect due to the problem I describe here.
Second Option
Like the first option, pass the UTC timestamp to JavaScript
Use that to get the offset for that timestamp.
Pass the offset back to the server in a postback or ajax call.
Apply the offset on the server
Output the local time zone
I don't particularly like this option because of the round trip. You might as well calculate it in JavaScript like the first option.
Third Option
Get the user's time zone. The best way is to ask them for it, usually on a user setting page.
Use it to convert from the UTC time to their local time completely on the server.
You can use Windows time zones and the TimeZoneInfo class to do the conversions.
Or you can use IANA/Olson time zones and the Noda Time library.
If you do this, you can optionally use a map-based timezone picker.
You can take a guess at the user's time zone with jsTimeZoneDetect.
This approach is more accurate, but requires more user interaction.
Also, please in future posts, ask just one question at a time. And please search before you post - I've written most of this in one form or another many times before.

Storing date/times as UTC in database

I am storing date/times in the database as UTC and computing them inside my application back to local time based on the specific timezone. Say for example I have the following date/time:
01/04/2010 00:00
Say it is for a country e.g. UK which observes DST (Daylight Savings Time) and at this particular time we are in daylight savings. When I convert this date to UTC and store it in the database it is actually stored as:
31/03/2010 23:00
As the date would be adjusted -1 hours for DST. This works fine when your observing DST at time of submission. However, what happens when the clock is adjusted back? When I pull that date from the database and convert it to local time that particular datetime would be seen as 31/03/2010 23:00 when in reality it was processed as 01/04/2010 00:00.
Correct me if I am wrong but isn't this a bit of a flaw when storing times as UTC?
Example of Timezone conversion
Basically what I am doing is storing the date/times of when information is being submitted to my system in order to allow users to do a range report. Here is how I am storing the date/times:
public DateTime LocalDateTime(string timeZoneId)
{
var tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi).ToUniversalTime().ToLocalTime();
}
Storing as UTC:
var localDateTime = LocalDateTime("AUS Eastern Standard Time");
WriteToDB(localDateTime.ToUniversalTime());
You don't adjust the date for DST changes based on whether you're currently observing them - you adjust it based on whether DST is observed at the instant you're describing. So in the case of January, you wouldn't apply the adjustment.
There is a problem, however - some local times are ambiguous. For example, 1:30am on October 31st 2010 in the UK can either represent UTC 01:30 or UTC 02:30, because the clocks go back from 2am to 1am. You can get from any instant represented in UTC to the local time which would be displayed at that instant, but the operation isn't reversible.
Likewise it's very possible for you to have a local time which never occurs - 1:30am on March 28th 2010 didn't happen in the UK, for example - because at 1am the clocks jumped forward to 2am.
The long and the short of it is that if you're trying to represent an instant in time, you can use UTC and get an unambiguous representation. If you're trying to represent a time in a particular time zone, you'll need the time zone itself (e.g. Europe/London) and either the UTC representation of the instant or the local date and time with the offset at that particular time (to disambiguate around DST transitions). Another alternative is to only store UTC and the offset from it; that allows you to tell the local time at that instant, but it means you can't predict what the local time would be a minute later, as you don't really know the time zone. (This is what DateTimeOffset stores, basically.)
We're hoping to make this reasonably easy to handle in Noda Time, but you'll still need to be aware of it as a possibility.
EDIT:
The code you've shown is incorrect. Here's why. I've changed the structure of the code to make it easier to see, but you'll see it's performing the same calls.
var tzi = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
var aussieTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi);
var serverLocalTime = aussieTime.ToLocalTime();
var utcTime = serverLocalTime.ToUniversalTime();
So, let's think about right now - which is 13:38 in my local time (UTC+1, in London), 12:38 UTC, 22:39 in Sydney.
Your code will give:
aussieTime = 22:39 (correct)
serverLocalTime = 23:39 (*not* correct)
utcTime = 22:39 (*not* correct)
You should not call ToLocalTime on the result of TimeZoneInfo.ConvertTimeFromUtc - it will assume that it's being called on a UTC DateTime (unless it's actually got a kind of DateTimeKind.Local, which it won't in this case).
So if you're accurately saving 22:39 in this case, you aren't accurately saving the current time in UTC.
It's good that you are attempting to store the dates and times as UTC. It is generally best and easiest to think of UTC as the actual date and time and local times are just pseudonyms for that. And UTC is absolutely critical if you need to do any math on the date/time values to get timespans. I generally manipulate dates internally as UTC, and only convert to local time when displaying the value to the user (if it's necessary).
The bug that you are experiencing is that you are incorrectly assigning the local time zone to the date/time values. In January in the UK it is incorrect to interpret a local time as being in a Summertime time zone. You should use the time zone that was in effect at the time and location that the time value represents.
Translating the time back for display depends entirely on the requirements of the system. You could either display the times as the user's local time or as the source time for the data. But either way, Daylight Saving/Summertime adjustments should be applied appropriately for the target time zone and time.
You could work around this by also storing the particular offset used when converting to UTC. In your example, you'd store the date as something like
31/12/2009 23:00 +0100
When displaying this to the user, you can use that same offset to convert, or their current local offset, as you choose.
This approach also comes with its own problems. Time is a messy thing.
The TimeZoneInfo.ConvertTimeFromUtc() method will solve your problem:
using System;
class Program {
static void Main(string[] args) {
DateTime dt1 = new DateTime(2009, 12, 31, 23, 0, 0, DateTimeKind.Utc);
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt1, tz));
DateTime dt2 = new DateTime(2010, 4, 1, 23, 0, 0, DateTimeKind.Utc);
Console.WriteLine(TimeZoneInfo.ConvertTimeFromUtc(dt2, tz));
Console.ReadLine();
}
}
Output:
12/31/2009 11:00:00 PM
4/2/2010 12:00:00 AM
You'll need .NET 3.5 or better and run on an operating system that keeps historical daylight saving time changes (Vista, Win7 or Win2008).
Correct me if I am wrong but isn't
this a bit of a flaw when storing
times as UTC?
Yes it is. Also, days of the adjustment will have either 23 or 25 hours so the idiom of the prior day at the same time being local time - 24 hours is wrong 2 days a year.
The fix is picking one standard and sticking with it. Storing dates as UTC and displaying as local is pretty standard. Just don't use a shortcut of doing calculations local (+- somthing) = new time and you are OK.
This is a huge flaw but it isn't a flaw of storing times in UTC (because that is the only reasonable thing to do -- storing local times is always a disaster). This is a flaw is the concept of daylight savings time.
The real problem is that the time zone information changes. The DST rules are dynamic and historic. They time when DST starting in USA in 2010 is not the same when it started in 2000. Until recently Windows did not even contain this historic data, so it was essentially impossible to do things correctly. You had to use the tz database to get it right. Now I just googled it and it appears that .NET 3.5 and Vista (I assume Windows 2008 too) has done some improvement and the System.TimeZoneInfo actually handles historic data. Take a look at this.
But basically DST must go.

Categories