@@ -128,8 +128,36 @@ defmodule Beacon.GraphQL.QueryExecutor do
128128 # top-level key, unwrap to just the value so templates get the data directly
129129 # (e.g., @featured_links = [...] instead of %{"featuredLinks" => [...]}).
130130 defp unwrap_response ( % { } = data ) when map_size ( data ) == 1 do
131- data |> Map . values ( ) |> hd ( )
131+ data |> Map . values ( ) |> hd ( ) |> parse_dates ( )
132132 end
133133
134- defp unwrap_response ( data ) , do: data
134+ defp unwrap_response ( data ) , do: parse_dates ( data )
135+
136+ # JSON has no date type. GraphQL responses serialize DateTime/NaiveDateTime
137+ # to ISO 8601 strings. Parse them back so template code that expects
138+ # DateTime structs (e.g., Date.to_iso8601, Calendar functions) works.
139+ @ iso_datetime ~r/ ^\d {4}-\d {2}-\d {2}T\d {2}:\d {2}:\d {2}/
140+
141+ defp parse_dates ( % { } = map ) do
142+ Map . new ( map , fn { k , v } -> { k , parse_dates ( v ) } end )
143+ end
144+
145+ defp parse_dates ( list ) when is_list ( list ) , do: Enum . map ( list , & parse_dates / 1 )
146+
147+ defp parse_dates ( value ) when is_binary ( value ) do
148+ if Regex . match? ( @ iso_datetime , value ) do
149+ case DateTime . from_iso8601 ( value ) do
150+ { :ok , dt , _offset } -> dt
151+ _ ->
152+ case NaiveDateTime . from_iso8601 ( value ) do
153+ { :ok , ndt } -> ndt
154+ _ -> value
155+ end
156+ end
157+ else
158+ value
159+ end
160+ end
161+
162+ defp parse_dates ( value ) , do: value
135163end
0 commit comments