Periods (Periods v0.2.0)

Copy Markdown View Source

Periods increments of time.

Periods are designed to represent fractions of time from milliseconds to decades in order to help make working with various time based structs easier.

Periods can be converted, added, and subtracted.

By default the base value is a second you can choose a different base for your application by setting the :default_value in you Application config.

The only limitation are months. Due to the inconsistency of a period of a month from 28-31 days conversion and mathematical operations are limited.

Summary

Functions

Returns a Period with the absolute value of its amount. The unit is unchanged.

Add a Period to a Period, Time, Date, DateTime, or NaiveDateTime.

Returns a list of all the current values Periods supports

Compare two Periods. Returns :lt, :eq, or :gt.

Convert a Period from one unit to another unit.

Converts and returns a %Period{} or raise error.

Returns the value set by your Application environment or the default :second

Get the difference of Time, Date, DateTime, NaiveDateTime in a Period.

Multiply a Period's amount by an integer scalar. The unit is unchanged.

Negate a Period's amount (flip its sign). The unit is unchanged.

Create a new Period through a variety of input types.

Creates a new Period.

Creates a new Period or raises an error.

Creates a new Period from an amount and unit or raises an error.

Subtract a Period from a Time, Date, DateTime, or NaiveDateTime.

Output a Period as an Integer with optional conversion unit.

Output a Period as a String with optional conversion unit.

Types

amount()

@type amount() :: integer() | String.t()

computation_type()

@type computation_type() ::
  Periods.Period.t() | Time.t() | Date.t() | DateTime.t() | NaiveDateTime.t()

difference_type()

@type difference_type() :: Time.t() | Date.t() | DateTime.t() | NaiveDateTime.t()

error_type()

@type error_type() :: {:error, atom()} | {:error, {atom(), atom()}}

parse_type()

@type parse_type() ::
  %{amount: amount(), unit: unit()} | {amount(), unit()} | amount()

unit()

@type unit() :: atom() | String.t()

Functions

abs(period)

Returns a Period with the absolute value of its amount. The unit is unchanged.

Examples

iex> Periods.abs(%Period{amount: -10, unit: :day})
%Periods.Period{amount: 10, unit: :day}

add(value_1, value_2)

Add a Period to a Period, Time, Date, DateTime, or NaiveDateTime.

Note: Months have limited additive properties

When performing addition of periods all values will be converted to the lowest unit value. For example if you add a Period of days to a Period of seconds then the result will be seconds.

When adding a period to an Elixir date/time struct the return will be the Elixir date/time struct.

Note: Since there is conversion involved you may loose fractional values such as adding 1 millisecond to a Date.

Examples

iex> Periods.add(%Period{amount: 10, unit: :day}, %Period{amount: 1000, unit: :second})
%Period(amount: 865000, unit: :second)

iex> DateTime.utc_now() |> Periods.add(%Period{amount: 1000, unit: :second})
~U[2023-07-09 14:14:50.896089Z]

iex> Time.utc_now() |> Periods.add(%Period{amount: 1000, unit: :second})
~T[14:17:04.932992]

iex> today = Date.utc_today()
~D[2023-07-09]
iex> Periods.add(today, %Period{amount: 1, unit: :millisecond})
~D[2023-07-09]

all_units()

@spec all_units() :: [atom()]

Returns a list of all the current values Periods supports

Examples

iex> Periods.all_units()
[:millisecond, :second, :minute, :hour, :day, :week, :month, :year, :decade]

compare(period_1, period_2)

@spec compare(Periods.Period.t(), Periods.Period.t()) ::
  :lt | :eq | :gt | error_type()

Compare two Periods. Returns :lt, :eq, or :gt.

Both Periods are converted to their lowest common unit before comparing, so units of different sizes compare correctly. Because this returns :lt | :eq | :gt, it can be handed to Enum.sort/2 directly as Enum.sort(periods, Periods).

Note: :month can only be compared with :month, :year, and :decade. Comparing a month with a smaller unit returns an error.

Examples

iex> Periods.compare(%Period{amount: 1, unit: :day}, %Period{amount: 1000, unit: :second})
:gt

iex> Periods.compare(%Period{amount: 60, unit: :second}, %Period{amount: 1, unit: :minute})
:eq

