# `Eyeon.Timestamp`
[🔗](https://gitlab.com/phinnaeus/eyeon/blob/main/lib/eyeon/timestamp.ex#L1)

Precision-preserving Ion timestamp representation.

Ion timestamps carry precision and local offset semantics that native Elixir
date/time types cannot represent. Two timestamps are Ion-equivalent only when
they have the same precision, the same offset status, and the same component
values. For example, `2001T` and `2001-01T` represent the same instant but
have different precisions — they are NOT equivalent in Ion.

## Fields

  * `:year` — always present
  * `:month` — nil for year precision
  * `:day` — nil for year/month precision
  * `:hour` — nil for date-only precision (always paired with minute)
  * `:minute` — nil for date-only precision
  * `:second` — nil for minute precision
  * `:fraction` — fractional seconds as a digit string (e.g. `"123"`, `"00300"`),
    preserving trailing zeros for precision. nil if no fractional seconds.
  * `:offset` — `:unknown` for `-00:00`, integer (minutes from UTC) for known
    offsets, nil when no time component is present
  * `:precision` — one of `:year`, `:month`, `:day`, `:minute`, `:second`,
    `:fractional_second`

# `offset`

```elixir
@type offset() :: :unknown | integer() | nil
```

# `precision`

```elixir
@type precision() :: :year | :month | :day | :minute | :second | :fractional_second
```

# `t`

```elixir
@type t() :: %Eyeon.Timestamp{
  day: pos_integer() | nil,
  fraction: String.t() | nil,
  hour: non_neg_integer() | nil,
  minute: non_neg_integer() | nil,
  month: pos_integer() | nil,
  offset: offset(),
  precision: precision(),
  second: non_neg_integer() | nil,
  year: pos_integer()
}
```

# `components_to_native`

```elixir
@spec components_to_native(
  integer(),
  integer() | nil,
  integer() | nil,
  integer() | nil,
  integer() | nil,
  integer() | nil,
  String.t() | nil,
  integer()
) :: Date.t() | NaiveDateTime.t() | DateTime.t()
```

Build a DateTime, NaiveDateTime, or Date from timestamp components.

- `offset` is in minutes, with -0x800 meaning unknown offset.
- When hour/minute/day/month are nil, returns coarser types (Date).

# `format_zone_abbr`

```elixir
@spec format_zone_abbr(integer()) :: String.t()
```

Format a UTC offset in seconds as a zone abbreviation like "+05:30" or "-08:00".

# `from_components`

```elixir
@spec from_components(
  integer(),
  integer() | nil,
  integer() | nil,
  integer() | nil,
  integer() | nil,
  integer() | nil,
  String.t() | nil,
  integer()
) :: t()
```

Build a `%Eyeon.Timestamp{}` directly from parsed components.

Used by the binary decoder which already has components parsed.
`offset` uses the binary encoding convention: `-0x800` for unknown offset,
minutes from UTC otherwise.

# `from_string`

```elixir
@spec from_string(String.t()) :: t()
```

Parse an Ion timestamp string into a `%Eyeon.Timestamp{}`.

Accepts all Ion timestamp formats:
- `2024T` (year)
- `2024-01T` (year-month)
- `2024-01-15` or `2024-01-15T` (date)
- `2024-01-15T12:30Z` (minute with offset)
- `2024-01-15T12:30:00Z` (second with offset)
- `2024-01-15T12:30:00.123Z` (fractional second with offset)

# `leap_year?`

```elixir
@spec leap_year?(integer()) :: boolean()
```

Returns true if the given year is a leap year.

# `max_days_in_month`

```elixir
@spec max_days_in_month(integer(), integer()) :: integer()
```

Maximum number of days in a given month, accounting for leap years.

# `parse_frac_to_microsecond`

```elixir
@spec parse_frac_to_microsecond(nil | String.t()) ::
  {non_neg_integer(), non_neg_integer()}
```

Parse a fractional seconds string (like ".123456" or "123456") into a
`{microseconds, precision}` tuple.

# `to_ion_string`

```elixir
@spec to_ion_string(t()) :: String.t()
```

Convert to an Ion text representation string.

# `to_native`

```elixir
@spec to_native(t()) :: Date.t() | NaiveDateTime.t() | DateTime.t()
```

Convert to a native Elixir date/time type.

Returns `DateTime`, `NaiveDateTime`, or `Date` depending on precision and offset.

# `to_utc`

```elixir
@spec to_utc(t()) :: DateTime.t()
```

Convert to a UTC `DateTime` for timeline comparison.

All timestamps are normalized to UTC. Unknown offsets are treated as UTC.
Date-only timestamps are treated as midnight UTC.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
