An impossible feed date reads as nil, not a crashed fetch

cb32133 · Gabe Morell · 2026-11-04 21:36

8 files +28 -21
Message
{commit_body(@commit)}

Files changed

modified lib/feed_pug/feeds/fetcher.ex
+13 −21
@@ -138,27 +138,19 @@ defmodule FeedPug.Feeds.Fetcher do
138 138
139 139 # Minimal RFC-822/1123 parser, e.g. "Wed, 02 Oct 2002 13:00:00 GMT".
140 140 defp parse_rfc822(str) do
141 − case Regex.run(~r/(\d{1,2})\s+(\w{3})\s+(\d{2,4})\s+(\d{2}):(\d{2})(?::(\d{2}))?/, str) do
142 − [_, d, mon, y, h, min | rest] ->
143 − case month_number(mon) do
144 − {:ok, month} ->
145 − year = String.to_integer(y) |> normalize_year()
146 − sec = rest |> List.first() |> to_int(0)
147 −
148 − case DateTime.new(
149 − Date.new!(year, month, String.to_integer(d)),
150 − Time.new!(String.to_integer(h), String.to_integer(min), sec)
151 − ) do
152 − {:ok, dt} -> dt
153 − _ -> nil
154 − end
155 −
156 − :error ->
157 − nil
158 − end
159 −
160 − _ ->
161 − nil
141 + # An impossible date or time ("31 Feb", hour 25) reads as nil like any
142 + # other unparseable string, rather than crashing the whole feed fetch.
143 + with [_, d, mon, y, h, min | rest] <-
144 + Regex.run(~r/(\d{1,2})\s+(\w{3})\s+(\d{2,4})\s+(\d{2}):(\d{2})(?::(\d{2}))?/, str),
145 + {:ok, month} <- month_number(mon),
146 + year = String.to_integer(y) |> normalize_year(),
147 + sec = rest |> List.first() |> to_int(0),
148 + {:ok, date} <- Date.new(year, month, String.to_integer(d)),
149 + {:ok, time} <- Time.new(String.to_integer(h), String.to_integer(min), sec),
150 + {:ok, dt} <- DateTime.new(date, time) do
151 + dt
152 + else
153 + _ -> nil
162 154 end
163 155 end
164 156
modified test/feed_pug/feeds/fetcher_test.exs
+15 −0
@@ -58,4 +58,19 @@ defmodule FeedPug.Feeds.FetcherTest do
58 58
59 59 assert Fetcher.parse_datetime(nil) == nil
60 60 end
61 +
62 + test "parse_datetime reads an impossible RFC-822 date or time as nil" do
63 + assert Fetcher.parse_datetime("Sat, 31 Feb 2024 13:00:00 GMT") == nil
64 + assert Fetcher.parse_datetime("Wed, 02 Oct 2002 25:00:00 GMT") == nil
65 + assert Fetcher.parse_datetime("Wed, 02 Oct 2002 13:61:00 GMT") == nil
66 + assert Fetcher.parse_datetime("Wed, 02 Oct 2002 13:00:99 GMT") == nil
67 + end
68 +
69 + test "an entry with an impossible date is still stored, not a crashed fetch" do
70 + {:ok, feed} = Feeds.upsert_feed_by_url("https://ex.com/feed.xml")
71 + rss = String.replace(@rss, "Wed, 02 Oct 2002", "Sat, 31 Feb 2024")
72 + Req.Test.stub(__MODULE__, fn conn -> Req.Test.text(conn, rss) end)
73 +
74 + assert {:ok, [_item]} = Fetcher.refresh(feed, stub())
75 + end
61 76 end

Parents: fa42332