Boxic.FEEL.Time (boxic_feel v0.1.0)

Copy Markdown View Source

Lossless FEEL time value.

This type exists because neither Elixir's Time nor Erlang's :calendar represents the complete FEEL time domain. Elixir time is limited to microsecond precision and carries no zone identity. OTP's RFC 3339 helpers can convert conventional numeric offsets to an instant, but do not represent floating times, IANA zone names, or second-precision offsets as values.

FEEL requires all of those distinctions. In particular, a floating time is not interchangeable with a zoned time, and an IANA zone name must be retained even when an offset can be resolved for comparison. Seconds are therefore stored as Decimal for up to nanosecond precision, while zone explicitly records floating, numeric-offset, or named-zone identity.

Named-zone validation uses the IANA database through tz; the value representation remains independent of that resolver.

Summary

Functions

Adds seconds and wraps the result within one day.

Compares compatible FEEL times, returning :unordered for incompatible zones.

Calculates left - right when the time values are comparable.

Converts an Elixir time into a floating FEEL time.

Returns seconds since local midnight without applying a zone offset.

Creates a validated FEEL time.

Parses a FEEL time, preserving fractional seconds and zone identity.

Serializes a FEEL time.

Types

t()

@type t() :: %Boxic.FEEL.Time{
  hour: 0..23,
  minute: 0..59,
  second: Decimal.t(),
  zone: zone()
}

zone()

@type zone() :: :floating | {:offset, integer()} | {:iana, String.t()}

Functions

add_seconds(value, seconds)

@spec add_seconds(t(), integer() | Decimal.t()) :: t()

Adds seconds and wraps the result within one day.

Boxic.FEEL.Time.add_seconds(time, Decimal.new("0.5"))

compare(left, right)

@spec compare(t(), t()) :: :lt | :eq | :gt | :unordered

Compares compatible FEEL times, returning :unordered for incompatible zones.

Boxic.FEEL.Time.compare(left, right)

difference(left, right)

@spec difference(t(), t()) :: {:ok, Boxic.FEEL.Duration.t()} | :error

Calculates left - right when the time values are comparable.

Boxic.FEEL.Time.difference(left, right)

from_elixir(value)

@spec from_elixir(Time.t()) :: t()

Converts an Elixir time into a floating FEEL time.

Boxic.FEEL.Time.from_elixir(~T[12:30:15])

local_seconds(value)

@spec local_seconds(t()) :: Decimal.t()

Returns seconds since local midnight without applying a zone offset.

Boxic.FEEL.Time.local_seconds(time)

new(hour, minute, second, zone \\ :floating)

@spec new(integer(), integer(), Decimal.t() | integer(), zone()) ::
  {:ok, t()} | :error

Creates a validated FEEL time.

Boxic.FEEL.Time.new(12, 30, Decimal.new("15.5"), {:offset, 0})

parse(value)

@spec parse(String.t()) :: {:ok, t()} | :error

Parses a FEEL time, preserving fractional seconds and zone identity.

Boxic.FEEL.Time.parse("12:30:15.5Z")

to_string(value)

@spec to_string(t()) :: String.t()

Serializes a FEEL time.

Boxic.FEEL.Time.to_string(time)