Localize.Duration (Localize v1.4.0)

Copy Markdown View Source

Functions to create and format durations — the difference between two dates, times, or datetimes expressed in calendar units.

A duration is represented as years, months, days, hours, minutes, seconds, and microseconds. This is useful for producing human-readable strings like "11 months, 30 days" or numeric patterns like "37:48:12".

Creating durations

  • new/2 — calculates the duration between two dates, times, or datetimes.

  • new_from_seconds/1 — creates a duration from a number of seconds.

Formatting durations

  • to_string/2 — formats a duration as a localized string using unit names (e.g., "11 months, 30 days") via Localize.Unit and Localize.List.

  • to_time_string/2 — formats the time portion of a duration using a pattern like "hh:mm:ss". Hours are unbounded (e.g., "37:48:12" for 37 hours).

Summary

Types

A date, time, naive datetime, or datetime.

t()

Duration in calendar units.

Functions

Calculates the calendar duration of a Date.Range.t/0.

Calculates the calendar duration between two dates, times, or datetimes.

Same as new/2 but raises on error.

Creates a duration from a number of seconds.

Formats a duration into typed parts, mirroring ECMA-402's formatToParts for Intl.DurationFormat.

Same as to_parts/2 but raises on error.

Formats a duration as a localized string using unit names.

Same as to_string/2 but raises on error.

Formats the time portion of a duration using a numeric pattern like "hh:mm:ss".

Types

date_or_time_or_datetime()

@type date_or_time_or_datetime() ::
  Calendar.date()
  | Calendar.time()
  | Calendar.datetime()
  | Calendar.naive_datetime()

A date, time, naive datetime, or datetime.

t()

@type t() :: %Localize.Duration{
  day: non_neg_integer(),
  hour: non_neg_integer(),
  microsecond: {integer(), 1..6},
  minute: non_neg_integer(),
  month: non_neg_integer(),
  second: non_neg_integer(),
  year: non_neg_integer()
}

Duration in calendar units.

Functions

new(range)

@spec new(Date.Range.t()) :: {:ok, t()} | {:error, Exception.t() | atom()}

Calculates the calendar duration of a Date.Range.t/0.

Equivalent to new(range.first, range.last).

Arguments

Returns

  • {:ok, duration} where duration is a t/0 struct.

  • {:error, exception} if the range endpoints are incompatible.

Examples

iex> {:ok, d} = Localize.Duration.new(Date.range(~D[2019-01-01], ~D[2019-12-31]))
iex> d.month
11

new(from, to)

@spec new(from :: date_or_time_or_datetime(), to :: date_or_time_or_datetime()) ::
  {:ok, t()} | {:error, Exception.t() | atom()}

Calculates the calendar duration between two dates, times, or datetimes.

The years, months and days are the span the values' own calendar adds to from to reach to, as Date.shift/2 adds it: the most years that do not pass to, then the most months after them, then the days left. They are never counted from the values' fields. A day of the month is brought into a shorter month, so 31 January to 29 February is one month. Between two datetimes the time between their times of day is added, and where to's time of day is the earlier of the two, the dates are counted to the day before to.

Two DateTime.t/0 values are two moments, measured where from is, as ECMA-262 Temporal measures two zoned date-times: to is moved to from's time zone, the years, months and days are counted on that wall clock, and the hours, minutes and seconds are the time that passes after them. So 10:00 UTC to 18:00 in Karachi is three hours, noon to noon across a change of clocks is one day, and 23:00 to 04:00 across an hour the clocks skip is four hours. Where the clocks repeat an hour the hours can be 24 or more. A time zone is known through Elixir's time zone database (Calendar.get_time_zone_database/0); without one that knows from's zone, to is taken to the UTC offset from carries.

Any other two values are measured on the wall clocks they are written in. A date paired with a datetime is taken at midnight, and a time paired with a datetime is measured against its time of day.

Arguments

  • from is a date, time, or datetime representing the start.

  • to is a date, time, or datetime representing the end. It is in the same calendar as from and is not earlier.

Returns

  • {:ok, duration} where duration is a t/0 struct.

  • {:error, exception} if the arguments are incompatible, to is earlier than from, or a value is not one its calendar has. A calendar that cannot be asked for its arithmetic is a Localize.UnknownCalendarError.t/0.

