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.
UPDATE: Since this blog post was created a lot changed and getting timestamps and managing calendar types (dates, datetimes, times) got a lot easier in Elixir in general. Consider checking out the updated version of this post
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
, anderlang
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
megasec * 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.