The Unix Timestamps in Elixir post is by far my most popular one. Because a lot changed in recent Elixir versions when it comes to handling of calendar types (such as dates, datetimes and times), I though it might be a good idea to update it.
What should I use?
Elixir standard library provides structs for all important calendrical types
(Date
, DateTime
, NaiveDateTime
and Time
) and some basic functions to
operate on them. More advanced functions for manipulating the structs are
provided by third-party libraries, such
as calendar
or timex. The important
thing, though is that all the libraries use the same types and are, therefore
composable and interchangeable. It’s not crazy to see some code like this for
producing a Date
struct to finally persist it as part of an ecto schema (as
:date
type):
"Sat, 06 Sep 2014 09:09:08 GMT"
|> Calendar.DateTime.Parse.httpdate!
|> DateTime.to_date
|> Timex.shift(years: -1)
DateTime vs NaiveDateTime
The “naive” part of the NaiveDateTime
refers to the fact that this
representation does not include a timezone. This means that due to some factors
(such as daylight saving time or timezone changes) the datetime may not describe
any point in time in certain areas of the world, or may be ambiguous and
describe multiple points in time in other areas, even though the values in the
struct are valid.
The DateTime
struct provides a similar type, but it includes the timezone
information. This means the ambiguities of the NaiveDateTime
are not a concern,
because the values are verified against the timezone database. Because of that,
creating the values directly should be avoided and functions from third party
libraries should be used. The standard library in Elixir itself does not contain
the timezone database and only provides support for creating datetimes in the
Etc/UTC
timezone.
Converting timestamps
That’s probably the most frequent use case - you need to convert between a
datetime tuples and timestamps. You can achieve that using the
DateTime.from_unix/1
and DateTime.from_unix!
functions:
iex> DateTime.from_unix(1486035766)
{:ok,
%DateTime{calendar: Calendar.ISO, day: 2, hour: 11,
microsecond: {0, 0}, minute: 42, month: 2, second: 46,
std_offset: 0, time_zone: "Etc/UTC",
utc_offset: 0, year: 2017, zone_abbr: "UTC"}}
iex> DateTime.from_unix!(1486035766)
%DateTime{calendar: Calendar.ISO, day: 2, hour: 11,
microsecond: {0, 0}, minute: 42, month: 2, second: 46,
std_offset: 0, time_zone: "Etc/UTC",
utc_offset: 0, year: 2017, zone_abbr: "UTC"}
# Timestamps until -62167219200,
# that is "0000-01-01T00:00:00Z" are supported
iex> DateTime.from_unix!(-62167219300)
** (ArgumentError) invalid Unix time -62167219300
(elixir) lib/calendar.ex:1513: DateTime.from_unix!/2
Those can be used directly in Ecto 2.1 and Phoenix with the :utc_datetime
type.
The reverse conversion can be achieved using DateTime.to_unix/1
function:
iex> DateTime.to_unix(%DateTime{calendar: Calendar.ISO, day: 2,
hour: 11, microsecond: {0, 0}, minute: 42, month: 2,
second: 46, std_offset: 0, time_zone: "Etc/UTC",
utc_offset: 0, year: 2017, zone_abbr: "UTC"})
1486035766
If you have a %NaiveDateTime{}
struct, it needs to be first forced to a
DateTime
in the Etc/UTC
timezone, before the conversion:
iex> dt = DateTime.from_naive(~N[2017-02-02 11:42:46], "Etc/UTC")
iex> DateTime.to_unix(dt)
1486035766
Generating timestamps
There are two ways of creating timestamps:
- create a
DateTime
struct first, and dump to the timestamp - use the
System.system_time/1
function to get the timestamp directly
iex> DateTime.utc_now |> DateTime.to_unix
1486035766
iex> System.system_time(:second)
1486035766
Different units
All the conversion functions support passing a unit as an extra argument.
The supported units are: :native
, :second
, :millisecond
, :microsecond
,
:nanosecond
. The :native
value is a special one that means the highest
resultion available to the runtime system. When precise operations on timestamps
are needed, they should be performed on the :native
values and converted to a
specific unit, as the last step, using the System.convert_time_unit/3
function.
Further reading
If you want to learn more about how time works in the Erlang runtime system, I can’t recommend enough learn you some Erlang’s chapter on time. You should also look at the Erlang’s documentation explaining different times (yes there are more then one!) the Erlang runtime system deals with and the Elixir’s documentation of the appropriate types.