Skip to main content

lib/quack_db/nanosecond_timestamp.ex

defmodule QuackDB.NanosecondTimestamp do
  @moduledoc """
  DuckDB `TIMESTAMP_NS` value stored as nanoseconds since the Unix epoch.

  Elixir's `NaiveDateTime` type stores microseconds, so this struct preserves
  DuckDB's nanosecond precision while offering conversion to a truncated
  `NaiveDateTime`.
  """

  defstruct [:nanoseconds]

  @type t :: %__MODULE__{nanoseconds: integer()}

  @doc "Builds a `TIMESTAMP_NS` value from nanoseconds since the Unix epoch."
  @spec new(integer()) :: t()
  def new(nanoseconds) when is_integer(nanoseconds) do
    %__MODULE__{nanoseconds: nanoseconds}
  end

  @doc "Builds a `TIMESTAMP_NS` value from an Elixir `NaiveDateTime`."
  @spec from_naive_datetime(NaiveDateTime.t()) :: t()
  def from_naive_datetime(%NaiveDateTime{} = value) do
    new(NaiveDateTime.diff(value, ~N[1970-01-01 00:00:00], :nanosecond))
  end

  @doc "Converts to an Elixir `NaiveDateTime`, truncating sub-microsecond precision."
  @spec to_naive_datetime(t()) :: NaiveDateTime.t()
  def to_naive_datetime(%__MODULE__{nanoseconds: nanoseconds}) do
    NaiveDateTime.add(~N[1970-01-01 00:00:00], div(nanoseconds, 1_000), :microsecond)
  end

  @doc "Returns the stored nanoseconds since the Unix epoch."
  @spec to_integer(t()) :: integer()
  def to_integer(%__MODULE__{nanoseconds: nanoseconds}), do: nanoseconds
end

if Code.ensure_loaded?(Inspect) do
  defimpl Inspect, for: QuackDB.NanosecondTimestamp do
    import Inspect.Algebra

    def inspect(value, opts) do
      concat([
        string("#QuackDB.NanosecondTimestamp<"),
        to_doc(value.nanoseconds, opts),
        string(" ns>")
      ])
    end
  end
end