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.