A tip you mght pick up as you mature from a junior dev to a more senior one is that DateTime.Now is frowned upon, instead you should use almost always use DateTime.UtcNow because it is not affected by time zones and clock changes like Daylight Savings. If you need to show the value to a user, you can then convert the time to the users timezone in your presentation layer.

But what about when you want something to expire in 5 mins time? Is that Now + 5m? Or UtcNow + 5m?

DateTimeOffset

LazyCache is the open source .net caching library I wrote. It is basically a wrapper (with nice ribbons on!) around Microsoft.Extensions.Caching.MemoryCache, which allows you to cache something computationally expensive in-memory and have it expire at some configurable point in the future. The question came up this week - should that absolute expiry date in the future be expressed based on Now, or UtcNow? And that got me thinking…

Say you live in the UK, as I do, then currently (July) you are in the British Summer Time zone. This means that UtcNow + 5m is actually 55m ago acording to my watch - which, depending on the way a cache expiry works, might mean my cached item expired already, and in effect never gets cached! Probably not what you intended. Equaly in another timezone, my cache expiry could be many hours further in the future than I was anticipated. Isn’t UTC supposed to fix this sort of thing?

When Microsoft moved to dotnet core they ported code from System.Runtime.Caching to Microsoft.Extensions.Caching and the cache APIs got a rewrite. In the process they adoped DateTimeOffset instead of DateTime for their absolute expiration APIs. DateTimeOffset should solve the question above because it is a structure to represent the difference between the time you specify and the current UTC time, rather than the time in a particular timezone. Sounds perfect for representing the scenario in a absolute cache expiry where you want the item to expire in 5 minutes time.

Using DateTimeOffset also suggests to the developer that all expiry times in the cache are based on UTC, making life a bit clearer. However DateTimeOffset has two properties - Now and UtcNow - which one do I need to use? I have always used Now - but maybe that is because for 6 months a year I live in UTC+0 and so it did not matter then? Does my code cache for an hour longer in summer time?! Time for some tests.

Confused by DateTimes? Turns out dates and times are more complicated in code than they first appear, even choosing between DateTime and DateTimeOffset is not trivial. Go have a read and come back - we will wait.

Quiz time

Now lets see if you have been paying attention. Have a look at the following unit test - it contains 3 options which can be uncommented one at a time.

The test validates that an item in the cache configured to expire in 2 seconds, does expire between 2 and 3 seconds.

What do you think happens in each of the three options? Try and guess without running it.

// Assume NUnit, LazyCache and Microsoft.Extensions.Caching.Memory are imported

var watch = new Stopwatch();
watch.Start();
var seconds = 2;

// Which option should i use?
var memoryCacheEntryOptions = new MemoryCacheEntryOptions
{
    // Option 1 - DateTimeOffset.UtcNow
    // AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(seconds)

    // Option 2 - DateTimeOffset.Now
    // AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(seconds)

    // Option 3 - AbsoluteExpirationRelativeToNow
    // AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(seconds)
};
memoryCacheEntryOptions.RegisterPostEvictionCallback(
    (key, value, reason, state) => watch.Stop()
);

while (watch.IsRunning)
{
    await Task.Delay(500);
    await sut.GetOrAddAsync(
        TestKey, 
        () => Task.FromResult(123),
        memoryCacheEntryOptions);

    if(watch.Elapsed.TotalSeconds > (seconds + 10))
        throw new Exception("test has timed out - cache item never expired!");
}

Assert.That(watch.Elapsed.TotalSeconds, Is.GreaterThan(seconds));
Assert.That(watch.Elapsed.TotalSeconds, Is.LessThan(seconds+1));

It turns out not entirely what I expected.

Option 1 - TEST PASS - everything here is in UTC, and so I expected this one to pass, and it did. So far so good.

Option 2 - TEST PASS - because Now is actually an hour ahead of UtcNow in my current time zone, I wondered if this might accidentally be cached for one hour and 2 seconds, but the test proves that is not the case. This is the whole benefit of using the DateTimeOffset structure - it is holding the offset between the specified time and utc, which is 2 seconds in both cases. Still not sure? Run the following in a non UTC time zone - it is always true:

DateTimeOffset.UtcNow.AddMinutes(10).ToUniversalTime() == DateTimeOffset.Now.AddMinutes(10).ToUniversalTime()

Option 3 - TEST FAILED - on paper this looked like the best API to use because it is very clear that I want 2 seconds in the future. However this fails with the Exception test has timed out - cache item never expired! - it seems like the cache expiry never happens. I am not yet clear why, perhaps a bug? Post in the comments if you have a suggestion.

UtcNow or Now?

So in summary, for LazyCache and MemoryCache absolute expiry, it does not matter if you specify expiry using UtcNow or Now. Personally I prefer to use UTC, such as DateTimeOffset.UtcNow.AddMinutes(10) because it is more explicit, but maybe you wany to save those three keyboard strokes.