iex> Periods.compare(%Period{amount: 1, unit: :second}, %Period{amount: 1, unit: :month})
{:error, :invalid_unit_type}

convert(period, unit)

@spec convert(Periods.Period.t(), atom() | String.t()) ::
  Periods.Period.t() | error_type()

Convert a Period from one unit to another unit.

Note: Months have limited conversion properties

When converting from a lower unit such as milliseconds to a higher unit such as seconds, since all return values are integers, you will loose precision due to rounding. In some conversion scenarios you may get a 0 value.

Examples

iex> Periods.convert(%Period{amount: 10, unit: :day}, :second)
%Periods.Period{amount: 864000, unit: :second}

iex> Periods.convert(%Period{amount: 864000, unit: :second}, :day)
%Period{amount: 10, unit: :day}

iex> Periods.convert(%Period{amount: 10, unit: :year}, :month)
%Periods.Period{amount: 120, unit: :month}

iex> Periods.convert(%Period{amount: 10, unit: :second}, :week)
%Periods.Period{amount: 0, unit: :week}

iex> Periods.convert(%Period{amount: 1000, unit: :second}, :month)
{:error, {:cannot_convert_to_month, :month}}

convert!(period, unit)

@spec convert!(Periods.Period.t(), atom() | String.t()) ::
  Periods.Period.t() | Periods.Conversion.ConversionError

Converts and returns a %Period{} or raise error.

Same as convert/2 excepts raises a %ConversionError{} when unsuccessful

Examples

iex> Periods.convert!(%Period{amount: 10, unit: :day}, :second)
%Periods.Period{amount: 864000, unit: :second}

iex> Periods.convert!(%Period{amount: 1000, unit: :second}, :month)
** (Periods.Conversion.ConversionError) unit: bad type

default_unit()

@spec default_unit() :: atom()

Returns the value set by your Application environment or the default :second

Examples

iex> Periods.default_unit()
:second

diff(difference_type_1, difference_type2, unit \\ nil)

@spec diff(difference_type(), difference_type(), unit()) ::
  {:ok, Periods.Period.t()} | error_type()

Get the difference of Time, Date, DateTime, NaiveDateTime in a Period.

Optional add unit for conversion. Some units will loose precision because fractional units are not supported. For example converting milliseconds to days will round to integer day.

Default conversion units will be dependant upon the struct.

When getting the difference between two DateTimes with different timezones diff/3 will convert both timezones to UTC then calculate the difference.

For DateTime currently using the Tz hex package to help adjust timezones.

In order to use Timezone conversion please add to your application config:

config :elixir, :time_zone_database, Tz.TimeZoneDatabase

Examples

iex> Periods.diff(~T[21:30:43], ~T[13:50:17])
{:ok, %Periods.Period{amount: 27626, unit: :second}}

iex> Periods.diff(~D[2000-03-15], ~D[2000-01-01])
{:ok, %Periods.Period{amount: 74, unit: :day}}

iex> Periods.diff(~D[2000-03-15], ~D[2000-01-01], :second)
{:ok, %Periods.Period{amount: 6393600, unit: :second}}

