c# parse string (without timezone) to DateTime, taking DST into account - c#

I've read many SO articles, but I don't seem to be able to find a good answer to the problem. Posted suggestions include functions that convert dates to and from strings more then once, concatenating bits on the end and it all just seems... messy
So to the problem:
We have servers around the world. All servers run in their own local time and keep logs with time entries that are local to the server. Some servers are in DST observing areas, others arent
Suppose I have these strings from a log: 2013-01-01 12:34:56, 2013-07-01 12:34:56
And I know that this server is in New York so it's UTC-5 or UTC-4 when DST is in operation
And I have the same strings from a log on a server in Hong Kong, where DST does not apply and the time zone is +8
What I'm after is a block of code where I can tell it:
Here is a string representing a time
Here is the timezone the string is from
Daylight Savings should apply if relevant
And the code will parse the string into a DateTimeOffset, where the offset is adjusted according to DST if the time being parsed is DST relevant
For example:
NY server log says "2013-01-01 ..." DST does NOT apply to this date in JANUARY so the date parsed should be:
12:34:56 in new york time, a.k.a 17:34:56 in UTC (because it's -5, no DST)
NY server log says "2013-07-01 ..." DST DOES apply to this date in june so the date parsed should be:
12:34:56 in new york time, a.k.a 16:34:56 in UTC (because it's -4, with the DST)
HK server, both date times parse to 04:34:56 UTC
Thanks guys

Firstly, I'd strongly recommend that you change the system to log in UTC everywhere. It'll make your life much simpler.
If you're really stuck with what you've got, you should use DateTime.TryParseExact with a DateTimeStyles of just 0 (the default). That will give you a value with a DateTimeKind of Unspecified, which is what you want. (It's not UTC, and it's not local to the machine doing the parsing.)
You can then use TimeZoneInfo.GetUtcOffset (with the right time zone for that log) to work out the offset, and create a DateTimeOffset from the two together.
As a completely biased aside, you could also change to use the Noda Time project I maintain, which will allow your code to be much simpler to understand :)

To get the UTC-times of the time logged in the different log files you will need to know the names of the local time zones. Then you can use the DateTimeOffset-struct TimeZoneInfo-class to calculate the UTC-times:
public DateTime ParseAsUtc(string logDate, string timezoneName)
{
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timezoneName);
var localDate = DateTime.Parse(logDate);
var offset = new DateTimeOffset(localDate, timeZone.GetUtcOffset(localDate));
return offset.ToUniversalTime().DateTime;
}
ParseAsUtc("2013-01-01 12:34:56", "Eastern Standard Time"); //01.01.2013 17:34:56
ParseAsUtc("2013-07-01 12:34:56", "Eastern Standard Time"); //01.07.2013 16:34:56
ParseAsUtc("2013-01-01 12:34:56", "China Standard Time"); //01.01.2013 04:34:56
ParseAsUtc("2013-01-01 12:34:56", "China Standard Time"); //01.07.2013 04:34:56

Related

C# TimeZoneInfo to convert GMT timezone name to system timezone

In windows, we get timezone list like this:
ID Time zone name Display string
-- -------------- --------------
0 Dateline Standard Time (UTC-12:00) International Date Line West
110 UTC-11 (UTC-11:00) Coordinated Universal Time -11
200 Hawaiian Standard Time (UTC-10:00) Hawaii
300 Alaskan Standard Time (UTC-09:00) Alaska
more here.
I use this list to convert from one timezone to another using TimeZoneInfo class which accepts time zone name shown in above list.
Ex.
// Local time zone to UTC
var utcOffset = new DateTimeOffset(DateTime.UtcNow, TimeSpan.Zero);
var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timezoneName); // here tz name can be any name from above table
var localOffset = new DateTimeOffset(date.Value, localTimeZone.GetUtcOffset(utcOffset));
DateTime utcDate = localOffset.UtcDateTime;
Now I came across SalesForce timezone representation like:
Time Zone Code Time Zone Name
-------------- --------------
GMT+14:00 Line Is. Time (Pacific/Kiritimati)
GMT+13:00 Phoenix Is.Time (Pacific/Enderbury)
GMT+13:00 Tonga Time (Pacific/Tongatapu)
GMT+12:45 Chatham Standard Time (Pacific/Chatham)
more here.
I couldn't find built in functionality to use either time zone code or time zone name given in above table for the conversion.
If you're happy to stick with TimeZoneInfo and DateTime/DateTimeOffset, you can use Matt Johnson's TimeZoneConverter library to convert the IANA ID (the part in brackets, e.g. Pacific/Kiritimati) to a Windows system time zone ID.
Examples from the project page docs:
string tz = TZConvert.IanaToWindows("America/New_York");
// Result: "Eastern Standard Time"
Or:
TimeZoneInfo tzi = TZConvert.GetTimeZoneInfo("America/New_York");
However, things to be aware of:
There won't always be a complete mapping from IANA IDs to Windows IDs. Some aren't mapped, although the situation is better than it used to be.
The Windows time zone data can be different to the IANA data sometimes. (Again, this is getting better.)
Time zone data changes over time. If you're storing future date/time values, you might want to store the time zone and local time rather than the UTC instant. (That instant may not map to the same local time in the future.)
Some local times can be ambiguous - e.g. if the clocks go back from 2am to 1am on a particular date in a particular time zone, then 1:15am on that day will occur twice in that time zone. Think about how you want to handle that, and test it.
I'd personally recommend using my Noda Time library anyway, as a cleaner way of handling date/time, but I acknowledge that if you've got a lot of code dealing with DateTime already, that may not be feasible.

Get eastern time in c# without converting local time

