diff --git a/src/distill.js b/src/distill.js
index d9274c1..0451bbd 100644
--- a/src/distill.js
+++ b/src/distill.js
@@ -724,7 +724,7 @@ export function feedToHTML(text) {
|| rssLink(entry)
|| (guidIsPermalink ? field(entry, 'guid') : '');
const author = field(entry, 'author name') || field(entry, 'author');
- const date = (field(entry, 'updated') || field(entry, 'published') || field(entry, 'pubdate')).slice(0, 10);
+ const date = feedDate(field(entry, 'updated') || field(entry, 'published') || field(entry, 'pubdate'));
const byline = [author && `by ${author}`, date].filter(Boolean).join(', ');
// Atom escapes the entry body, so textContent of content/summary is the
// HTML itself, ready to be embedded and parsed like any page.
@@ -752,6 +752,27 @@ export function feedToHTML(text) {
return `
${esc(feedTitle)}\n${parts.join('\n')}\n`;
}
+const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
+const RFC822_DATE = /^(?:[a-z]{3},\s*)?(\d{1,2})\s+([a-z]{3})\s+(\d{4})\b/i;
+
+/**
+ * An entry's date as YYYY-MM-DD. Atom writes ISO 8601, so the day is its
+ * first ten characters. RSS 2.0 writes RFC 822 ('Mon, 17 Aug 2026 00:00:00
+ * GMT'), and ten characters of that are 'Mon, 17 Au'. Its day, month, and
+ * year are read as written rather than through Date, which would move a late
+ * evening post to the next day in UTC; the Atom slice keeps the feed's own
+ * day too. Anything else is printed as the feed wrote it, not cut.
+ * @param {string} s
+ * @returns {string}
+ */
+function feedDate(s) {
+ if (/^\d{4}-\d\d-\d\d/.test(s)) return s.slice(0, 10);
+ const m = s.match(RFC822_DATE);
+ const month = m ? MONTHS.indexOf(m[2].toLowerCase()) + 1 : 0;
+ if (!month) return s;
+ return `${m[3]}-${String(month).padStart(2, '0')}-${m[1].padStart(2, '0')}`;
+}
+
const escHTML = (s) => String(s ?? '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
/**
diff --git a/tests/distill.test.js b/tests/distill.test.js
index d365fda..11549c8 100644
--- a/tests/distill.test.js
+++ b/tests/distill.test.js
@@ -267,6 +267,27 @@ test('an RSS 2.0 entry is opened by its link, not a non-permalink guid', () => {
assert.ok(text.includes('Second lede.'), 'item without content:encoded lost its description');
});
+test('an RSS 2.0 pubDate prints as a date, not as its first ten characters', () => {
+ // pubDate is RFC 822, the day name first. Cutting it where an Atom
+ // timestamp is cut printed "Mon, 17 Au" under every WordPress post.
+ const rss = readFileSync(new URL('./pages/rss20.xml', import.meta.url), 'utf8');
+ const lines = distill(rss, 'https://blog.example.test/feed.xml').blocks.map((b) => b.text);
+ assert.ok(lines.includes('2026-08-17'), `pubDate not read as a date: ${JSON.stringify(lines)}`);
+ assert.ok(lines.includes('2026-08-18'), 'second item lost its date');
+ assert.ok(!lines.some((l) => /\bAu$/.test(l)), 'a date was cut mid-month');
+ // The day is the one the feed wrote, in its own zone, the way the slice of
+ // an Atom timestamp already reads, not the UTC day it converts to.
+ const late = `Late
+ Late posthttps://example.test/late
+ Sat, 5 Sep 2026 23:30:00 -0700Body text here.
+ `;
+ assert.ok(distill(late, 'https://example.test/rss').blocks.some((b) => b.text === '2026-09-05'),
+ 'a single-digit day or a zone offset threw the date off');
+ // A date in neither form is printed as the feed wrote it, rather than cut.
+ const odd = late.replace('Sat, 5 Sep 2026 23:30:00 -0700', 'yesterday');
+ assert.ok(distill(odd, 'https://example.test/rss').blocks.some((b) => b.text === 'yesterday'));
+});
+
test('a youtube watch page renders as title, byline, description, and transcript links', () => {
const watch = readFileSync(new URL('./pages/youtube_watch.html', import.meta.url), 'utf8');
const p = distill(watch, 'https://www.youtube.com/watch?v=fixture123');