Hi
One of my team mates found an interesting issue in our code-base so I thought I'd do a write-up.
TLDR: Don't compare a DateTimeOffset to DateTime.MinValue, compare it with DateTimeOffset.MinValue.
Technically you could compare DateTimeOffset with DateTime.MinValue if you can guarantee that the system timezone will always be UTC or UTC minus some offset.
Into The Detail
Every request my teammate mate to our API was failing with the same exception, but despite running the master branch and the exact same request, I couldn't reproduce the error.
The exception was being thrown by the constructor of an AbstractValidator
from the FluentValidation package.
public class TestValidator : AbstractValidator<ThingWithDate>
{
public TestValidator()
{
RuleFor(x => x.Date)
.GreaterThan(default(DateTime));
}
}
This validator was injected into nearly every controller in our API via a BaseController (👍).
It turns out that to reproduce the issue I needed to run the service using the same system timezone as my colleague in the Balkans (UTC+3).
You can see from the stack trace that:
- We're initialising the
TestValidator
- That is hitting the
DateTimeOffset
implicit cast operator to convert theDateTime
to aDateTimeOffset
. - That calls the
DateTimeOffset
constructor which accepts oneDateTime
parameter. - The constructor calls
ValidateDate(DateTime, Timespan)
. TheTimeSpan
is the difference between UTC and the local system timezone.
Internally a DateTimeOffset
is a DateTime
and an offset in minutes. ValidateDate
, unsurprisingly, ensures that the DateTime
is valid when the offset is applied. When you apply a negative offset to DateTime.MinValue
you get a date that can't be represented by the DateTime
type and you get an ArgumentOutOfRangeException
instead.
Here is the code for the validate method lifted from here. Comments are mine...
private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset)
{
// +2 is 72000000000 ticks
// utcTicks is converting the "local" dateTime to UTC by subtracting the offset
Int64 utcTicks = dateTime.Ticks - offset.Ticks;
// This section validates that utcTicks is a valid time that can be represented
// utcTicks = -72000000000
// DateTime.MinTicks = 0
// -72000000000 < 0 == true => Kablamo
if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks)
{
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("Argument_UTCOutOfRange"));
}
return new DateTime(utcTicks, DateTimeKind.Unspecified);
}
Top comments (0)