Examples

iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
iex> d.month
11

iex> {:ok, d} = Localize.Duration.new(~D[2023-01-14], ~D[2023-07-13])
iex> {d.month, d.day}
{5, 29}

iex> {:ok, d} = Localize.Duration.new(~T[10:00:00], ~T[12:30:45])
iex> {d.hour, d.minute, d.second}
{2, 30, 45}

iex> karachi = DateTime.new!(~D[2026-06-15], ~T[18:00:00], "Asia/Karachi")
iex> {:ok, d} = Localize.Duration.new(~U[2026-06-15 10:00:00Z], karachi)
iex> {d.day, d.hour}
{0, 3}

new!(from, to)

@spec new!(from :: date_or_time_or_datetime(), to :: date_or_time_or_datetime()) ::
  t() | no_return()

Same as new/2 but raises on error.

Arguments

  • from is a date, time, or datetime representing the start.

  • to is a date, time, or datetime representing the end.

Returns

  • A t/0 duration struct.

  • Raises an exception if the arguments are incompatible.

Examples

iex> d = Localize.Duration.new!(~D[2019-01-01], ~D[2019-12-31])
iex> d.month
11

new_from_seconds(seconds)

@spec new_from_seconds(seconds :: number()) :: t() | {:error, Exception.t()}

Creates a duration from a number of seconds.

The duration will contain only hours, minutes, seconds, and microseconds (year/month/day will be zero).

Arguments

  • seconds is a number of seconds (integer or float).

Returns

  • A t/0 struct.

  • {:error, exception} if seconds is not a number.

Examples

iex> d = Localize.Duration.new_from_seconds(136_092)
iex> {d.hour, d.minute, d.second}
{37, 48, 12}

iex> d = Localize.Duration.new_from_seconds(90.5)
iex> {d.minute, d.second}
{1, 30}

to_parts(duration, options \\ [])

@spec to_parts(t(), Keyword.t()) ::
  {:ok, [%{type: atom(), value: String.t()}]} | {:error, Exception.t()}

Formats a duration into typed parts, mirroring ECMA-402's formatToParts for Intl.DurationFormat.

The parts concatenate to exactly the string to_string/2 produces with the same options. Each duration field contributes its unit parts (from Localize.Unit.to_parts/2) with the numeric segments carrying a :unit key naming the field; the list separators between fields are :literal parts.

Arguments

  • duration is a t/0 struct.

  • options is a keyword list of options.

Options

See to_string/2 for the supported options.

Returns

  • {:ok, parts} where parts is a list of %{type: atom(), value: String.t()} maps; numeric parts also carry a :unit key.

  • {:error, exception} if formatting fails.

Examples

iex> duration = %Localize.Duration{hour: 2, minute: 30}
iex> Localize.Duration.to_parts(duration, locale: :en)
{:ok,
 [
   %{type: :integer, value: "2", unit: :hour},
   %{type: :literal, value: " "},
   %{type: :unit, value: "hours"},
   %{type: :literal, value: ", "},
   %{type: :integer, value: "30", unit: :minute},
   %{type: :literal, value: " "},
   %{type: :unit, value: "minutes"}
 ]}

to_parts!(duration, options \\ [])

@spec to_parts!(t(), Keyword.t()) :: [%{type: atom(), value: String.t()}]

Same as to_parts/2 but raises on error.

Arguments

  • duration is a t/0 struct.

  • options is a keyword list of options. See to_parts/2.

Returns

  • A list of %{type: atom(), value: String.t()} maps.

Raises

  • Raises an exception if formatting fails.

Examples

iex> Localize.Duration.to_parts!(%Localize.Duration{hour: 2}, locale: :en) |> length()
3

to_string(duration, options \\ [])

@spec to_string(t(), Keyword.t()) :: {:ok, String.t()} | {:error, Exception.t()}

Formats a duration as a localized string using unit names.

Non-zero duration parts are formatted as units and joined with the locale's unit list pattern for the requested width, per ECMA-402 Intl.DurationFormat (e.g., "11 months, 30 days").

Arguments

  • duration is a t/0 struct.

  • options is a keyword list of options.

