Packages
timex
2.1.0
3.7.13
3.7.12
3.7.11
3.7.9
3.7.8
3.7.7
3.7.6
3.7.5
3.7.3
3.7.2
3.7.1
3.7.0
3.6.4
3.6.3
3.6.2
retired
3.6.1
3.6.0
3.5.0
3.4.2
3.4.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.25
retired
3.1.24
3.1.23
3.1.22
3.1.21
3.1.20
3.1.19
3.1.18
3.1.17
3.1.16
3.1.15
3.1.13
3.1.12
3.1.11
3.1.10
3.1.9
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.2.1
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.0
1.0.2
1.0.1
1.0.0
1.0.0-rc4
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
1.0.0-pre
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.2
0.10.1
0.10.0
0.9.0
0.8.0
0.7.1
0.6.0
0.5.0
0.4.8
0.4.7
0.4.6
Timex is a rich, comprehensive Date/Time library for Elixir projects, with full timezone support via the :tzdata package. If you need to manipulate dates, times, datetimes, timestamps, etc., then Timex is for you!
Current section
Files
Jump to
Current section
Files
lib/time/time.ex
defmodule Timex.Time do
@moduledoc """
This module provides a friendly API for working with Erlang
timestamps, i.e. `{megasecs, secs, microsecs}`. In addition,
it provides an easy way to wrap the measurement of function
execution time (via `measure`).
"""
alias Timex.Types
use Timex.Constants
import Timex.Macros
@type units :: :microseconds | :milliseconds | :seconds | :minutes | :hours | :days | :weeks | :hms
@doc """
Converts a timestamp to its value in microseconds
"""
@spec to_microseconds(Types.timestamp) :: integer
def to_microseconds({mega, sec, micro}) do
total_seconds = (mega * @million) + sec
total_microseconds = (total_seconds * 1_000 * 1_000) + micro
total_microseconds
end
defdeprecated to_usecs(timestamp), "use to_microseconds/1 instead", do: to_microseconds(timestamp)
@doc """
Converts a timestamp to its value in milliseconds
"""
@spec to_milliseconds(Types.timestamp) :: float
def to_milliseconds({_, _, _} = ts), do: to_microseconds(ts) / 1_000
defdeprecated to_msecs(timestamp), "use to_milliseconds/1 instead", do: to_milliseconds(timestamp)
@doc """
Converts a timestamp to its value in seconds
"""
@spec to_seconds(Types.timestamp) :: float
def to_seconds({_, _, _} = ts), do: to_milliseconds(ts) / 1_000
defdeprecated to_secs(timestamp), "use to_seconds/1 instead", do: to_seconds(timestamp)
@doc """
Converts a timestamp to its value in minutes
"""
@spec to_minutes(Types.timestamp) :: float
def to_minutes(timestamp), do: to_seconds(timestamp) / 60
defdeprecated to_mins(timestamp), "use to_minutes/1 instead", do: to_minutes(timestamp)
@doc """
Converts a timestamp to its value in hours
"""
@spec to_hours(Types.timestamp) :: float
def to_hours(timestamp), do: to_minutes(timestamp) / 60
@doc """
Converts a timestamp to its value in days
"""
@spec to_days(Types.timestamp) :: float
def to_days(timestamp), do: to_hours(timestamp) / 24
@doc """
Converts a timestamp to its value in weeks
"""
@spec to_weeks(Types.timestamp) :: float
def to_weeks(timestamp), do: (to_days(timestamp) / 365) * 52
Enum.each [{:microseconds, 1 / @usecs_in_sec, :usecs},
{:milliseconds, 1 / @msecs_in_sec, :msecs},
{:seconds, 1, :secs},
{:minutes, @secs_in_min, :mins},
{:hours, @secs_in_hour, :hours},
{:days, @secs_in_day, :days},
{:weeks, @secs_in_week, :weeks}], fn {type, coef, type_alias} ->
@spec to_microseconds(integer | float, unquote(type)) :: float
def to_microseconds(value, unquote(type)), do: do_round(value * unquote(coef) * @usecs_in_sec)
if not type in [:hours, :days, :weeks] do
def to_microseconds(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
to_microseconds(value, unquote(type))
end
end
@spec to_milliseconds(integer | float, unquote(type)) :: float
def to_milliseconds(value, unquote(type)), do: do_round(value * unquote(coef) * @msecs_in_sec)
if not type in [:hours, :days, :weeks] do
def to_milliseconds(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
to_milliseconds(value, unquote(type))
end
end
@spec to_seconds(integer | float, unquote(type)) :: float
def to_seconds(value, unquote(type)), do: do_round(value * unquote(coef))
if not type in [:hours, :days, :weeks] do
def to_seconds(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
to_seconds(value, unquote(type))
end
end
@spec to_minutes(integer | float, unquote(type)) :: float
def to_minutes(value, unquote(type)), do: do_round(value * unquote(coef) / @secs_in_min)
if not type in [:hours, :days, :weeks] do
def to_minutes(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
to_minutes(value, unquote(type))
end
end
@spec to_hours(integer | float, unquote(type)) :: float
def to_hours(value, unquote(type)), do: do_round(value * unquote(coef) / @secs_in_hour)
if not type in [:hours, :days, :weeks] do
def to_hours(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
to_hours(value, unquote(type))
end
end
@spec to_days(integer | float, unquote(type)) :: float
def to_days(value, unquote(type)), do: do_round(value * unquote(coef) / @secs_in_day)
if not type in [:hours, :days, :weeks] do
def to_days(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
to_days(value, unquote(type))
end
end
@spec to_weeks(integer | float, unquote(type)) :: float
def to_weeks(value, unquote(type)), do: do_round(value * unquote(coef) / @secs_in_week)
if not type in [:hours, :days, :weeks] do
def to_weeks(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
to_weeks(value, unquote(type))
end
end
end
Enum.each [:to_microseconds, :to_milliseconds, :to_seconds, :to_minutes, :to_hours, :to_days, :to_weeks], fn name ->
@spec unquote(name)({integer | float, integer | float, integer | float}, :hms) :: float
def unquote(name)({hours, minutes, seconds}, :hms), do: unquote(name)(hours * @secs_in_hour + minutes * @secs_in_min + seconds, :seconds)
end
defdeprecated to_usecs(value, type), "use to_microseconds/2 instead", do: to_microseconds(value, type)
defdeprecated to_msecs(value, type), "use to_milliseconds/2 instead", do: to_milliseconds(value, type)
defdeprecated to_secs(value, type), "use to_seconds/2 instead", do: to_seconds(value, type)
defdeprecated to_mins(value, type), "use to_minutes/2 intead", do: to_minutes(value, type)
@doc """
Converts an hour between 0..24 to {1..12, :am/:pm}
## Examples
iex> Timex.Time.to_12hour_clock(23)
{11, :pm}
"""
def to_12hour_clock(hour) when hour in 0..24 do
case hour do
hour when hour in [0, 24] -> {12, :am}
hour when hour < 12 -> {hour, :am}
hour when hour === 12 -> {12, :pm}
hour when hour > 12 -> {hour - 12, :pm}
end
end
@doc """
Converts an hour between 1..12 in either am or pm, to value between 0..24
## Examples
iex> Timex.Time.to_24hour_clock(7, :pm)
19
"""
def to_24hour_clock(hour, am_or_pm) when hour in 1..12 and am_or_pm in [:am, :pm] do
case am_or_pm do
:am when hour === 12 -> 0
:am -> hour
:pm when hour === 12 -> hour
:pm -> hour + 12
end
end
@doc """
Converts the given input value and unit to an Erlang timestamp.
## Example
iex> Timex.Time.from(1500, :seconds)
{0, 1500, 0}
"""
@spec from(integer | Types.time, units) :: Types.timestamp
def from(value, :usecs) do
IO.write :stderr, "warning: :usecs is a deprecated unit name, use :microseconds instead\n"
from(value, :microseconds)
end
def from(value, :msecs) do
IO.write :stderr, "warning: :msecs is a deprecated unit name, use :milliseconds instead\n"
from(value, :milliseconds)
end
def from(value, :secs) do
IO.write :stderr, "warning: :secs is a deprecated unit name, use :seconds instead\n"
from(value, :seconds)
end
def from(value, :mins) do
IO.write :stderr, "warning: :mins is a deprecated unit name, use :minutes instead\n"
from(value, :minutes)
end
def from(value, :microseconds) do
value = round(value)
{ sec, micro } = mdivmod(value)
{ mega, sec } = mdivmod(sec)
{ mega, sec, micro }
end
def from(value, :milliseconds), do: from(value * @usecs_in_msec, :microseconds)
def from(value, :seconds), do: from(value * @usecs_in_sec, :microseconds)
def from(value, :minutes), do: from(value * @secs_in_min, :seconds)
def from(value, :hours), do: from(value * @secs_in_hour, :seconds)
def from(value, :days), do: from(value * @secs_in_day, :seconds)
def from(value, :weeks), do: from(value * @secs_in_week, :seconds)
def from(value, :hms), do: from(to_seconds(value, :hms), :seconds)
Enum.each [{:microseconds, :usecs},
{:milliseconds, :msecs},
{:seconds, :secs},
{:minutes, :mins},
:hours, :days, :weeks, :hms], fn
{type, type_alias} ->
def to_timestamp(value, unquote(type)), do: from(value, unquote(type))
def to_timestamp(value, unquote(type_alias)) do
IO.write :stderr, "warning: #{unquote(type_alias)} is a deprecated unit name, use #{unquote(type)} instead\n"
from(value, unquote(type))
end
type ->
def to_timestamp(value, unquote(type)), do: from(value, unquote(type))
end
def add({mega1,sec1,micro1}, {mega2,sec2,micro2}) do
normalize({ mega1+mega2, sec1+sec2, micro1+micro2 })
end
def sub({mega1,sec1,micro1}, {mega2,sec2,micro2}) do
normalize({ mega1-mega2, sec1-sec2, micro1-micro2 })
end
def scale({mega, secs, micro}, coef) do
normalize({ mega*coef, secs*coef, micro*coef })
end
def invert({mega, sec, micro}) do
{ -mega, -sec, -micro }
end
def abs(timestamp={mega, sec, micro}) do
value = cond do
mega != 0 -> mega
sec != 0 -> sec
true -> micro
end
if value < 0 do
invert(timestamp)
else
timestamp
end
end
@doc """
Return a timestamp representing a time lapse of length 0.
Time.convert(Time.zero, :seconds)
#=> 0
Can be useful for operations on collections of timestamps. For instance,
Enum.reduce(timestamps, Time.zero, Time.add(&1, &2))
Can also be used to represent the timestamp of the start of the UNIX epoch,
as all Erlang timestamps are relative to this point.
"""
def zero, do: {0, 0, 0}
@doc """
Convert timestamp in the form { megasecs, seconds, microsecs } to the
specified time units.
## Supported units
- :microseconds
- :milliseconds
- :seconds
- :minutes
- :hours
- :days
- :weeks
"""
def convert(timestamp, type \\ :timestamp)
def convert(timestamp, :timestamp), do: timestamp
def convert(timestamp, :microseconds), do: to_microseconds(timestamp)
def convert(timestamp, :milliseconds), do: to_milliseconds(timestamp)
def convert(timestamp, :seconds), do: to_seconds(timestamp)
def convert(timestamp, :minutes), do: to_minutes(timestamp)
def convert(timestamp, :hours), do: to_hours(timestamp)
def convert(timestamp, :days), do: to_days(timestamp)
def convert(timestamp, :weeks), do: to_weeks(timestamp)
def convert(timestamp, :usecs) do
IO.write :stderr, "warning: :usecs is a deprecated unit name, use :microseconds instead\n"
to_microseconds(timestamp)
end
def convert(timestamp, :msecs) do
IO.write :stderr, "warning: :msecs is a deprecated unit name, use :milliseconds instead\n"
to_milliseconds(timestamp)
end
def convert(timestamp, :secs) do
IO.write :stderr, "warning: :secs is a deprecated unit name, use :seconds instead\n"
to_seconds(timestamp)
end
def convert(timestamp, :mins) do
IO.write :stderr, "warning: :mins is a deprecated unit name, use :minutes instead\n"
to_minutes(timestamp)
end
@doc """
Return time interval since the first day of year 0 to Epoch.
"""
def epoch(type \\ :timestamp)
def epoch(:timestamp) do
seconds = :calendar.datetime_to_gregorian_seconds({ {1970,1,1}, {0,0,0} })
{ mega, sec } = mdivmod(seconds)
{ mega, sec, 0 }
end
def epoch(type), do: convert(epoch, type)
@doc """
Time interval since Epoch.
The argument is an atom indicating the type of time units to return (see
convert/2 for supported values).
When the argument is omitted, the return value's format is { megasecs, seconds, microsecs }.
"""
def now(type \\ :timestamp)
def now(:usecs) do
IO.write :stderr, "warning: :usecs is a deprecated unit name, use :microseconds instead\n"
now(:microseconds)
end
def now(:msecs) do
IO.write :stderr, "warning: :msecs is a deprecated unit name, use :milliseconds instead\n"
now(:milliseconds)
end
def now(:secs) do
IO.write :stderr, "warning: :secs is a deprecated unit name, use :seconds instead\n"
now(:seconds)
end
def now(:mins) do
IO.write :stderr, "warning: :mins is a deprecated unit name, use :mins instead\n"
now(:minutes)
end
case Timex.Utils.get_otp_release do
ver when ver >= 18 ->
def now(:timestamp), do: :os.system_time(:micro_seconds) |> from(:microseconds)
def now(:microseconds), do: :os.system_time(:micro_seconds)
def now(:milliseconds), do: :os.system_time(:milli_seconds)
def now(:seconds), do: :os.system_time(:seconds)
def now(type), do: now(:timestamp) |> convert(type)
_ ->
def now(:timestamp), do: :os.timestamp
def now(type), do: :os.timestamp |> convert(type)
end
@doc """
Time interval between timestamp and now. If timestamp is after now in time, the
return value will be negative. Timestamp must be in format:
{ megasecs, seconds, microseconds }.
The second argument is an atom indicating the type of time units to return:
:microseconds, :milliseconds, :seconds, :minutes, or hours (:hours).
When the second argument is omitted, the return value's format is
{ megasecs, seconds, microsecs }.
"""
def elapsed(timestamp, type \\ :timestamp)
def elapsed(timestamp = {_,_,_}, type) do
elapsed(timestamp, now, type)
end
def elapsed(timestamp = {_,_,_}, reference_time = {_,_,_}, type) do
diff(reference_time, timestamp) |> convert(type)
end
@doc """
Time interval between two timestamps. If the first timestamp comes before the
second one in time, the return value will be negative. Timestamp must be in format:
{ megasecs, seconds, microseconds }.
The third argument is an atom indicating the type of time units to return:
:microseconds, :milliseconds, :seconds, :minutes, or :hours
When the third argument is omitted, the return value's format is:
{ megasecs, seconds, microsecs }.
## Examples
iex> use Timex
...> Time.diff({1457, 136000, 785000}, Time.zero, :days)
16865
"""
def diff(t1, t2, type \\ :timestamp)
def diff({_,_,_} = t1, {_,_,_} = t2, :timestamp) do
microsecs = :timer.now_diff(t1, t2)
mega = div(microsecs, 1_000_000_000_000)
secs = div(microsecs - mega*1_000_000_000_000, 1_000_000)
micro = rem(microsecs, 1_000_000)
{mega, secs, micro}
end
def diff(t1 = {_,_,_}, t2 = {_,_,_}, type) do
trunc(convert(diff(t1, t2), type))
end
@doc """
Evaluates fun() and measures the elapsed time.
Returns {timestamp, result}, timestamp is the usual `{ megasecs, seconds, microsecs }`.
## Example
iex> {_timestamp, result} = Time.measure(fn -> 2 * 2 end)
...> result == 4
true
"""
@spec measure((() -> any)) :: { Types.timestamp, any }
def measure(fun), do: do_measure(fun)
@doc """
Evaluates apply(fun, args). Otherwise works like measure/1
"""
@spec measure(fun, [any]) :: { Types.timestamp, any }
def measure(fun, args), do: do_measure(fun, args)
@doc """
Evaluates apply(module, fun, args). Otherwise works like measure/1
"""
@spec measure(module, atom, [any]) :: { Types.timestamp, any }
def measure(module, fun, args), do: do_measure(module, fun, args)
case Timex.Utils.get_otp_release do
ver when ver >= 18 ->
defp do_measure(m, f \\ nil, a \\ []) do
start_time = :erlang.monotonic_time(:micro_seconds)
result = cond do
is_function(m) && f == nil -> apply(m, [])
is_function(m) && is_list(f) -> apply(m, f)
is_atom(m) && is_atom(f) && is_list(a) -> apply(m, f, a)
true -> {:error, "Invalid arguments for do_measure!"}
end
end_time = :erlang.monotonic_time(:micro_seconds)
{(end_time - start_time) |> to_timestamp(:microseconds), result}
end
_ ->
defp do_measure(m, f \\ nil, a \\ []) do
{time, result} = cond do
is_function(m) && f == nil -> :timer.tc(m)
is_function(m) && is_list(f) -> :timer.tc(m, f)
is_atom(m) && is_atom(f) && is_list(a) -> :timer.tc(m, f, a)
true -> {:error, "Invalid arguments for do_measure!"}
end
{to_timestamp(time, :microseconds), result}
end
end
defp normalize({mega, sec, micro}) do
# TODO: check for negative values
{ sec, micro } = mdivmod(sec, micro)
{ mega, sec } = mdivmod(mega, sec)
{ mega, sec, micro }
end
defp divmod(a, b) do
{ div(a, b), rem(a, b) }
end
defp divmod(initial, a, b) do
{ initial + div(a, b), rem(a, b) }
end
defp mdivmod(a) do
divmod(a, 1_000_000)
end
defp mdivmod(initial, a) do
divmod(initial, a, 1_000_000)
end
defp do_round(value) when is_integer(value), do: value
defp do_round(value) when is_float(value), do: Float.round(value, 6)
end