I know there are bunch of solutions on converting timezone to timezone, but what I'd like to know whether we can get eastern time without making conversion from our local time.
Well yes and no, if you represent using GMT always then you don't need to convert until you need to show locality.
Time is always in GMT and services should run under the GMT Timezone because of this reason among others.
Receiving and Storing a time in anything other than GMT / UTC requires conversions.
Please note the Timezone of the server should also be set to GMT for things to work as I indicated.
When you set things up like this it thus becomes much easier to reference and compare times and dates in different calendar date formats also.
This is the reason why DateTime offset made its way into the framework and sql server. if not careful when the Timezone on a server changes so does all stored and loaded dates in local format.
A date comparison is a date comparison. DateTime just wraps a memory structure which is defined in ticks and makes methods to access commonly used parts of the memory e.g. day or year or Time or TimeOfDay etc.
Furthermore conversion is only possible if you know both the source and destination offsets and then the calculation is always as given is given by -1 * (sourceOffset - destOffset)
Where the part in parenthesis represents the time zone difference.
Where the DateTime you want to convert is in UTC and called theDate
DateTime eastern = TimeZoneInfo
.ConvertTimeFromUtc(
theDate,
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"))
If theDate has a Kind of Local this will throw, only accepting Kind values of Utc or Unspecified. Of course, moving from local to UTC is easy and we could even check and do this conversion when required, but since you say you want this conversion "without making conversion from our local time" I'm assuming you have your times in UTC and hence having the exception would be better as it would warn you that times that should be UTC are being treat as local and a bug could pop up elsewhere.
This will use Eastern Daylight Time when theDate is a time when EDT is in effect, as that is the normal rule for EST. To use EST even when it is the summer you can create your own TimeZoneInfo:
TimeZoneInfo
.ConvertTimeFromUtc(
theDate,
TimeZoneInfo.CreateCustomTimeZone(
"Eastern Standard Time No Daylight Savings",
new TimeSpan(-5, 0, 0),
"Eastern Standard Time No Daylight Savings",
"Eastern Standard Time No Daylight Savings"))

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.

How to convert time between timezones (UTC to EDT)?

I need to have a common function to convert UTC time to EDT. I have a server in India. An application in it needs to use EDT time for all time purposes.
I am using .NET 3.5.
I found this on some other forum.
DateTime eastern = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(
DateTime.UtcNow, "Eastern Standard Time");
When i tried with "Easten Daylight Time" I got an error.
"The time zone ID 'Eastern Daylight Time' was not found on the local computer".
Please help with this or any other solution.
Eastern Daylight Time isn't the name of a "full" time zone - it's "half" a time zone, effectively, always 4 hours behind UTC. (There may be proper terminology for this, but I'm not aware of it.)
Why would you want to use EDT for times which don't have daylight savings applied? If you want a custom time zone that always has the same offset to UTC, use TimeZoneInfo.CreateCustomTimeZone.
Note that if you use get the Eastern Standard timezone (TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")) then that will still have daylight saving time applied appropriately (i.e. during summer).
For example:
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
// Prints True
Console.WriteLine(tzi.IsDaylightSavingTime(new DateTime(2009, 6, 1)));
// Prints False
Console.WriteLine(tzi.IsDaylightSavingTime(new DateTime(2009, 1, 1)));
I would have said that you should use UTC for calculations of time periods, so that you avoid issues of daylight saving time and then use LocalTime for display only.
DateTime.ToLocalTime for UTC to whatever the local time zone is and then DateTime.ToUniversalTime to convert from local time to UTC.
Edit after comment 1
Do I take it then that you're after displaying a different timezone to that of the server?
If you're using web pages to access your server then use
HttpRequest.UserLanguages to help create a CultureInfo object and use that to parse your DateTime object.
Look here for a full explanation:Microsoft link on displaying local user time for web pages.
If you're using client-server architecture then if the LocalTime call is on the client side it will display the LocalTime for the client. You then convert it to UTC to send back to your server.
Either way your server doesn't need to know where the client is so if you have multiple clients in multiple timezones then all calculations will match. It will also allow you to show the times in any timezone that you wish by use of different Culture objects.
Edit 2 copied my second comment
You can get time data in UTC format from the server. Then you can convert it using DateTime.ToLocalTime or DateTime.ToUniversalTime as requried. If you're including dates as well and need to cope with say US MM/dd/yyyy and european dd/MM/yyyy formats the you can use CultureInfo class to parse the DateTime value accordingly. It sounds like more work than what you have at the moment, but it would mean that if you move your server again then you don't need to recode the DateTime handling.
A new point
Another point to look at is clock synchronisation between the server and the clients using NTP (Network Time Protocol) or SNTP (Simple Network Time Protocol) if it is accurate enough. I don't know what OS you are using but this is used by Windows Server time services to synchronise networks.
The cowboy method is to take the UTC time, subtract four hours' worth of seconds from it (the timezone offset), format it using a UTC formatting function, and slap a "EDT" label on it.
If you need to use Daylight Time sometimes and Standard Time other times, either make a lookup table of switchover dates, or use some calendar function.
TimeZoneInfo.ConvertTimeFromUtc will have correct offset depending on the DateTime you give it. For example:
3AM UTC/11PM ET (4 hour offset):
DateTime timeSummerET = TimeZoneInfo.ConvertTimeFromUtc(Convert.ToDateTime("08/01/2019 03:00:00"), zoneET);
3AM UTC/10PM ET (5 hour offset):
DateTime timeWinterET = TimeZoneInfo.ConvertTimeFromUtc(Convert.ToDateTime("12/01/2019 03:00:00"), zoneET);

Categories