Using TimeZoneInfo to convert between UTC/Specified TimeZone not working - c#

All of our date/time data in our database is stored in UTC time. I'm trying to write a function to convert that UTC time into the user's preferred time zone (they can select their timezone in their profile so it has NOTHING to do with local settings on their computer and everything to do with which timezone they have selected from a dropdown of available choices.
This function is in the context of a DevExpress AspxGridView event (the third party control is not relevant to the question but I thought I'd mention it):
DateTimeOffset utcTime = (DateTimeOffset)e.Value;
TimeZoneInfo destTimeZone = Helper.GetTimeZoneInfo();
DateTime modifiedDate = TimeZoneInfo
.ConvertTimeFromUtc(utcTime.DateTime, destTimeZone);
e.DisplayText = String.Format("{0} {1}",
modifiedDate.ToString("g"),
destTimeZone.Abbreviation());
Helper.GetTimeZoneInfo() simply returns a TimeZoneInfo class that corresponds to the one the user selected, or defaults to "Pacific Standard Time" if they have not chosen one.
This generally works fine until I switch my system clock (which this server is running on) from today (which is Oct. 14, a DST date) to something like January 11, which is NOT DST.
The time seems to always be displayed in DST (i.e. always a 7 hour offset). No matter what I do with my system clock, I can't get the time to adjust for the extra hour.
For example, when I have my timezone set to Pacific Standard Time, for UTC time 10-10-2011 20:00:00, it is always displaying this time:
10-10-2011 13:00:00 (the DST time, offset of -7).
During non-Daylight Savings dates (standard), the time should be:
10-10-2011 12:00:00 (offset of -8).
What am I missing?

The converted local time of a UTC time is always based on the time zone info for that UTC time... not for whatever the current time happens to be.
That is, the PST offset on Oct 10, 2011 UTC is always -7. It doesn't matter what date you are doing the conversion on.
...Or am I misunderstanding what you are asking?

You might have a look at the e.Value.
Is the DateTimeKind for it set to DateTimeKind.Utc or is it DateTimeKind.Unspecified
If it is Unspecified, the conversion will not work correctly.
One cannot set the Kind directly. The best I have come up with is something along the lines of
// the value off the DB, but Kind is unspecified
var fromDb = new DateTime(1999,31,12)
// convert it to a Utc version of the same date
var fromDbInUtc = new DateTime(fromDb.Ticks, DateTimeKind.Utc)
var destTimeZone = Helper.GetTimeZoneInfo();
var local = TimeZoneInfo.ConvertFromUtc(fromDbInUtc, destTimeZone);
Hope this helps,
Alan.

Related

C# How to convert UTC date time to Mexico date time

see my code which i used to convert Mexico date and time to UTC date and time.
string strDateTime = "25/01/2017 07:31:00 AM";
DateTime localDateTime = DateTime.Parse(strDateTime);
DateTime univDateTime = localDateTime.ToUniversalTime();
ToUniversalTime return UTC 25-01-2017 02:01:00
when again i try to convert the same UTC date and time UTC 25-01-2017 02:01:00 to Mexico local time then i got 24-01-2017 06:01:00
so see 07:31:00 AM becomes 06:01:00 which is not right. so tell me what is missing in my code for which i am getting wrong local time when i convert from utc to Mexico time using timezone info.
see my code which converting from utc to Mexico local time using timezone info.
string strDateTime = "25-01-2017 02:01:00";
DateTime utcDateTime = DateTime.Parse(strDateTime);
string nzTimeZoneKey = "Pacific Standard Time (Mexico)";
TimeZoneInfo nzTimeZone = TimeZoneInfo.FindSystemTimeZoneById(nzTimeZoneKey);
DateTime nzDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, nzTimeZone);
You current time zone (UTC+05:30) is different from the time zone you are converting into (UTC-8:00). So you get the difference. There is about 13 hours and 30 minutes difference from your original time zone to the targeted one. 5:30 - (-8) = 13:30.
Subtract 13 hours and 30 minutes from your original date, and then you get 18:01:00, which in 12-hour format is 6PM on the previous day.
Edit:
Instead of hard-coding Mexico time zone, you will need to have a method by which you can determine user's time zone no matter where they are coming from. This is best done using JavaScript as outlined in this answer.
Okay, I didn't know that you were located in India - which changes things a little bit:
You're going to want to utilize the TimeZoneInfo.ConvertTime() API for this one.. Maybe something like :
var dt = new DateTime(2017, 01, 25, 7, 31, 0).ToUniversalTime();
var nzTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time (Mexico)");
//var ist = TimeZoneInfo.FindSystemTimeZoneById("India Standard Time");
DateTime nzDateTime = TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Utc, nzTimeZone);
Your problem is that the Parse is done without specifying the timezone it comes from - therefore the system will use whatever the default is of your computer. It appears that Your computer is NOT in PST. Rather somewhere in India.
Therefore after turning it into a DateTime object you need to convert it to UTC by specifying the PST timezone. There are a few ways to do this:
Specify the timezone offset as part of the string.
Call one of the TimeZoneInfo.ConvertTimeToUtc and specify the timezoneid
Maybe all you want to do is convert between two timezones by calling ConvertTime or ConvertTimeByTimeZoneId.
https://msdn.microsoft.com/en-us/library/bb382770(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/bb382058(v=vs.110).aspx
string pst = "Pacific Standard Time";
TimeZoneInfo.ConvertTimeBySystemTimeZoneId(currentTime, TimeZoneInfo.Local.Id, pst));
For example: 7:30AM PST should be 1:30 UTC - not 2:30. So that suggests a problem in the initial conversion. 2 AM UTC to PST is indeed 6 PM. Also I noticed your input was 7:31 and you claim it output 2:01 -- does Mexico do 30 minute timezones? I know India does.
I use Google to test conversions by literally searching for "2:01 UTC to PST" and it returns the answer for comparison.
See this other post which shows declaring the input timezone for Parsing. And as stated one does NOT need to convert for DST. Does ConvertTimeFromUtc() and ToUniversalTime() handle DST?
More info on MSDN for TimeZoneInfo: https://msdn.microsoft.com/en-us/library/bb495915(v=vs.110).aspx

