The date problem
Dates are a complex problem in themselves. We format and talk differently about dates in different parts of the world. Even Western countries sometimes use different formats or different days to start the week. With varying numbers of days in each month, leap years and much more, there are a lot of moving parts that are not always expressed easily in code. The .NET library already has some basic date functionality in the form of the DateTimeOffset
type, which allows us to describe dates in a uniform UTC format across the world. But the problem does not end there.
If we talk in the context of the domain, we could be using common terms like ‘next week’, ‘within 14 days’, ‘should not be apart more than 1 month’, and so on. The domain often talks in higher, more abstract terms which our regular DateTimeOffset
type does not easily handle. As an experiment within my own F# FPrimitive domain library, I wanted to see if I could fix this problem by bridging the gap that exists between technical implementations and social conventions.
Date operations
The first thing I did was extract the different measurements in which we describe time — the largest being a year and the smallest, a millisecond. This extraction immediately shows the internal structure of any universal date or time format and will help us later if we talk about other date operations.
The logical next thing from a F# perspective is to use this measurement to describe the add/subtract, get/set functionality for the DateTimeOffset
and common values like UTC now.
🚩 All date operations will be collected in the System.Date
module from now on.
Until now, there is no real added value besides a F#-friendly way of manipulating simple date values by their measurements.
Date trimming
One of the common things with dates is that comparing them is rather cumbersome. In our rational mind, if two dates have the same year/month/day, they are the same. But that is not the case with pure technical operations. Even a single millisecond is technically a different date. Trimming a date is one of the common things we do before comparing.
⚡ Note that we can re-use our date measurements here. We can create our own ‘equalization’ based on where the fault margin should be.
European weeks
As Europeans, we see Monday as the first day of the week. If we talk about ‘next week’, this means all of the days from Monday to Sunday. But Americans start the week with Sunday, and as .NET is by-default an American system, Sunday is used as the first day internally. Week operations are therefore a little trickier but definitely a worthwhile date operation feature.
Date range
This brings us to another one of those situations that we find ourselves in during testing. Valid dates are usually between a min/max boundary, but no default way to generate dates exists, or a custom date measurement.
Date difference
Knowing the difference between two dates can also be very interesting, especially if you know that a regular built-in .NET difference does not take into account years and months. This is because the result is a TimeSpan
with a maximum measurement of days. It is strange to think that there is not even a built-in .NET way to know the number of months between two dates, which seems like common knowledge.
The following example shows how we use a dedicated DateDiff
type that has an amount for each date measurement difference.
Domain modelling
Now that we have some reusable date operations, we can look at how we can incorporate them into a domain model. I am going to reuse the restaurant model from my previous posts, where we have a reservation date model that only allows a date representation within two weeks.
💡 See how we can not only simply use the ‘week’ abstraction in our model validation — we can even incorporate a sanitization on the input to trim on the day. Sanitization should probably be more closely related to the DTO’s (data-transfer models), but for the sake of this exercise, these are added here. See my other post on how we can easily include sanitization in our dto-domain translation.
To finish this exercise, here are some test properties to show you how we can easily test our restaurant code with these date operations.
Notice that all the complexity is hidden behind generator code. We only have to focus on the domain in the test properties.
Having these reusable date operations allows us to easily talk about dates in our generator code. The complexity only exists around the domain, instead of in figuring out how to generate dates.
Conclusion
Handling dates in code is a regularly occurring problem that I wanted to fix once and for all. The built-in .NET functionality does not provide easy usage of dates in the way that is described here in this post.
I have published my findings in this GitHub repository, where you will also find extensions for C# developers. Feel free to use it in any way you see fit. This will also be a valuable addition to my other FPrimitive library, where domain modeling is made easy.
Thanks for reading!
Stijn
Subscribe to our RSS feed