Elixir's standard library offers 4 different modules for working with dates and time, each with its own struct.
The Date module. A Date struct can be created with the ~D sigil.
~D[2021-01-01]
The Time module. A Time struct can be created with the ~T sigil.
~T[12:00:00]
The NaiveDateTime module for datetimes without a timezone. A NaiveDateTime struct can be created with the ~N sigil.
~N[2021-01-01 12:00:00]
The DateTime module for datetimes with a timezone. Using this module for timezones other than UTC requires an external dependency, a timezone database. A DateTime struct can be represented with the ~U sigil, but should be created using DateTime functions instead.
DateTime.new!(~D[2021-01-01], ~T[12:00:00], "Etc/UTC")
# => ~U[2021-01-01 12:00:00Z]
To compare dates or times to one another, look for a compare or diff function in the corresponding module. Comparison operators such as ==, >, and < seem to work, but they don't do a correct semantic comparison for those structs.
Date.compare(~D[2020-11-30], ~D[2020-12-01])
# => :lt
Time.diff(~T[13:45:00], ~T[13:46:30])
# => -90
Dates, time, and datetimes can be shifted forwards and backwards in time using the add/2 function from the corresponding module.
# add 4 days
Date.add(~D[2021-01-01], 4)
# => ~D[2021-01-05]
# subtract 1 second
Time.add(~T[12:00:00], -1)
# => ~T[11:59:59.000000]
# add 4 days and 30 seconds
NaiveDateTime.add(~N[2021-01-01 12:00:00], 4 * 24 * 60 * 60 + 30)
# => ~N[2021-01-05 12:00:30]
A NaiveDateTime struct can be deconstructed into a Date struct and a Time struct using NaiveDateTime.to_date/1 and NaiveDateTime.to_time/1. The opposite can be achieved with NaiveDateTime.new!/2.
NaiveDateTime.to_date(~N[2021-01-01 12:00:00])
# => ~D[2021-01-01]
NaiveDateTime.to_time(~N[2021-01-01 12:00:00])
# => ~T[12:00:00]
NaiveDateTime.new!(~D[2021-01-01], ~T[12:00:00])
# => ~N[2021-01-01 12:00:00]