346

We are developing a C# application for a web-service client. This will run on Windows XP PC's.

One of the fields returned by the web service is a DateTime field. The server returns a field in GMT format i.e. with a "Z" at the end.

However, we found that .NET seems to do some kind of implicit conversion and the time was always 12 hours out.

The following code sample resolves this to some extent in that the 12 hour difference has gone but it makes no allowance for NZ daylight saving.

CultureInfo ci = new CultureInfo("en-NZ");
string date = "Web service date".ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            

As per this date site:

UTC/GMT Offset

Standard time zone: UTC/GMT +12 hours
Daylight saving time: +1 hour
Current time zone offset: UTC/GMT +13 hours

How do we adjust for the extra hour? Can this be done programmatically or is this some kind of setting on the PC's?

Richard Ev
  • 52,939
  • 59
  • 191
  • 278
rbrayb
  • 46,440
  • 34
  • 114
  • 174

14 Answers14

413

For strings such as 2012-09-19 01:27:30.000, DateTime.Parse cannot tell what time zone the date and time are from.

DateTime has a Kind property, which can have one of three time zone options:

  • Unspecified
  • Local
  • Utc

NOTE If you are wishing to represent a date/time other than UTC or your local time zone, then you should use DateTimeOffset.


So for the code in your question:

DateTime convertedDate = DateTime.Parse(dateStr);

var kind = convertedDate.Kind; // will equal DateTimeKind.Unspecified

You say you know what kind it is, so tell it.

DateTime convertedDate = DateTime.SpecifyKind(
    DateTime.Parse(dateStr),
    DateTimeKind.Utc);

var kind = convertedDate.Kind; // will equal DateTimeKind.Utc

Now, once the system knows its in UTC time, you can just call ToLocalTime:

DateTime dt = convertedDate.ToLocalTime();

This will give you the result you require.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • 19
    just another way to specify the kind: `DateTime convertedTime = new DateTime(DateTime.Parse(dateStr).Ticks), DateTimeKind.Utc);` – Brad Oct 06 '10 at 14:37
  • isn't it ToLocalTime()? @Brad - your parens don't match. – TrueWill Feb 09 '11 at 22:50
  • 2
    Does this solution account for daylight savings? When I try it, I'm off an hour. – Bob Horn Sep 18 '12 at 20:41
  • @BobHorn, yes it should. Here in London, it accounts for GMT/BST as they toggle through the seasons. Perhaps step through your code in the debugger and check the `Kind` property of the `DateTime` values as you go. – Drew Noakes Sep 19 '12 at 00:26
  • 9
    The step of changing the `Kind` of the `DateTime` from `Unspecified` to `UTC` is unnecessary. `Unspecified` is assumed to be `UTC` for the purposes of `ToLocalTime`: http://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime.aspx – CJ7 Nov 14 '12 at 06:43
  • 17
    @CJ7: Yes, but being explicit is particularly helpful to other developers that may have to maintain the code. – Ryan Jan 17 '13 at 23:20
  • What if we specify DateTimeKind.Local in your second snippet? – Faisal Mq Dec 24 '18 at 13:12
  • I find this less helpful in Azure as servers are running as UTC. Using ".ToLocalTime()" just gets me more UTC. I need to specify the time zone with TimeZoneInfo to get the correct answer. – Hogan Oct 19 '22 at 17:24
129

I'd look into using the System.TimeZoneInfo class if you are in .NET 3.5. See http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx. This should take into account the daylight savings changes correctly.

// Coordinated Universal Time string from 
// DateTime.Now.ToUniversalTime().ToString("u");
string date = "2009-02-25 16:13:00Z"; 
// Local .NET timeZone.
DateTime localDateTime = DateTime.Parse(date); 
DateTime utcDateTime = localDateTime.ToUniversalTime();

// ID from: 
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zone"
// See http://msdn.microsoft.com/en-us/library/system.timezoneinfo.id.aspx
string nzTimeZoneKey = "New Zealand Standard Time";
TimeZoneInfo nzTimeZone = TimeZoneInfo.FindSystemTimeZoneById(nzTimeZoneKey);
DateTime nzDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, nzTimeZone);
Noctis
  • 11,507
  • 3
  • 43
  • 82
