Unix Timestamps in Elixir 1.4

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.

Where is my comment box!?

I don't do traditional comments, but you're welcome to send me an email to michal at muskala dot eu, and I'll publish it at the bottom of the article as a comment