Subtracting UTC and non-UTC DateTime in C#
Asked Answered
C

2

8

I assumed that when subtracting 2 datetimes the framework will check their timezone and make the appropriate conversions.

I tested it with this code:

Console.WriteLine(DateTime.Now.ToUniversalTime() - DateTime.UtcNow.ToUniversalTime());
Console.WriteLine(DateTime.Now.ToUniversalTime() - DateTime.UtcNow);
Console.WriteLine(DateTime.Now - DateTime.UtcNow);

Output:

-00:00:00.0020002
-00:00:00.0020001
01:59:59.9989999

To my surprise, DateTime.Now - DateTime.UtcNow does not make the appropriate conversion automatically. At the same time, DateTime.UtcNow is the same as DateTime.UtcNow.ToUniversalTime(), so obviously there is some internal flag that indicates the time zone.

Is that correct, does that framework not perform the appropriate timezone conversion automatically, even if the information is already present? If so, is applying ToUniversalTime() safe for both UTC and non-UTC datetimes, i.e. an already UTC datetime will not be incorrectly corrected by ToUniversalTime()?

Camshaft answered 12/10, 2016 at 7:38 Comment(4)
Different result in mono: ideone.com/AfFUpVMcvay
@Mcvay I'm on VS2015, I don't know if it matters. Can you check on ideone with Visual Studio? I haven't personally used ideone much so I can't figure out how to do it.Camshaft
There is not time zone stored in the DateTime it just stores date and time. So it doesn't know in which time zone that DateTime is.Plausible
You are just stating a fact, readily visible from the reference source. Use DateTimeOffset instead.Indemnification
T
8

The type System.DateTime does not hold time zone information, only a .Kind property that specifies whether it is Local or UTC. But before .NET 2.0, there was not even a .Kind property.

When you subtract (or do other arithmetic, like == or >, on) two DateTime values, their "kinds" are not considered at all. Only the numbers of ticks are considered. This gives compatibility with .NET 1.1 when no kinds existed.

The functionality you ask for (and expect) is in the newer and richer type System.DateTimeOffset. In particular, if you do the subtraction DateTimeOffset.Now - DateTimeOffset.UtcNow you get the result you want. The DateTimeOffset structure does not have a local/UTC flag; instead, it holds the entire time zone, such as +02:00 in your area.

Theatheaceous answered 12/10, 2016 at 8:7 Comment(3)
Yes, coming from Qt, QDateTime holds a UTC time plus a timezone offset. I expected DateTime to use it, and was a little confused when Hans Passant mentioned DateTimeOffset, because it sounded like a TimeSpan rather than what DateTime should have been.Camshaft
Not much of a proper explanation, IMHO. There isn't much in the .NET Framework that remains (or ever remained) beholden to .NET 1.0/1.1 backwards compatibility, .NET 2.0 is the compatibility strived for.Maine
@MahmoudAl-Qudsi The type DateTimeOffset that I mentioned was introduced in .NET 2.0. When I described why the functionality of DateTime was chosen the way it was, when .NET 2.0 was introduced and DateTime acquired a Kind property, I was guessing a bit. However, when I described how DateTime actually works, since .NET 2.0, I was correct.Theatheaceous
C
1

The framework does nothing with the date if it is already UTC:

 internal static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
{
    if (dateTime.Kind == DateTimeKind.Utc)
    {
        return dateTime;
    }
    CachedData cachedData = s_cachedData;
    return ConvertTime(dateTime, cachedData.Local, cachedData.Utc, flags, cachedData);
}
Chip answered 12/10, 2016 at 7:44 Comment(5)
Though you have to be careful about DateTimeKind.Unknown (which can often come from querying a DB). In that case if you call ToUniversalTime it will assume local and convert to UTC, and if you call ToLocalTimeit will assume UTC and convert to local.Kapellmeister
@ChrisChilvers That is an important observation.Theatheaceous
The question is focused on the case where the ToUniversalTime() is not used. An explanation of how ConvertTimeToUtc works is not helpful in answering the case the question asked for. The method is not called in that case. If you want to post source code, post the code for public static TimeSpan operator -(DateTime d1, DateTime d2).Theatheaceous
@JeppeStigNielsen , one of the questions was is it safe to apply ToUniversalDateTime to an UTC date time.Chip
You are right, I focused too much on one aspect of the question (the one in the subject).Theatheaceous

© 2022 - 2024 — McMap. All rights reserved.