10

I need to convert a 'DateTime' value from different timezones to UTC and vice-versa. I use TimeZoneInfo to do this. But, the issue is when the 'Day Light Saving' time change happens.

For example, this year, next time the time change happens at 2AM[CDT] on November 3. So on Nov 3, 1AM[CDT] is converted to 6AM and as time change happens the next hour we get 1AM[now its CST] again and it is also converted to 6AM. I tried the code on this page, but it didn't say anything how to handle this issue. So how to deal with this issue???

Edit:

I tried NodaTime and when I do the conversion like

 DateTimeZoneProviders.Tzdb["America/Chicago"].AtStrictly(<localDateTime>) 

it throws AmbiguousTimeException. Thats good and I can do this using TimeZoneInfo too. But how do I know which localTime value I need to pick?

Edit 2:

here is the link for the chat discussion with Matt.

Community
  • 1
  • 1
usp
  • 797
  • 3
  • 10
  • 24
  • 3
    You should use Noda Time, which is specifically designed to help you in scenarios like this. – SLaks Jun 07 '13 at 22:00

1 Answers1

12

If all you have is a local time, and that time is ambiguous, then you cannot convert it to an exact UTC instant. That is why we say "ambiguous".

For example, in the US Central time zone, which has the IANA zone name America/Chicago and the Windows zone id Central Standard Time - covering both "Central Standard Time" and "Central Daylight Time". If all I know is that it is November 3rd, 2013 at 1:00 AM, then then this time is ambiguous, and there is absolutely no way to know whether this was the first instance of 1:00 AM that was in Central Daylight Time (UTC-5), or Central Standard Time (UTC-6).

Different platforms do different things when asked to convert an ambiguous time to UTC. Some go with the first instance, which is usually the Daylight time. Some go with the Standard time, which is usually the second instance. Some throw an exception, and some (like NodaTime) give you a choice of what you want to happen.

Let's start with TimeZoneInfo first.

// Despite the name, this zone covers both CST and CDT.
var tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var dt = new DateTime(2013, 11, 3, 1, 0, 0);
var utc = TimeZoneInfo.ConvertTimeToUtc(dt, tz);
Debug.WriteLine(utc); // 11/3/2013 7:00:00 AM

As you can see, .net chose to use the "standard" time, which is UTC-6. (Adding 6 hours to 1AM gets you to 7AM). It didn't give you any warning that the time was ambiguous. You could have checked yourself, like this:

if (tz.IsAmbiguousTime(dt))
{
    throw new Exception("Ambiguous Time!");
}

But there isn't anything to enforce this. You must check it yourself.

The only way to not have ambiguity is to not use the DateTime type. Instead, you can use DateTimeOffset. Observe:

// Central Standard Time
var dto = new DateTimeOffset(2013, 11, 3, 1, 0, 0, TimeSpan.FromHours(-6));
var utc = dto.ToUniversalTime();
Debug.WriteLine(utc); // 11/3/2013 7:00:00 AM +00:00

// Central Daylight Time
var dto = new DateTimeOffset(2013, 11, 3, 1, 0, 0, TimeSpan.FromHours(-5));
var utc = dto.ToUniversalTime();
Debug.WriteLine(utc); // 11/3/2013 6:00:00 AM +00:00

Now, compare this to NodaTime:

var tz = DateTimeZoneProviders.Tzdb["America/Chicago"];
var ldt = new LocalDateTime(2013, 11, 3, 1, 0, 0);

// will throw an exception, only because the value is ambiguous.
var zdt = tz.AtStrictly(ldt);

// will pick the standard time, like TimeZoneInfo did
var zdt = tz.AtLeniently(ldt);

// manually specify the offset for CST
var zdt = new ZonedDateTime(ldt, tz, Offset.FromHours(-6));

// manually specify the offset for CDT
var zdt = new ZonedDateTime(ldt, tz, Offset.FromHours(-5));


// with any of the above, once you have a ZonedDateTime
// you can get an instant which represents UTC
var instant = zdt.ToInstant();

As you can see, there are lots of options. All are valid, it just depends on what you want to do.

If you want to completely avoid ambiguity, then always keep a DateTimeOffset, or when using NodaTime use a ZonedDateTime or OffsetDateTime. If you use DateTime or LocalDateTime, there is no avoiding ambiguity.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Thanks. But the issue is how you determine the offset?? I mean to determine the correct offset, you need to know that you are in Daylight savings or not. That is my issue here!! – usp Jun 08 '13 at 00:10
  • You can't. You have to *record* it. For example, using `DateTimeOffset.Now` during the event. Or you can ask for it as input. If you don't have the offset, then you can't resolve ambiguity. – Matt Johnson-Pint Jun 08 '13 at 00:13
  • I have the standard offset but not the daylight savings offset. So you suggest store both offset values?? – usp Jun 08 '13 at 00:15
  • For whatever time it is, you record the full date, time, and offset. Using `DateTimeOffset` or `OffsetDateTime`. Then you have the correct offset when you need it. – Matt Johnson-Pint Jun 08 '13 at 00:17
  • For example, SQL Server has a `datetimeoffset` column type, and you can serialize over the web in ISO8601 extended format such as `2013-11-03T01:00:00-06:00`. – Matt Johnson-Pint Jun 08 '13 at 00:19
  • If you don't like the idea of storing an extra field, you can convert to UTC before you store it, and just keep a UTC `DateTime`. A lot of people swear by always storing UTC, and this is one of the main reasons why. – Matt Johnson-Pint Jun 08 '13 at 00:19
  • ok. let me explain you my scenario. I have a Table where I store some data along with datetime[UTC]. The information is provided by different clients from diff TimeZones across USA. I record data every hour. So now, when I query the table for records between 'time_A' and 'time_B', I have to first convert these times to UTC for all the timezones. I am having issues while converting Local Times to UTC. – usp Jun 08 '13 at 00:26
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31433/discussion-between-matt-johnson-and-usp) – Matt Johnson-Pint Jun 08 '13 at 00:27