all Technical posts

Implementing and Property-Based Testing Reusable Date Operations in F#

Dates are a recurring and tedious problem when we describe domain models and tests. With some abstract thinking, we can extract this functionality in reusable components, solving the problem once and for all.

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

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!