Search code examples
c#.nettimezonedatetimeoffset

Difference between two DateTimeOffset returns an unexpected negative value


The following c# code is supposed to return a timespan that indicates how much time there is between the current time and the next occurence of an event, supposed to happen at 5am. It must handle the case when the code is called before or after 5am.

The code manages to return negative delays on our servers (+1GMT) between 4 and 5am, which I cannot comprehend. Would anyone know what the explanation is ?

TimeSpan EverydayAt(int hourOfDay)
{
    DateTimeOffset now = DateTimeOffset.UtcNow;
    DateTimeOffset nextTime = now.Date.AddDays(now.Hour < hourOfDay ? 0 : 1).AddHours(hourOfDay);
    TimeSpan delay = nextTime - now;
    return delay;
}

Solution

  • Would anyone know what the explanation is ?

    To expound the issue is coming in from this line.

    now.Date.AddDays(now.Hour < hourOfDay ? 0 : 1).AddHours(hourOfDay);
    

    This is a DateTime object unbeknownst to you, you are actually doing an implicit conversion to the DateTimeOffset object nextTime.

    This nextTime object created will have an Offset of 1 hour since "our servers(+1GMT)".

    So essentially time close to 5am (1 hr and less) will generate a DateTimeOffset object of the current day with an Offset of 1 hour which you compared with now (DateTimeOffset object of the current day with an Offset of 0 hours).

    Alternative solution:

    Is to convert it back to a DateTimeOffset with zero Offset

    int hourOfDay = 15;
    DateTimeOffset now = DateTimeOffset.UtcNow;
    DateTimeOffset nextTime = new DateTimeOffset(now.Date.AddDays(now.Hour < hourOfDay ? 0 : 1).AddHours(hourOfDay), TimeSpan.Zero);
    TimeSpan delay = nextTime - now;
    

    Additional Info

    Conversions from DateTime to DateTimeOffset

    The DateTimeOffset structure provides two equivalent ways to perform DateTime to DateTimeOffset conversion that are suitable for most conversions:

    • The DateTimeOffset constructor, which creates a new DateTimeOffset object based on a DateTime value.
    • The implicit conversion operator, which allows you to assign a DateTime value to a DateTimeOffset object.

    An Implicit conversion is what you're doing on that line.

    However, for DateTime values whose Kind property is DateTimeKind.Unspecified, these two conversion methods produce a DateTimeOffset value whose offset is that of the local time zone.