iex> Periods.diff(#DateTime<2023-05-21 18:23:45.023+09:00 JST Asia/Tokyo>, #DateTime<2023-04-13 13:50:07.003+07:00 +07 Asia/Bangkok>)
{:ok, %Periods.Period{amount: 3292418, unit: :second}}

multiply(period, scalar)

@spec multiply(Periods.Period.t(), integer()) :: Periods.Period.t()

Multiply a Period's amount by an integer scalar. The unit is unchanged.

Examples

iex> Periods.multiply(%Period{amount: 10, unit: :day}, 3)
%Periods.Period{amount: 30, unit: :day}

negate(period)

@spec negate(Periods.Period.t()) :: Periods.Period.t()

Negate a Period's amount (flip its sign). The unit is unchanged.

Examples

iex> Periods.negate(%Period{amount: 10, unit: :day})
%Periods.Period{amount: -10, unit: :day}

iex> Periods.negate(%Period{amount: -5, unit: :second})
%Periods.Period{amount: 5, unit: :second}

new(value)

@spec new(parse_type()) :: {:ok, Periods.Period.t()} | error_type()

Create a new Period through a variety of input types.

Examples

iex> Periods.new(%{amount: 100, unit: :second})
{:ok, %Periods.Period{amount: 100, unit: :second}}

iex> Periods.new(%{amount: "100", unit: "second"})
{:ok, %Periods.Period{amount: 100, unit: :second}}

iex> Periods.new({100, "second"})
{:ok, %Periods.Period{amount: 100, unit: :second}}

iex> Periods.new("100")
{:ok, %Periods.Period{amount: 100, unit: :second}}

new(amount, unit)

@spec new(amount(), unit()) :: {:ok, Periods.Period.t()} | error_type()

Creates a new Period.

Examples

iex> Periods.new(100, :second)
{:ok, %Periods.Period{amount: 100, unit: :second}}

iex> Periods.new("100", "second")
{:ok, %Periods.Period{amount: 100, unit: :second}}

new!(value)

@spec new!(parse_type()) :: Periods.Period.t() | Periods.Parser.ParserError

Creates a new Period or raises an error.

Same as new/1 excepts raises a %ParserError{} when unsuccessful

Examples

iex> Periods.new!({100, "second"})
%Periods.Period{amount: 100, unit: :second}

iex> Periods.new!(%{amount: 100, unit: :fort_nights})
** (Periods.Parser.ParserError) invalid unit type

new!(amount, unit)

@spec new!(amount(), unit()) :: Periods.Period.t() | Periods.Parser.ParserError

Creates a new Period from an amount and unit or raises an error.

Same as new/2 except raises a %ParserError{} when unsuccessful

Examples

iex> Periods.new!(100, "second")
%Periods.Period{amount: 100, unit: :second}

iex> Periods.new!(100, :fort_nights)
** (Periods.Parser.ParserError) invalid unit type

subtract(value_1, value_2)

Subtract a Period from a Time, Date, DateTime, or NaiveDateTime.

Note: Months have limited subtracting properties

When performing subtraction of periods all values will be converted to the lowest unit value. For example if you subtract a Period of days from a Period of seconds then the result will be seconds.

When subtracting a period from an Elixir date/time struct the return will be the Elixir date/time struct.

Note: Since there is conversion involved you may loose fractional values such as subtracting 1 millisecond from a Date.

Examples

iex> Periods.subtract(%Period{amount: 10, unit: :day}, %Period{amount: 1000, unit: :second})
%Periods.Period{amount: 863000, unit: :second}

iex> DateTime.utc_now() |> Periods.subtract(%Period{amount: 1000, unit: :second})
~U[2023-07-09 14:08:32.916532Z]

iex> Time.utc_now() |> Periods.subtract(%Period{amount: 1000, unit: :second})
~T[14:08:45.831176]

iex> today = Date.utc_today()
~D[2023-07-10]
iex> Periods.subtract(today, %Period{amount: 10000000000000, unit: :millisecond})
~D[1706-08-21]
iex> Periods.subtract(today, %Period{amount: 3, unit: :year})
~D[2020-07-10]

to_integer(period, convert_unit \\ nil)

@spec to_integer(Periods.Period.t(), unit() | nil) :: integer() | error_type()

Output a Period as an Integer with optional conversion unit.

Examples

iex> Periods.to_integer(%Period{amount: 10, unit: :day}, :second)
864000

iex> Periods.to_integer(%Period{amount: 10, unit: :day})
10

iex> Periods.to_integer(%Period{amount: 1000, unit: :second}, :month)
{:error, {:cannot_convert_to_month, :second}}

to_string(period, convert_unit \\ nil)

@spec to_string(Periods.Period.t(), unit() | nil) :: String.t() | error_type()

Output a Period as a String with optional conversion unit.

Examples

iex> Periods.to_string(%Period{amount: 10, unit: :day}, :second)
"864000 seconds"

iex> Periods.to_string(%Period{amount: 10, unit: :day})
"10 days"

iex> Periods.to_string(%Period{amount: 1, unit: :day})
"1 day"

iex> Periods.to_string(%Period{amount: 1000, unit: :second}, :month)
{:error, {:cannot_convert_to_month, :second}}