I, DATETIMEPROVIDER
The Persistence of Memory, by Salvador Dali
There are several things I learnt coming out of a large project, and before I stitch them together in an Agile Development Best Practices publication of some sort, I decided to write them into smaller blogs first so as to be able to share sooner and get feedback.
An on-going issue at the time I started writing these series is dealing with DateTime. Dealing with Dates and Times can be a surprisingly hard nut to crack. This is not as well known as it should be.
The different meanings of now
You would think that the concept of now — the date and time that you get when looking on the clock — is fairly simple. Lots of devices will tell you the current date and time, and you’d think that’s that. But when you start writing software that involves the current date and time, things quickly get more complicated. Or, which is far worse, slowly …
By the time you realize you have a problem, you may have written many lines of code, and several times more lines of tests (larger projects can end up with thousands). Having to refactor a lot of code, redo conversions, get tests back to green, it can become a daunting process. So the best way to prepare for this, is make sure that you abstract out your DateTime logic.
But what problems, you ask? We’ll not go into them into too much detail, as the point here is how to be flexible when you run into a problem you didn’t think about in advance, but let’s focus on the most important concepts.
“Using Agile with DateTime development can be like a slow cooking frog. Except that contrary to popular belief, frogs do in fact jump out of the pan when it gets too hot.”
Time is on your side
This is the part where I try to convince you that you may have a problem, now or in the future. If you already know this and are pressed for time, then feel free to skip to the next section.
Still here? Alright then, here goes.
Let’s say that I was writing a piece of software, perhaps for a hospital, or for astrology, that records my exact time and date of birth, and let’s work under the illusion that I was certain that no one would ever want me to do anything else with that data. Simple, right? Let’s say I used .NET and stored a DateTime object, and save that to a SQL Database. When I load it again, I’ll just get the value I put in there, and that’s that. Well, that is, if the SQL Database is running on a machine that has the same regional settings as the client or server that is running my machine, there’s a fair chance the value will be similar, right? But if I had a user from the UK, and they read my value, for them it would be one hour earlier.
Is that a problem? Depends on what you want to do obviously, but it is not hard to imagine a scenario that could pose one. What if I just happened to be born 00:15? In UK time, that would be 23:15 the previous day. So does that mean that nice marketing email should congratulate me on my birthday a day earlier? Hmm.
So perhaps, if I just stick do doing everything per country, host individual servers there, have all the clients the same, I won’t have this problem, right? Ok, but wait, some countries have multiple time zones (you know, small edge-cases like the US, Russia, China … ). Well, OK, but I happen to be targeting a small country with one time zone anyway, and it will be fully localized and everything, so what could go wrong? Well …
So someone in the hospital presses that nice little button that registers when the baby was born at 00:15 and voila, that’s that. Except, this was in the summer, and then winter time comes. Is the baby now born at 23:15 the previous day? No, obviously, but what if the programmer thought
“I read somewhere that if I use a DateTime value, I should always use UTC.”
… then very likely, yes, the baby’s birthday, when you looked at it in the winter, would be a day earlier than you expected it to be. Because, you know, every programmer learns in school that if you just use Universal Coordinated Time, then all DateTime problems will melt away like snow before the sun.
Now what if I had a database with people with their dates and times of birth, and I wanted to order them by age, listing the oldest first. If they all lived in the same country time zone, that would be pretty easy. But what if they didn’t? Then 00:15 isn’t the same as 00:15, so I need a common ground. UTC could be good here, right? Yes, right, for sorting it is pretty good, doesn’t really matter if these people have their birthday a day earlier depending on whether I look at this in the winter or in the summer. For sorting … Of course I wouldn’t possibly have any other uses for these dates … And don’t forget that sorting mess for data when the clock goes back for daylight saving time.
There are also some really nice stories out there about fun variants in time zone, and of course everyone knows that Germany had a small zone of land in Switzerland that had its own time zone until that ended in 2013, right? (I learnt that in an actual project). So perhaps if someone was born there before that date …
“Wisdom comes with age and experience if you’re lucky, but learning you don’t know everything and preparing for it is always better than betting on getting it right the first time.”
So quickly (well, not in my case, it took about 10 years), you start to realize that if you want to be able to do everything you possibly want with a date, you need to have a lot more information than you might have thought earlier: you’ll want the date and time it was recorded on, the time zone it was recorded in, and whether or not daylight saving time was active. So if I say I was born at 7:15 today, but all those years ago we used slightly different time zone data, if I then need to compare my birthdate with someone else from another time zone … You get the idea.
Fortunately, in some cases you can get away with shortcuts. For instance, let’s say we have to keep track of when a store opens, and we want to publish this on Google Maps. A store (one of those old-school non-online ones) opens at say 07:00 no matter whether it is winter or summer. And no matter if originally I am from Australia or Brazil, when I am going to that store I am generally interested in the local time only. Also, as a developer or end-consumer, if I know that is the local time, I can probably even translate it to other time zones if necessary
So I know that the time for the location of that point on Google Maps will be just fine. I store it in the database for just that location, and voila. Except … I have to make sure that my UI control records the time in the correct format, and if that is serialized to .NET, it retains that format, and if that is saved to by say Entity Framework to say a SQL database it retains that format, and when it travels all the way back it still retains that format, and it is consistent with getting a Now value in the backend if necessary, and …. Other databases can have completely different issues too — they may lack the necessary precision for instance (MySQL for instance), and suddenly you end up preferring to store the value as a double containing ticks instead (read my next blog on separating logic from state for more on how to deal with that).
So after some consideration your start learning about DateTimeKind (Unspecified for the win) and TimeSpan (for mapping to Time only), you learnt how JSON.NET translates that format to json back and from, you learn that some of your html controls always give back local or UTC but not unspecified and compensate for that, slowly you’re getting somewhere, but you wish you’d known all this in advance … .
So, then, what can I do to protect my project from this in the future?
Create an IDateTimeProvider interface, create an ICustomDateTime, implement it, and use it religiously. Add any semantic variation, any type of ‘Now’ operation you need, as a separate method and make sure you give them clear names.
For example, when registering opening hours for a store, we have a store in a fixed location with opening hours relative to the local clock. So create a new method: a GetLocationDateTime (valid for that location). Now things are simpler — as long as I can relate the DateTime to a location and I can relate the location to the correct TimeZone etc., I only need to store a neutral DateTime value.
If I want to keep track of when a user visited that store from the perspective of the store, I need to use that same function. But if I want the user’s perspective (e.g. to keep track of when did I visit what store), then use a GetUserDateTime (this would for instance be from the perspective of where I live rather than where the store was located).
Perhaps I am creating InvoiceProposals, then best put a GetInvoiceDateTime in there for good measure. Another one you will want to think about is whether your Now should be the same now during your REST call / session. In some cases you may want that, in which case you can create a GetSessionDateTime(). You’re going to thank yourself later, even if you did end up doing the right thing and injecting that timestamp from the proper top-level.
“Time is … absolutely relative.”
And remember, when completely unsure of what you may need, consider implementing that GetAwesomeDateTime, the one that saves DateTime, the TimeZone and Daylight Saving Time of the moment that the DateTime was recorded. You should be able to go anywhere from there, as you know exactly what time was meant at exactly the time it was relevant. Any other format is open to interpretation!
Another huge advantage of having the IDateTimeProvider provide all your time related functionality, is that you become the master of now. This is essential for testing. If you want to test if your business logic can handle the last minute of the day, the first or last day of the month, the last day of February, the last day of the year, translations to special days in other calendars, anything: what is nicer than being able to make your mocking framework decide what Date and Time to test with, or even input a whole range.
As for your ICustomDateTime and other logic (say, calculating intervals, validity periods, etc.), they can all still first wrap .NET’s DateTime of course, or perhaps you go for NodaTime right away. But the most important thing here is that now you’ve made sure that you can switch when needed, without having to go through too much code and tests. And not unimportantly, you are free to throw errors when someone is doing something that they shouldn’t … .
“Hopefully saving you some Time here …”
Speaking of which, when it is time to store those dates and times somewhere, don’t forget to check your database provider or ORM’s best practices when you implement them for storage. There are some caveats here too — if you store a DateTime in SQL and your DateTimeKind is UTC, when you next retrieve it back into a DateTime, it will have DateTimeKind Unspecified. This can be pretty … bad, especially when that DateTime goes into Json later (hello four hours of time difference), so in these cases, wrap your fields into some logic that restores the correct DateTimeKind (use an attribute, for instance).
You could also use Date and Time column types separately, and for instance specify your Time to be precise only to the minute (can be handy for calendar purposes, etc.). You can still query them as a combined DateTime value for sorting and map them in a read-only property. With a library like MySQL or SQLLite you may want to just store a Time (or even DateTime) as a double, because you may want more precision rather than less, and their basic types may lack the precision you need. If you do just want to store times, the n use a TimeSpan, which maps to a Time column automatically in for instance EntityFramework. If you happen to use NHibernate, there are some neat tricks you can do with type mappings. Sometimes it seems there’s just no end to the hassle, but as long as you use the right patterns and interfaces, you have enough opportunity to change your implementation to use the right solutions without too much refactoring (refactoring in a large project can get very costly).
“To be, or to have, Time … ?”
Also, when adding DateTime functionality to an object, definitely consider the ‘HasA’ rather than ‘IsA’ approach. For instance, if your objects are going to have a StartDateTime and (optional) EndDateTime indicating how long that object was valid, then create a ValidPeriod object and add that to objects that need it (Tip: in EntityFramework, HasA is best done using a separate object that shares the primary key and is mapped to the same table, as ComplexTypes cannot handle nullable values.)
CONCLUSION
If you take away one thing from all this exposition, then let it be this:
Abstract out your DateTime functions, objects and properties right at the start of your project, so that they can be easily changed if needed, and properly tested. You will thank your future self and be much more Agile when it counts.
(this blog was posted two weeks earlier on medium.com)
Somewhere in the back of my head I am still praying this is not true and that it is solvable with normal Date(for JS) and normal DateTime(for .NET) but the more I read articles on the internet the more I realize I will have to create a custom Date DTO to communicate the date from client to server! Time is not on my side. Damn you Mick Jagger!