DateTime comparison gotcha in Elixir
I wrote a simple function in elixir that checks if a given date is before another date and if the difference in milliseconds between the two dates is less than a given threshold.
The problem
Here is my naive implementation:
Looks fine right? Well, it’s not. While the threshold check works as expected, the date comparison does not. Let’s take a look at what happens when we give it two dates that are a few milliseconds apart and in the correct order:
That’s not what we expected. The dates are in the correct order and the difference is less than 10,000 milliseconds.
The DateTime documentation states the following:
Remember, comparisons in Elixir using
==/2
,>/2
,</2
and friends are structural and based on the DateTime struct fields. For proper comparison between datetimes, use thecompare/2
function.
That’s it, a date such as ~U[2024-01-17 15:05:08.997288Z]
is represented as a DateTime
struct and that’s what we are comparing. The DateTime
struct has the following fields:
According to the elixir documentation regarding structural comparison the fields are compared in the order seen above. In our example the only differing fields are microsecond
and second
. The microsecond
field is compared before the second
field and since the .997288
part in first date is greater than the .011286
part in the second date, the first date is considered greater than the second date even though the second date is actually greater than the first date.
The solution
As the documentation states, the comparison operators in Elixir are structural and based on the DateTime struct fields and we need to use the DateTime.compare/2
function instead of the comparison operators. DateTime.compare/2
returns :lt
if the first date is less than the second date, :gt
if the first date is greater than the second date and :eq
if the dates are equal.
Et voilà!
Conclusion
The ~U
sigil is a great way to create DateTime
structs in Elixir but it’s important to remember that the a date like ~U[2024-01-17 15:05:08.997288Z]
is not a primitive term but a DateTime
struct with fields that are compared in a specific order.