Creating daylight savings-aware DateTime when server set to UTC

My application is hosted on Windows Azure, which has all servers set to UTC.
I need to know when any given DateTime is subject to daylight savings time. For simplicity, we can assume that my users are all in the UK (so using Greenwich Mean Time).
The code I am using to convert my DateTime objects is
public static DateTime UtcToUserTimeZone(DateTime dateTime)
{
dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Greenwich Standard Time");
var userDateTime = TimeZoneInfo.ConvertTime(dateTime, timeZone);
return DateTime.SpecifyKind(userDateTime, DateTimeKind.Local);
}
However, the following test fails at the last line; the converted DateTime does not know that it should be in daylight savings time.
[Test]
public void UtcToUserTimeZoneShouldWork()
{
var utcDateTime = new DateTime(2014, 6, 1, 12, 0, 0, DateTimeKind.Utc);
Assert.IsFalse(utcDateTime.IsDaylightSavingTime());
var dateTime = Utils.UtcToUserTimeZone(utcDateTime);
Assert.IsTrue(dateTime.IsDaylightSavingTime());
}
Note that this only fails when my Windows time zone is set to (UTC) Co-ordinated Universal Time. When it is set to (UTC) Dublin, Edinburgh, Lisbon, London (or any other northern-hemisphere time zone that observes daylight savings), the test passes. If you change this setting in Windows a restart of Visual Studio is required in order for the change to fully take effect.
What am I missing?
Your last line specifies that the value is in the local time zone of the system it's running in - but it's not really... it's converted using a different time zone.
Basically DateTime is somewhat broken, in my view - which makes it hard to use correctly. There's no way of saying "This DateTime value is in time zone x" - you can perform a conversion to find a specific local time, but that doesn't remember the time zone.
For example, from the docs of DateTime.IsDaylightSavingTime (emphasis mine):
This method determines whether the current DateTime value falls within the daylight saving time range of the local time zone, which is returned by the TimeZoneInfo.Local property.
Obviously I'm biased, but I'd recommend using my Noda Time project for this instead - quite possibly using a ZonedDateTime. You can call GetZoneInterval to obtain the ZoneInterval for that ZonedDateTime, and then use ZoneInterval.Savings to check whether the current offset contains a daylight savings component:
bool daylight = zonedDateTime.GetZoneInterval().Savings != Offset.Zero;
This is slightly long-winded... I've added a feature request to consider adding an IsDaylightSavingTime() method to ZonedDateTime as well...
The main thing you are missing is that "Greenwich Standard Time" is not the TimeZoneInfo id for London. That one actually belongs to "(UTC) Monrovia, Reykjavik".
You want "GMT Standard Time", which maps to "(UTC) Dublin, Edinburgh, Lisbon, London"
Yes, Windows time zones are weird. (At least you don't live in France, which gets strangely labeled as "Romance Standard Time"!)
Also, you should not be doing this part:
return DateTime.SpecifyKind(userDateTime, DateTimeKind.Local);
That will make various other code think it came from the local machine's time zone. Leave it with the "Unspecified" kind. (Or even better, use a DateTimeOffset instead of a DateTime)
Then you also need to use the .IsDaylightSavingsTime method on the TimeZoneInfo object, rather than the one on the .DateTime object. (There are two different methods, with the same name, on different objects, with differing behavior. Ouch!)
But I wholeheartedly agree that this is way too complicated and error prone. Just use Noda Time. You'll be glad you did!

how to get UTC time of a past date using asp.net and c# or sql server

Can you guide me to get utc time of a given date (which can be either in the past,current time). Let us say user enters the date in the format of 12-AUG-2013 17:10 PHT. Is it possible to get the UTC time of that given date? I am using asp.net and c# 3.5 and sql server 2008 for my project. Please suggest me ways to implement this option. Please let us know for more information regarding my query. Thanks
PHT is not part of the input string, but i append it to display that its Phillines time zone. But i get this abbrevations from admin screen where the country,timedifference to UTC/GMT and abbrevations of time zone are stored.
I would give the details of my requirement. User enters a date of his/her local time zone. This date can be the date in past or current one. For example, user is entering the date 12-AUG-2013 17:10 which is a phillipines date and time (Let us say). I want this date to be converted to UTC/GMT date and time. How i do it is, I ask admin user to store time difference for each country. when the user enters the date and based on the selected country, i decide it as phillipnes date and time if the user has selected country Phillipines.Then i add or substract that timedifference to get the UTC/GMT date. My concern is if user enters the date and time of a past time, then how can we get the UTC time of that date because we can only get the current UTC time. Hope I could give you enough details. I know the code and able to google and all but i need good approach to do this. I just need concept to do it.
... i get this abbreviations from admin screen where the country, time difference to UTC/GMT and abbreviations of time zone are stored...
If you are trying to support the majority of the world, that won't be sufficient. A time zone is not just an offset from UTC. In most cases, you can't just ask the user for the offset. Please read the timezone tag wiki. You need to know what the time zone is. In this case it's either "Singapore Standard Time" for the Windows time zone database, or "Asia/Manila" for the IANA database.
For example, user is entering the date 12-AUG-2013 17:10 which is a Philippines date and time (Let us say). I want this date to be converted to UTC/GMT date and time.
Here's how you convert with C# for the Windows time zone:
DateTime local = new DateTime(2013, 8, 12, 17, 10, 0);
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Singapore Standard Time");
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(local, tz);
And here's how you could convert it using the IANA database, via the Noda Time library:
LocalDateTime local = new LocalDateTime(2013, 8, 12, 17, 10, 0);
DateTimeZone tz = DateTimeZoneProviders.Tzdb["Asia/Manila"];
ZonedDateTime zdt = local.InZoneLeniently(tz);
Instant utcinstant = zdt.ToInstant(); // for a NodaTime instant
DateTime utcdt = zdt.ToDateTimeUtc(); // for a regular UTC DateTime
I skipped over the fact that you are starting with a string since your question is more focused on the UTC and time zone issue. If you need help on that, look at DateTime.Parse or DateTime.ParseExact. If you go with Noda Time, look at their text parsing docs.
Try as
DateTime utcDateTime = DateTimeVariable.ToUniversalTime();

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.

Having problems with converting my DateTime to UTC

I am storing all my dates in UTC format in my database. I ask the user for their timezone and I want to use their time zone plus what I am guessing is the server time to figure out the UTC for them.
Once I have that I want to do a search to see what the range is in the database using their newly converted UTC date.
But I always get this exception.
System.ArgumentException was unhandled by user code
Message="The conversion could not be completed because the
supplied DateTime did not have the Kind property set correctly.
For example, when the Kind property is DateTimeKind.Local,
the source time zone must be TimeZoneInfo.Local.
Parameter name: sourceTimeZone"
I don't know why I am getting this.
I tried 2 ways
TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(id);
// I also tried DateTime.UtcNow
DateTime now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local);
var utc = TimeZoneInfo.ConvertTimeToUtc(now , zone );
This failed so I tried
DateTime now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local);
var utc = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(now,
ZoneId, TimeZoneInfo.Utc.Id);
This also failed with the same error. What am I doing wrong?
Edit Would this work?
DateTime localServerTime = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local);
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(id);
var usersTime = TimeZoneInfo.ConvertTime(localServerTime, info);
var utc = TimeZoneInfo.ConvertTimeToUtc(usersTime, userInfo);
Edit 2 # Jon Skeet
Yes, I was just thinking about that I might not even need to do all this. Time stuff confuses me right now so thats why the post may not be as clear as it should be. I never know what the heck DateTime.Now is getting (I tried to change my Timezone to another timezone and it kept getting my local time).
This is what I wanted to achieve: User comes to the site, adds some alert and it gets saved as utc (prior it was DateTime.Now, then someone suggested to store everything UTC).
So before a user would come to my site and depending where my hosting server was it could be like on the next day. So if the alert was said to be shown on August 30th (their time) but with the time difference of the server they could come on August 29th and the alert would be shown.
So I wanted to deal with that. So now I am not sure should I just store their local time then use this offset stuff? Or just store UTC time. With just storing UTC time it still might be wrong since the user still probably would be thinking in local time and I am not sure how UTC really works. It still could end up in a difference of time.
Edit3
var info = TimeZoneInfo.FindSystemTimeZoneById(id)
DateTimeOffset usersTime = TimeZoneInfo.ConvertTime(DataBaseUTCDate,
TimeZoneInfo.Utc, info);
You need to set the Kind to Unspecified, like this:
DateTime now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified);
var utc = TimeZoneInfo.ConvertTimeToUtc(now , zone);
DateTimeKind.Local means the in local time zone, and not any other time zone. That's why you were getting the error.
The DateTime structure supports only two timezones:
The local timezone the machine is running in.
and UTC.
Have a look at the DateTimeOffset structure.
var info = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
DateTimeOffset localServerTime = DateTimeOffset.Now;
DateTimeOffset usersTime = TimeZoneInfo.ConvertTime(localServerTime, info);
DateTimeOffset utc = localServerTime.ToUniversalTime();
Console.WriteLine("Local Time: {0}", localServerTime);
Console.WriteLine("User's Time: {0}", usersTime);
Console.WriteLine("UTC: {0}", utc);
Output:
Local Time: 30.08.2009 20:48:17 +02:00
User's Time: 31.08.2009 03:48:17 +09:00
UTC: 30.08.2009 18:48:17 +00:00
Everyone else's answer seems overly complex. I had a specific requirement and this worked fine for me:
void Main()
{
var startDate = DateTime.Today;
var StartDateUtc = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTime.SpecifyKind(startDate.Date, DateTimeKind.Unspecified), "Eastern Standard Time", "UTC");
startDate.Dump();
StartDateUtc.Dump();
}
Which outputs (from linqpad) what I expected:
12/20/2013 12:00:00 AM
12/20/2013 5:00:00 AM
Props to Slaks for the Unspecified kind tip. That's what I was missing. But all the talk about there being only two kinds of dates (local and UTC) just muddled the issue for me.
FYI -- the machine I ran this on was in Central Time Zone and DST was not in effect.
As dtb says, you should use DateTimeOffset if you want to store a date/time with a specific time zone.
However, it's not at all clear from your post that you really need to. You only give examples using DateTime.Now and you say you're guessing that you're using the server time. What time do you actually want? If you just want the current time in UTC, use DateTime.UtcNow or DateTimeOffset.UtcNow. You don't need to know the time zone to know the current UTC time, precisely because it's universal.
If you're getting a date/time from the user in some other way, please give more information - that way we'll be able to work out what you need to do. Otherwise we're just guessing.
UTC is just a time zone that everyone agreed on as the standard time zone. Specifically, it's a time zone that contains London, England. EDIT: Note that it's not the exact same time zone; for example, UTC has no DST. (Thanks, Jon Skeet)
The only special thing about UTC is that it's much easier to use in .Net than any other time zone (DateTime.UtcNow, DateTime.ToUniversalTime, and other members).
Therefore, as others have mentioned, the best thing for you to do is store all dates in UTC within your database, then convert to the user's local time (by writing TimeZoneInfo.ConvertTime(time, usersTimeZone) before displaying.
If you want to be fancier, you can geolocate your users' IP addresses to automatically guess their time zones.

Categories