Daniel Ballinger
  • 13,187
  • 11
  • 69
  • 96
  • If you are working in your own time zone (en-NZ in this case) then you don't need to deal with TimeZoneInfo. It's just unnecessary complexity. See my answer for more detail. – Drew Noakes Jun 08 '09 at 07:45
  • 15
    And if anyone needs, here is the list of timezones I've found for TimeZoneInfo.FindSystemTimeZoneById - http://www.codeproject.com/Messages/3867850/Csharp-NET-List-of-timezones-for-TimeZoneInfo-Find.aspx – nikib3ro Apr 25 '11 at 19:54
66
TimeZone.CurrentTimeZone.ToLocalTime(date);
George Johnston
  • 31,652
  • 27
  • 127
  • 172
coder1
  • 3,447
  • 5
  • 30
  • 46
33

DateTime objects have the Kind of Unspecified by default, which for the purposes of ToLocalTime is assumed to be UTC.

To get the local time of an Unspecified DateTime object, you therefore just need to do this:

convertedDate.ToLocalTime();

The step of changing the Kind of the DateTime from Unspecified to UTC is unnecessary. Unspecified is assumed to be UTC for the purposes of ToLocalTime: http://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime.aspx

CJ7
  • 22,579
  • 65
  • 193
  • 321
16

I know this is an older question, but I ran into a similar situation, and I wanted to share what I had found for future searchers, possibly including myself :).

DateTime.Parse() can be tricky -- see here for example.

If the DateTime is coming from a Web service or some other source with a known format, you might want to consider something like

DateTime.ParseExact(dateString, 
                   "MM/dd/yyyy HH:mm:ss", 
                   CultureInfo.InvariantCulture, 
                   DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)

or, even better,

DateTime.TryParseExact(...)

The AssumeUniversal flag tells the parser that the date/time is already UTC; the combination of AssumeUniversal and AdjustToUniversal tells it not to convert the result to "local" time, which it will try to do by default. (I personally try to deal exclusively with UTC in the business / application / service layer(s) anyway. But bypassing the conversion to local time also speeds things up -- by 50% or more in my tests, see below.)

Here's what we were doing before:

DateTime.Parse(dateString, new CultureInfo("en-US"))

We had profiled the app and found that the DateTime.Parse represented a significant percentage of CPU usage. (Incidentally, the CultureInfo constructor was not a significant contributor to CPU usage.)

So I set up a console app to parse a date/time string 10000 times in a variety of ways. Bottom line:
Parse() 10 sec
ParseExact() (converting to local) 20-45 ms
ParseExact() (not converting to local) 10-15 ms
... and yes, the results for Parse() are in seconds, whereas the others are in milliseconds.

David
  • 2,226
  • 32
  • 39
15

I'd just like to add a general note of caution.

If all you are doing is getting the current time from the computer's internal clock to put a date/time on the display or a report, then all is well. But if you are saving the date/time information for later reference or are computing date/times, beware!

Let's say you determine that a cruise ship arrived in Honolulu on 20 Dec 2007 at 15:00 UTC. And you want to know what local time that was.
1. There are probably at least three 'locals' involved. Local may mean Honolulu, or it may mean where your computer is located, or it may mean the location where your customer is located.
2. If you use the built-in functions to do the conversion, it will probably be wrong. This is because daylight savings time is (probably) currently in effect on your computer, but was NOT in effect in December. But Windows does not know this... all it has is one flag to determine if daylight savings time is currently in effect. And if it is currently in effect, then it will happily add an hour even to a date in December.
3. Daylight savings time is implemented differently (or not at all) in various political subdivisions. Don't think that just because your country changes on a specific date, that other countries will too.

Mark T
  • 3,464
  • 5
  • 31
  • 45
  • 8
    Actually, #2 is not entirely correct. There are in fact rules about DST in every timezone that your computer will know if the info has been installed (and updated). For many zones, those rules are fixed. Others implement "dynamic DST". Brazil is my pet peeve for this. In summary though, your coputer can work out if your local time would be DST in december, assuming that no changes are passed into law between now and then. – Roger Willcocks Jun 16 '11 at 12:04
  • Even if you do not live in Brazil, DST is "dynamic" in that politicians can change it at any time (as was done a few years ago in the U.S.). As most software is written with future use in mind, it is important to realize there is NO practical, predictable or even theoretical way of knowing what DST rules will be in effect. You can get close, but save yourself some frustration by giving up on perfection. – DaveWalley May 14 '14 at 20:45
