This was part of a spec that had been working fine until I upgraded Ecto to 2.1, and swapped out Ecto.DateTime for elixir’s DateTime. After upgrading to 1.4.0, I tried DateTime.compare, which seemed to work
The arguments can be of different data types. The following order is defined:
number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string
Lists are compared element by element. Tuples are ordered by size, two tuples with the same size are compared element by element.
Maps are ordered by size, two maps with the same size are compared by keys in ascending term order and then by values in key order. In maps key order integers types are considered less than floats types.
When comparing an integer to a float, the term with the lesser precision is converted into the type of the other term, unless the operator is one of =:= or =/=. A float is more precise than an integer until all significant figures of the float are to the left of the decimal point. This happens when the float is larger/smaller than +/-9007199254740992.0. The conversion strategy is changed depending on the size of the float because otherwise comparison of large floats and integers would lose their transitivity.
Term comparison operators return the Boolean value of the expression, true or false.
So since DateTime is a map, it will sort the keys in term order, and microsecond is before minute. Once it finds a key that disgrees it stops searching.
It is possible to make structs that will sort properly with the default Erlang term ordering, but it is quite tricky and often leads to many strange edge cases and odd naming conventions.
Do note, the ‘nil’ in Erlang is not the ‘nil’ in Elixir. The nil in Elixir is just an atom, nothing special about it whatsoever (I really wish Elixir did not confuse these two things). The ‘nil’ in Erlang is the empty list in Elixir [].