defmodule CodeNameRaven.Timezone do @moduledoc """ A robust, enterprise-grade timezone utility for Code Name Raven. Utilizes the official IANA Time Zone Database (Tzdb) via the `tz` package to natively, safely, and dynamically shift UTC DateTime structs to any target timezone (such as `"America/Chicago"`) on-the-fly, handling all Daylight Saving Time transitions flawlessly. """ @doc """ Formats a UTC DateTime based on the display timezone requested by the integration module. Defaults to UTC if no timezone is supplied or supported. """ @spec format_datetime(DateTime.t() | nil, module() | nil) :: String.t() def format_datetime(nil, _mod), do: "Waiting for first check…" def format_datetime(%DateTime{} = dt, mod) do tz = if mod && function_exported?(mod, :display_timezone, 0), do: mod.display_timezone(), else: "UTC" case shift_timezone(dt, tz) do {:ok, shifted, suffix} -> Calendar.strftime(shifted, "%I:%M:%S %p ") <> suffix _ -> Calendar.strftime(dt, "%I:%M:%S %p UTC") end end @doc """ Shifts a UTC DateTime and returns it as a fake UTC ISO-8601 string representing the target timezone, so that client-side Plotly.js can parse it as a native Date and apply downsampling. """ @spec format_chart_time(DateTime.t(), module() | nil) :: String.t() def format_chart_time(%DateTime{} = dt, mod) do tz = if mod && function_exported?(mod, :display_timezone, 0), do: mod.display_timezone(), else: "UTC" case shift_timezone(dt, tz) do {:ok, shifted, _suffix} -> # Convert the shifted date back into a fake UTC ISO-8601 string so Plotly parses it as a native Date. DateTime.to_iso8601(shifted) _ -> DateTime.to_iso8601(dt) end end @doc """ Natively shifts a UTC DateTime to the target IANA timezone. Returns `{:ok, shifted_datetime, zone_abbreviation}`. """ @spec shift_timezone(DateTime.t(), String.t()) :: {:ok, DateTime.t(), String.t()} | {:error, any()} def shift_timezone(%DateTime{} = dt, "UTC"), do: {:ok, dt, "UTC"} def shift_timezone(%DateTime{} = dt, tz) do case DateTime.shift_zone(dt, tz) do {:ok, shifted} -> {:ok, shifted, shifted.zone_abbr} {:error, reason} -> {:error, reason} end end end