13
@TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.Local)
Prince Prasad
  • 1,528
  • 1
  • 16
  • 20
  • Or to a specific time zone (combined with Daniel Ballinger's answer): TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.FindSystemTimeZoneById("New Zealand Standard Time")); – Patrick Koorevaar Jan 12 '23 at 20:34
5

Don't forget if you already have a DateTime object and are not sure if it's UTC or Local, it's easy enough to use the methods on the object directly:

DateTime convertedDate = DateTime.Parse(date);
DateTime localDate = convertedDate.ToLocalTime();

How do we adjust for the extra hour?

Unless specified .net will use the local pc settings. I'd have a read of: http://msdn.microsoft.com/en-us/library/system.globalization.daylighttime.aspx

By the looks the code might look something like:

DaylightTime daylight = TimeZone.CurrentTimeZone.GetDaylightChanges( year );

And as mentioned above double check what timezone setting your server is on. There are articles on the net for how to safely affect the changes in IIS.

Brendan Kowitz
  • 1,795
  • 10
  • 14
  • The system will deal with this comlexity for you, provided you tell the system what 'kind' your date is (local/utc/unspecified). – Drew Noakes Jun 08 '09 at 07:47
4

In answer to Dana's suggestion:

The code sample now looks like:

string date = "Web service date"..ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            
DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(convertedDate);

The original date was 20/08/08; the kind was UTC.

Both "convertedDate" and "dt" are the same:

21/08/08 10:00:26; the kind was local

rbrayb
  • 46,440
  • 34
  • 114
  • 174
1

I came across this question as I was having a problem with the UTC dates you get back through the twitter API (created_at field on a status); I need to convert them to DateTime. None of the answers/ code samples in the answers on this page were sufficient to stop me getting a "String was not recognized as a valid DateTime" error (but it's the closest I have got to finding the correct answer on SO)

Posting this link here in case this helps someone else - the answer I needed was found on this blog post: http://www.wduffy.co.uk/blog/parsing-dates-when-aspnets-datetimeparse-doesnt-work/ - basically use DateTime.ParseExact with a format string instead of DateTime.Parse

DannykPowell
  • 1,227
  • 5
  • 18
  • 30
1

I had the problem with it being in a data set being pushed across the wire (webservice to client) that it would automatically change because the DataColumn's DateType field was set to local. Make sure you check what the DateType is if your pushing DataSets across.

If you don't want it to change, set it to Unspecified

Miles
  • 5,646
  • 18
  • 62
  • 86
0

This code block uses universal time to convert current DateTime object then converts it back to local DateTime. Works perfect for me I hope it helps!

CreatedDate.ToUniversalTime().ToLocalTime();
0

If you have a dateTime string that is considered to be in other than the UTC timezone, you could use DateTimeOffset in order to just set the timeZone info without changing the time from the string as follows:

// Input datetime string
string datetimeString = "2023-07-11 12:42:56";

// Specify the timezone as "Europe/Prague"
TimeZoneInfo pragueTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Prague");

// Parse the datetime string with the specified format
DateTime datetimeObj = DateTime.ParseExact(datetimeString, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);

// Create a DateTimeOffset with the parsed datetime and the "Europe/Prague" timezone
DateTimeOffset pragueDateTimeOffset = new DateTimeOffset(datetimeObj, pragueTimeZone.GetUtcOffset(datetimeObj));

You can also format the DateTimeOffset string as you would normally do with DateTime instance.

// Print the resulting DateTimeOffset object
Console.WriteLine(pragueDateTimeOffset.ToString("O"));
rePhat
  • 314
  • 3
  • 9
0

I had similar situation in my ASP.NET Core 7.0 Web API and I solved it as follows,

  1. Install NodaTime Nuget.
  2. Get the country code from User's Request using CultureInfo.CurrentCulture.Name[^2..]. In my case this is IN and this will return Asia/Kolkatta.
  3. If its not null then get the TimeZoneInfo using ZoneId else DateTime will be exported as UTC.
  4. Now we can use TimeZoneInfo.ConvertTimeFromUtc(UTCDateTime, TimeZoneInfo) to convert UTC DateTime to user's Local DateTime.
var timeZone = TzdbDateTimeZoneSource.Default.ZoneLocations!.FirstOrDefault(x => x.CountryCode == CultureInfo.CurrentCulture.Name[^2..]);

if (timeZone is not null) 
{
    var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone.ZoneId);
    foreach (var order in orders)
    {
        order.OrderDate = TimeZoneInfo.ConvertTimeFromUtc(order.OrderDate.DateTime, timeZoneInfo);
    }
}

Note that I'm taking the FirstOrDefault() which means for countries having single timezone this will suffice. But for countries with multiple timezone, we need to have timezone details from user stored in database for conversion.

fingers10
  • 6,675
  • 10
  • 49
  • 87