Common Pitfall: Why DateTime.Now Returns an Incorrect Past Date
When validating a user's date of birth with FluentValidation, comparing the input against DateTime.Now.Date can produce unexpected results: an error may incorrectly state that the input date (e.g. 2020-07-06) cannot be after the current date, even when the actual current date is 2020-07-08, and the input date was correctly passed from the client.
To understand why this happens, look at the following validation class implementation:
class DateValidator
{
private readonly DateTime _maxAllowedTime;
public DateValidator(DateTime maxDate)
{
_maxAllowedTime = maxDate;
}
public bool ValidateDate(DateTime inputDate)
{
return inputDate <= _maxAllowedTime;
}
}
When creating an instance and running validation like this:
var validator = new DateValidator(DateTime.Now);
bool validationResult = validator.ValidateDate(DateTime.Now);
The result is often false if there is any delay between instantiation and validation, rather than the expected true. Now compare this to an alternative implementation:
class DateValidatorWithDynamicEvaluation
{
private readonly Expression<Func<DateTime>> _maxDateExpression;
public DateValidatorWithDynamicEvaluation(Expression<Func<DateTime>> maxDateSelector)
{
_maxDateExpression = maxDateSelector;
}
public bool ValidateDate(DateTime inputDate)
{
return inputDate <= _maxDateExpression.Compile()();
}
}
Used like this:
var dynamicValidator = new DateValidatorWithDynamicEvaluation(() => DateTime.Now);
bool dynamicValidationResult = dynamicValidator.ValidateDate(DateTime.Now);
This implementation consistently returns true as expected.
The root cause of the bug in the first implementation is straightforward: when you pass DateTime.Now as a constructor parameter, the value is evaluated immediately at the moment the constructor executes. That fixed snapshot of time is then stored in the readonly _maxAllowedTime field, and it never updates.
If your validator is a long-lived, singleton, or reused instance (a common pattern in validation libraries like FluentValidation), the stored time will stay as the time when the app started or the validator was first created, which becomes a date in the past as time passes. This matches the original issue: the validator was created on 2020-07-06, and when it was reused two days later, it still used the 2020-07-06 value as "current date".
In the dynamic implementatino, you do not store an evaluated time value. Instead, you store an expression that describes how to get the current time. Every time validation runs, the expression is compiled and invoked, which calls DateTime.Now at that exact moment, producing the correct up-to-date current time.
Key takeaways to avoid this pitfall:
- Always treat
DateTime.Nowand other time-dependent dynamic values as snapshot values, not dynamic references. - Any time you store the result of
DateTime.Nowin a variable or field, it becomes a fixed static value that will never update automatically. - When you need to access the actual current time at the time of logic execution, always wrap
DateTime.Nowin a deleagte or expression that evaluates it on demand, such asFunc<DateTime>orExpression<Func<DateTime>>.