aprs.me/lib/aprsme_web/time_helpers.ex
Graham McIntire 564d21bb26
refactor: pattern-match in TimeHelpers.to_datetime and PageController
- TimeHelpers.to_datetime: `cond` with match?/2 guards becomes five
  function heads dispatched by input type — DateTime passes through,
  NaiveDateTime anchors to UTC, integer is a millisecond Unix epoch,
  binary tries ISO 8601, everything else is nil.
- PageController.health: the three-branch `cond` + inner `if db_healthy`
  becomes a chain of small respond_* helpers, each pattern-matched on a
  single axis (health_status, shutting_down?, db_healthy?). db_healthy?/0
  isolates the rescue.
- format_uptime splits the cond into four format_uptime_parts/4 clauses.

Adds tests for the PageController actions (health 200 / 503-draining,
ready, sitemap.xml, .well-known/api-catalog, status.json). Coverage
66.81 → 67.29%.
2026-04-23 13:58:42 -05:00

60 lines
2.6 KiB
Elixir

defmodule AprsmeWeb.TimeHelpers do
@moduledoc """
Shared helpers for formatting time and dates in the web layer.
"""
use Gettext, backend: AprsmeWeb.Gettext
@doc """
Returns a human-readable string for how long ago the given DateTime was.
"""
@spec time_ago_in_words(nil | NaiveDateTime.t() | DateTime.t()) :: String.t()
def time_ago_in_words(nil), do: gettext("unknown time") <> " " <> gettext("ago")
def time_ago_in_words(%NaiveDateTime{} = naive_datetime) do
# Convert NaiveDateTime to DateTime assuming UTC
datetime = DateTime.from_naive!(naive_datetime, "Etc/UTC")
time_ago_in_words(datetime)
end
def time_ago_in_words(datetime) do
now = DateTime.utc_now()
diff_seconds = DateTime.diff(now, datetime, :second)
format_time_diff(diff_seconds) <> " " <> gettext("ago")
end
@spec format_time_diff(integer()) :: String.t()
defp format_time_diff(seconds) when seconds < 60, do: gettext("less than a minute")
defp format_time_diff(seconds) when seconds < 120, do: gettext("1 minute")
defp format_time_diff(seconds) when seconds < 3600, do: gettext("%{count} minutes", count: div(seconds, 60))
defp format_time_diff(seconds) when seconds < 7200, do: gettext("1 hour")
defp format_time_diff(seconds) when seconds < 86_400, do: gettext("%{count} hours", count: div(seconds, 3600))
defp format_time_diff(seconds) when seconds < 172_800, do: gettext("1 day")
defp format_time_diff(seconds) when seconds < 2_592_000, do: gettext("%{count} days", count: div(seconds, 86_400))
defp format_time_diff(seconds) when seconds < 5_184_000, do: gettext("1 month")
defp format_time_diff(seconds) when seconds < 31_536_000,
do: gettext("%{count} months", count: div(seconds, 2_592_000))
defp format_time_diff(seconds) when seconds < 63_072_000, do: gettext("1 year")
defp format_time_diff(seconds), do: gettext("%{count} years", count: div(seconds, 31_536_000))
@doc """
Converts various timestamp formats into a standard DateTime object.
Handles ISO 8601 strings, Unix timestamps in milliseconds, and existing
DateTime or NaiveDateTime structs.
"""
@spec to_datetime(String.t() | integer() | DateTime.t() | NaiveDateTime.t() | any()) :: DateTime.t() | nil
def to_datetime(%DateTime{} = dt), do: dt
def to_datetime(%NaiveDateTime{} = ndt), do: DateTime.from_naive!(ndt, "Etc/UTC")
def to_datetime(ts) when is_integer(ts), do: DateTime.from_unix!(ts, :millisecond)
def to_datetime(ts) when is_binary(ts) do
case DateTime.from_iso8601(ts) do
{:ok, dt, _} -> dt
_ -> nil
end
end
def to_datetime(_), do: nil
end