Elixir — Sort Lists by Date

How to Sort Lists by Date using Elixir - Coletiv Blog

Currently, if you need to use Enum.sort on your Elixir project and want to sort lists by date, you may experience problems depending on the Elixir version you are using.

Recently, while working on a project at Coletiv, I needed to sort a list containing structs by their creation date.

iex(2)> items
    [
      %{id: 2, inserted_at: ~D[2019-01-01]},
      %{id: 1, inserted_at: ~D[2018-01-31]},
      %{id: 3, inserted_at: ~D[2020-03-01]}
    ]

First Try

My first approach was to use the Enum.sort function and compare the struct’s inserted date property:

[]|> Enum.sort(&(&1.inserted_at > &2.inserted_at))

After some tests, I found that the sorting was not working. Why? It was supposed to be an easy fix 🤔

Let’s go to IEx to do some tests.

Erlang/OTP 21 [erts-10.0.8] [source] [64-bit]

Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> dates = [~D[2018-01-31],~D[2019-02-01],~D[2020-03-01]]
[~D[2018-01-31], ~D[2019-02-01], ~D[2020-03-01]]
iex(2)> dates |> Enum.sort()
[~D[2019-02-01], ~D[2020-03-01], ~D[2018-01-31]]
iex(3)> dates |> Enum.sort(& &1 > &2)
[~D[2018-01-31], ~D[2020-03-01], ~D[2019-02-01]]
iex(4)> dates |> Enum.sort(& &1 < &2)
[~D[2019-02-01], ~D[2020-03-01], ~D[2018-01-31]]

Strange…

Second Try

After some investigation, I found this article and the possible problem here.

The collection types are compared using the following rules:

- Tuples are compared by size, then element by element.

- Maps are compared by size, then by keys in ascending term order, then by values in key order. In the specific case of maps’ key ordering, integers are always considered to be less than floats.

- Lists are compared element by element.

- Bitstrings are compared byte by byte, incomplete bytes are compared bit by bit.

Considering that a date is a struct, it’s normal that the order was wrong. So the possible solution is:

iex(5)> dates |> Enum.sort_by(fn d -> {d.year, d.month, d.day} end)
[~D[2018-01-31], ~D[2019-02-01], ~D[2020-03-01]]

For previous versions of elixir (like elixir v1.7.3), this is the solution. A little rudimentary but it works.

Third Try

In elixir v1.10, Jose Valim and his team introduce improvements to sort-based APIs in Enum, as you can see here.

Let’s go to IEx again to do some tests…

Erlang/OTP 21 [erts-10.0.8] [source] [64-bit]

Interactive Elixir (1.10.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> dates = [~D[2018-01-31],~D[2019-02-01],~D[2020-03-01]]
[~D[2018-01-31], ~D[2019-02-01], ~D[2020-03-01]]
iex(2)> dates |> Enum.sort()
[~D[2019-02-01], ~D[2020-03-01], ~D[2018-01-31]]
iex(3)> dates |> Enum.sort(&(Date.compare(&1, &2) != :lt))
[~D[2020-03-01], ~D[2019-02-01], ~D[2018-01-31]]
iex(4)> dates |> Enum.sort(Date)
[~D[2018-01-31], ~D[2019-02-01], ~D[2020-03-01]]
iex(5)> dates |> Enum.sort({:desc, Date})
[~D[2020-03-01], ~D[2019-02-01], ~D[2018-01-31]]
iex(6)> dates |> Enum.sort({:asc, Date})
[~D[2018-01-31], ~D[2019-02-01], ~D[2020-03-01]]

Voilá 🎉 The improvements to sort-based API’s in Enum made sorting by date a much beautiful and clean solution.

Lesson learned

In Elixir, as well as in programming in general, the quick solutions don’t always have the expected result and we have to carefully test each solution to make sure it behaves as expected.

Thank you for reading!

Thank you so much for reading, it means a lot to us! Also don’t forget to follow Coletiv on Twitter and LinkedIn as we keep posting more and more interesting articles on multiple technologies.

In case you don’t know, Coletiv is a software development studio from Porto specialised in Elixir, Web, and App (iOS & Android) development. But we do all kinds of stuff. We take care of UX/UI design, software development, and even security for you.

So, let’s craft something together?