defmodule QuackDB.NanosecondTime do
@moduledoc """
DuckDB `TIME_NS` value stored as nanoseconds since midnight.
Elixir's `Time` type stores microseconds, so this struct preserves DuckDB's
nanosecond precision while still offering conversion to a truncated `Time`.
"""
defstruct [:nanoseconds]
@day_nanoseconds 86_400_000_000_000
@type t :: %__MODULE__{nanoseconds: integer()}
@doc "Builds a `TIME_NS` value from nanoseconds since midnight."
@spec new(integer()) :: t()
def new(nanoseconds) when is_integer(nanoseconds) do
%__MODULE__{nanoseconds: nanoseconds}
end
@doc "Builds a `TIME_NS` value from an Elixir `Time`."
@spec from_time(Time.t()) :: t()
def from_time(%Time{} = time) do
new(Time.diff(time, ~T[00:00:00], :nanosecond))
end
@doc "Converts to an Elixir `Time`, truncating sub-microsecond precision."
@spec to_time(t()) :: Time.t()
def to_time(%__MODULE__{nanoseconds: nanoseconds}) do
Time.add(~T[00:00:00], div(nanoseconds, 1_000), :microsecond)
end
@doc "Returns true when the value is in DuckDB's time-of-day range."
@spec valid?(t()) :: boolean()
def valid?(%__MODULE__{nanoseconds: nanoseconds}) do
nanoseconds in 0..(@day_nanoseconds - 1)
end
@doc "Returns the stored nanoseconds since midnight."
@spec to_integer(t()) :: integer()
def to_integer(%__MODULE__{nanoseconds: nanoseconds}), do: nanoseconds
end
if Code.ensure_loaded?(Inspect) do
defimpl Inspect, for: QuackDB.NanosecondTime do
import Inspect.Algebra
def inspect(value, opts) do
concat([
string("#QuackDB.NanosecondTime<"),
to_doc(value.nanoseconds, opts),
string(" ns>")
])
end
end
end