BCL Refresher: DateTime.ToUniversalTime returns MaxValue/MinValue on overflow [Josh Free]
DateTime.ToUniversalTime converts the value of the current DateTime object to Coordinated Universal Time (UTC). When the converted value is either too large or too small to fit into a DateTime object then MaxValue or MinValue is returned, respectively. Returning MaxValue and MinValue for these overflow edge cases is fine for the vast majority of applications that do not care about the edge cases near “23:59:59.9999999, December 31, 9999” and “00:00:00.0000000, January 1, 0001”. However, for applications that do not want to work with values that cannot be round-tripped, this presents a problem. Fortunately, this problem is easily solved with a little bit of extra code.
Let’s start by walking through how to use DateTime.ToUniversalTime, how DateTime.ToUniversalTime works under the hood, and finally how to write a little bit of extra code to detect the overflow case yourself:
Sample Program using DateTime.ToUniversalTime:
This code snippet below demonstrates the use of DateTime.ToUniversalTime to convert the local time “8:00PM 12/31/9999” to UTC:
using System;
class Program {
static void Main(string[] args) {
// 20:00.00 (8PM) on 12/31/9999
DateTime myTime = new DateTime(
9999, /* year */
12, /* month */
31, /* day */
20, /* hour */
0, /* minute */
0, /* second */
DateTimeKind.Local);
// Converts the value of myTime to Coordinated Universal Time (UTC).
// When the conversion results in a value greater than MaxValue or
// less than MinValue then MaxValue or MinValue will be returned,
// respectively.
DateTime myUtcTime = myTime.ToUniversalTime();
Console.WriteLine("myTime = " + myTime.ToString("o"));
Console.WriteLine("myUtcTime = " + myUtcTime.ToString("o"));
Console.WriteLine("DateTime.MaxValue = " + DateTime.MaxValue.ToString("o"));
Console.WriteLine("myUtcTime == DateTime.MaxValue : " +
(myUtcTime == DateTime.MaxValue));
}
}
Program Output in Pacific Time (GMT-08:00):
myTime = 9999-12-31T20:00:00.0000000-08:00
myUtcTime = 9999-12-31T23:59:59.9999999Z
DateTime.MaxValue = 9999-12-31T23:59:59.9999999
myUtcTime == DateTime.MaxValue : True
In Pacific Time (GMT-08:00), the returned value is DateTime.MaxValue, because the actual converted value lands in the year 10,000 (“04:00:00.0000000, January 1, 10000”), which is too large to be stored in a DateTime.
How DateTime.ToUniversalTime Works
DateTime.ToUniversalTime is rather simple. It can be effectively implemented like this:
public DateTime ToUniversalTime(DateTime time) {
if (time.Kind == DateTimeKind.Utc) {
return time;
}
// The UTC time is equal to the local time minus the UTC offset.
long tickCount = time.Ticks -
TimeZone.CurrentTimeZone.GetUtcOffset(time).Ticks;
if (tickCount > DateTime.MaxValue.Ticks) {
// return MaxValue for values too large to fit in DateTime
return new DateTime(DateTime.MaxValue.Ticks, DateTimeKind.Utc);
}
if (tickCount < DateTime.MinValue.Ticks) {
// return MinValue for values too small to fit in DateTime
return new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc);
}
return new DateTime(tickCount, DateTimeKind.Utc);
}
How to Convert DateTime to UTC and Check for Overflow
Finally the solution to the problem of how to convert DateTime to UTC and detect the overflow case (it should look very similar to the code above :-) ):
public DateTime ToUniversalTime(DateTime time) {
if (time.Kind == DateTimeKind.Utc) {
return time;
}
// The UTC time is equal to the local time minus the UTC offset.
long tickCount = time.Ticks -
TimeZone.CurrentTimeZone.GetUtcOffset(time).Ticks;
if (tickCount > DateTime.MaxValue.Ticks) {
// value is too large to fit in DateTime
// handle the overflow case here...
}
if (tickCount < DateTime.MinValue.Ticks) {
// value is too small to fit in DateTime
// handle the overflow case here...
}
return new DateTime(tickCount, DateTimeKind.Utc);
}
A Note about DateTime.ToLocalTime
DateTime.ToLocalTime works similarly to DateTime.ToUniversalTime. However, instead of subtracting the UTC offset, the offset is added when converting from UTC to Local time.
Comments
Anonymous
June 12, 2007
A recent .NET Base Class Library blog post points out that DateTime.ToUniversalTime does not throw anAnonymous
June 12, 2007
I must protest about the design of this method (as well as most of the type itself, but others have made similar comments in the past). In exceptional causes such as this, I would expect and hope that it would throw an exception. We should not be checking return values for errors in this day (legacy libraries withstanding). How about a replacement method (ToUniversalTimeEx)? Or better yet unseal the type and I will do the work myself.Anonymous
June 13, 2007
Thanks Josh, but... you don't explain why this bug in the DateTime exists. You seem to be saying it is by design. Please expand! Thanks.Anonymous
July 21, 2007
Sometimes it is easy to build implementations in software that spend so much time striving for consistencyAnonymous
July 21, 2007
Sometimes it is easy to build implementations in software that spend so much time striving for consistency