Options

  • :except is a list of time unit atoms to omit from the output (e.g., [:microsecond]). The default is [:microsecond].

  • :locale is a locale identifier. The default is Localize.get_locale().

  • :format is the display width applied to every unit: one of :long ("11 months, 30 days"), :short ("11 mths, 30 days"), or :narrow ("11m 30d"). The default is :long. It also selects the CLDR unit list pattern that joins the parts.

  • :display is a keyword list of per-unit display control, mirroring ECMA-402's per-unit *Display options. Each key is a unit atom (:year, :month, :day, :hour, :minute, :second, :microsecond) and each value is :auto (omit the unit when zero, the default) or :always (render the unit even when zero).

  • :formats is a keyword list of per-unit width overrides, mirroring ECMA-402's per-unit width options. Each key is a unit atom (the same set as :display) and each value is :long, :short, or :narrow, overriding :format for that unit alone; units not named keep :format. Note the plural: :format sets the width for the whole duration, :formats overrides individual units within it.

Returns

  • {:ok, formatted_string} on success.

  • {:error, exception} if formatting fails.

Examples

iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
iex> Localize.Duration.to_string(d, locale: :en)
{:ok, "11 months, 30 days"}

iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
iex> Localize.Duration.to_string(d, locale: :en, format: :narrow)
{:ok, "11m 30d"}

iex> duration = %Localize.Duration{hour: 2}
iex> Localize.Duration.to_string(duration, locale: :en, display: [minute: :always])
{:ok, "2 hours, 0 minutes"}

iex> duration = %Localize.Duration{hour: 2, minute: 30}
iex> Localize.Duration.to_string(duration, locale: :en, formats: [hour: :narrow])
{:ok, "2h, 30 minutes"}

to_string!(duration, options \\ [])

@spec to_string!(t(), Keyword.t()) :: String.t() | no_return()

Same as to_string/2 but raises on error.

Arguments

  • duration is a t/0 struct.

  • options is a keyword list of options.

Options

See to_string/2 for the supported options.

Returns

  • The formatted duration as a string.

  • Raises an exception if formatting fails.

Examples

iex> {:ok, d} = Localize.Duration.new(~D[2019-01-01], ~D[2019-12-31])
iex> Localize.Duration.to_string!(d, locale: :en)
"11 months, 30 days"

to_time_string(duration, options \\ [])

@spec to_time_string(t(), Keyword.t()) :: {:ok, String.t()} | {:error, Exception.t()}

Formats the time portion of a duration using a numeric pattern like "hh:mm:ss".

Hours are unbounded — a duration of 37 hours, 48 minutes, and 12 seconds formats as "37:48:12".

Arguments

  • duration is a t/0 struct.

  • options is a keyword list of options.

Options

  • :format is a format pattern string. The default is "hh:mm:ss". Use "h:mm:ss" for no zero-padding on hours, or "mm:ss" for minutes and seconds only. Each of h, m and s takes one or two letters; a longer field formats as U+FFFD, as TR35 recommends for an invalid field.

Returns

  • {:ok, formatted_string} on success.

  • {:error, exception} if duration is not a t/0 struct, options is not a keyword list or :format is not a string.

Examples

iex> d = Localize.Duration.new_from_seconds(136_092)
iex> Localize.Duration.to_time_string(d)
{:ok, "37:48:12"}

iex> d = Localize.Duration.new_from_seconds(65)
iex> Localize.Duration.to_time_string(d, format: "m:ss")
{:ok, "1:05"}

to_time_string!(duration, options \\ [])

@spec to_time_string!(t(), Keyword.t()) :: String.t()

Same as to_time_string/2 but raises on error.

Arguments

  • duration is a t/0 struct.

  • options is a keyword list of options.

Options

See to_time_string/2 for the supported options.

Returns

  • The formatted time portion of the duration as a string.

  • Raises an exception if formatting fails.

Examples

iex> d = Localize.Duration.new_from_seconds(136_092)
iex> Localize.Duration.to_time_string!(d)
"37:48:12"

iex> d = Localize.Duration.new_from_seconds(65)
iex> Localize.Duration.to_time_string!(d, format: "m:ss")
"1:05"