Unix Timestamps in Elixir

Lately, there were several people asking on IRC how to deal with unix timestamps in Elixir, so I decided to write a little bit more about this.

What should I use?

Elixir’s standard library doesn’t have any modules dealing with times, dates or any of that, but we can always reach out to Erlang. In Erlang there are basically three modules dealing with dates and times:

  • The os, and erlang modules, where, we can find functions that can help us with what I’ll call “machine time”. As a base format they use the :erlang.timestamp type (defined as three element tuples {megaseconds, seconds, microseconds}), or simple integers. They operate assuming 01.01.1970 as the beginning of the time.
  • The calendar module, that deals with human-readable dates and times. It uses date tuples in format of {year, month, day} and time tuples in format of {hour, minute, second} or even datetime tuples combining the two: {date_tuple, time_tuple}. It assumes the beginning of the time was 01.01.0000 as defined by the ISO 8601 standard (year 0 in fact being 1 BC).

If you need anything more than simple conversions, or need to deal with time zones, I would recommend using a proper library for that, like the excellent Timex. But when all you need is a simple conversion here and there, pulling in an entire library for one or two functions may be excessive.

Converting timestamps

That’s probably the most frequent use case - you need to convert between the datetime tuples and timestamps. You can achieve that using the calendar module.


defmodule Convert do
  epoch = {{1970, 1, 1}, {0, 0, 0}}
  @epoch :calendar.datetime_to_gregorian_seconds(epoch)

  def from_timestamp(timestamp) do
    timestamp
    |> +(@epoch)
    |> :calendar.gregorian_seconds_to_datetime
  end

  def to_timestamp(datetime) do
    datetime
    |> :calendar.datetime_to_gregorian_seconds
    |> -(@epoch)
  end
end

If you’re looking to convert those between Ecto types in Phoenix, you can use Ecto.DateTime.to_erl/1 and Ecto.DateTime.from_erl/1 as additional steps in the pipeline.

Generating timestamps

If you’re on Erlang 18 the easiest way is to use newly introduced function, where you can specify the unit you want to receive:

def timestamp do
  :os.system_time(:seconds)
end

There are also other formats allowed: :milli_seconds, :micro_seconds, :nano_seconds, and :native (as used internally by the Erlang runtime system). You can also use a parts_per_second integer (:seconds is just alias to 1, :milli_seconds is just an alias to 1000, etc.).

If you’re on an older Erlang system, you can use :os.timestamp:

def timestamp do
  {megasec, sec, _microsec} = :os.timestamp
  mega * 1000000 + sec
end

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.