Dates: ISO8601 or pain
When sending date-time data between systems, your options fall into two general categories:
- ISO8601 with an explicit timezone, or
- A world of pain.
Do you want a world of pain? I didn’t think so.
ISO8601
{
"OrderId": 123456,
"OrderDue": "2017-04-05T14:30Z"
}
The order is due at 2:30PM UTC on the 5th of April, 2017.
If you can keep the time zone in which something happened, then keep it, it’s valuable information.
Field ordering
ISO8601 is a standard format where all parts of the date or datetime are laid out in a structured way, largest to smallest. It is human readable and can be unambiguously parsed by code.
Largest to smallest is also a logical ordering: When measuring duration, would you say “seven hours, three days and fifteen minutes”? Of course not, the order largest to smallest makes the most logical sense, so you’d say “three days, seven hours and fifteen minutes”.
When measuring distance, would you say “Two feet, three yards and an inch”? Of course not, and not just because you should use the metric system.
Largest to smallest ordering has the pleasing side-effect that an alphabetic sort of these values is also a chronological sort.
Using in .Net code
For .Net code, an ISO8601 Datetime maps most closely to the DateTimeOffset
structure. You should stop using DateTime
and use DateTimeOffset
almost everywhere - The docs say “DateTimeOffset should be considered the default date and time type for application development”.
What does this code output?
Console.WriteLine(new DateTime(1970, 1, 1).ToUniversalTime());
I am in the UK, and I get: 01/01/1970 00:00:00
Someone in Australia gets: 31/12/1969 14:00:00
And someone running .Net Core on Linux in the UK gets 31/12/1969 23:00:00
because that system knows about the permanent summer time of 1968-1971. This code is ambiguous, and your results may vary.
While these details are interesting, I am not suggesting that you cultivate a John-Skeet-like knowledge of the esoteric details of time zones in order to work out how your code will behave in production.
I am suggesting that you instead write simple and unambiguous code by using an explicit time zone in a DateTimeOffset
.
e.g. Console.WriteLine(new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero));
.
Links
Read the excellent Wikipedia page, the RFC, or get the specification itself.
There is a lot more to ISO8601, e.g there’s also a syntax for expressing durations. I am not going to define it all here, just convince you that it is the right way.
Other options
And why they suck.
UK or US date format
These are the twins: DD/MM/YYYY
and MM/DD/YYYY
. You might be used to reading one of them, but they are terrible for data interchange as they look the same but mean different things. Use one of these formats only if you want these problems caused by confusing them:
- Differences in date parsing functionality between different machines with different system settings. What works on one machine may or may not work on another machine, and may or may not give the same result.
- Code that works for the first 12 days of the month only, and then fails.
- Stored values that might mean one of two completely different dates, depending on how they were parsed.
- How does time zone info fit into these formats anyway?
In short, unless you want a world of pain, avoid these date formats for data interchange and storage.
Epoch seconds
This format may be fine for system administration uses, or e.g. timestamping log entries. But it is not suitable for recording most business date and time values because:
-
Epoch values are not human readable. Don’t underestimate this. Many bugs are caught because anyone can see that the value is wrong. This won’t happen with epoch dates.
-
Epoch values do not store time zone info at all. 10AM in London coincides with 11AM in Paris, but there are cases when the difference is significant.
-
Epoch values are always UTC. But did you know that before today? There is high probability that someone can get this implicit rule wrong when generating a value, or when reading it. Explicit, human-readable time zone is better.
-
Epoch values are always the same precision. What if you want to express “it happened on the 12th of Feb, 2017, all day” - in ISO8601 this is
2017-02-12
, but in epoch date you have to specify the time of day as well. Probably you’ll use an implicit convention that midnight at the start of day stands in for the whole day, right? Midnight in UTC or in your local time zone? Now you have implicit rules that someone will get wrong, and you can’t store both “just the date” and “actual precise midnight value”. A timezone issue one hour backwards is likely to push the value not just to the wrong hour but to the wrong day. The worst case is that you find this out when the clocks change, up to six months later.
The common theme is that there are implicit decisions and ways to miscommunicate that the single integer value of the epoch date does not capture.
Summary
Save yourself a whole lot of trouble - insist that data is sent in ISO8601 format with an explicit time zone.