<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://kingwarzcharts.com/index.php?action=history&amp;feed=atom&amp;title=Module%3AOnThisDay</id>
	<title>Module:OnThisDay - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://kingwarzcharts.com/index.php?action=history&amp;feed=atom&amp;title=Module%3AOnThisDay"/>
	<link rel="alternate" type="text/html" href="https://kingwarzcharts.com/index.php?title=Module:OnThisDay&amp;action=history"/>
	<updated>2026-07-21T17:38:50Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://kingwarzcharts.com/index.php?title=Module:OnThisDay&amp;diff=3182&amp;oldid=prev</id>
		<title>KWCBot: main(): accept month/day from direct #invoke frame args too (testing)</title>
		<link rel="alternate" type="text/html" href="https://kingwarzcharts.com/index.php?title=Module:OnThisDay&amp;diff=3182&amp;oldid=prev"/>
		<updated>2026-07-15T00:12:46Z</updated>

		<summary type="html">&lt;p&gt;main(): accept month/day from direct #invoke frame args too (testing)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local p = {}&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- CONFIG: Week 1 anchor for King Warz Charts&lt;br /&gt;
-- Week 1 starts: 2020-12-25 (Friday)&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
local WEEK1_Y, WEEK1_M, WEEK1_D = 2020, 12, 25&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- Load data from Module:OnThisDay/Data&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
local ok, data = pcall(mw.loadData, &amp;#039;Module:OnThisDay/Data&amp;#039;)&lt;br /&gt;
if not ok or type(data) ~= &amp;#039;table&amp;#039; then&lt;br /&gt;
	data = {}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Extra day-precise events (certs, first plays, debuts, first #1s),&lt;br /&gt;
-- generated by the Control Center&amp;#039;s otd_events.py&lt;br /&gt;
local ok2, extraEvents = pcall(mw.loadData, &amp;#039;Module:OnThisDay/Extra&amp;#039;)&lt;br /&gt;
if not ok2 or type(extraEvents) ~= &amp;#039;table&amp;#039; then&lt;br /&gt;
	extraEvents = {}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- Date helpers (pure Lua date math; no os.time needed)&lt;br /&gt;
-- Uses &amp;quot;days from civil&amp;quot; algorithms (safe in Scribunto)&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
local function isLeap(y)&lt;br /&gt;
	return (y % 4 == 0 and y % 100 ~= 0) or (y % 400 == 0)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function daysInMonth(y, m)&lt;br /&gt;
	local dim = {31,28,31,30,31,30,31,31,30,31,30,31}&lt;br /&gt;
	if m == 2 and isLeap(y) then&lt;br /&gt;
		return 29&lt;br /&gt;
	end&lt;br /&gt;
	return dim[m] or 30&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Convert civil date -&amp;gt; serial day number (Rata Die-ish)&lt;br /&gt;
local function daysFromCivil(y, m, d)&lt;br /&gt;
	-- Howard Hinnant algorithm&lt;br /&gt;
	y = y - (m &amp;lt;= 2 and 1 or 0)&lt;br /&gt;
	local era = math.floor(y / 400)&lt;br /&gt;
	local yoe = y - era * 400&lt;br /&gt;
	local doy = math.floor((153 * (m + (m &amp;gt; 2 and -3 or 9)) + 2) / 5) + d - 1&lt;br /&gt;
	local doe = yoe * 365 + math.floor(yoe / 4) - math.floor(yoe / 100) + doy&lt;br /&gt;
	return era * 146097 + doe&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Convert serial day number -&amp;gt; civil date&lt;br /&gt;
local function civilFromDays(z)&lt;br /&gt;
	-- Inverse Howard Hinnant algorithm&lt;br /&gt;
	local era = math.floor(z / 146097)&lt;br /&gt;
	local doe = z - era * 146097&lt;br /&gt;
	local yoe = math.floor((doe - math.floor(doe/1460) + math.floor(doe/36524) - math.floor(doe/146096)) / 365)&lt;br /&gt;
	local y = yoe + era * 400&lt;br /&gt;
	local doy = doe - (yoe * 365 + math.floor(yoe/4) - math.floor(yoe/100))&lt;br /&gt;
	local mp = math.floor((5 * doy + 2) / 153)&lt;br /&gt;
	local d = doy - math.floor((153 * mp + 2) / 5) + 1&lt;br /&gt;
	local m = mp + (mp &amp;lt; 10 and 3 or -9)&lt;br /&gt;
	y = y + (m &amp;lt;= 2 and 1 or 0)&lt;br /&gt;
	return y, m, d&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function pad2(n)&lt;br /&gt;
	if n &amp;lt; 10 then return &amp;quot;0&amp;quot; .. tostring(n) end&lt;br /&gt;
	return tostring(n)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function toISO(y, m, d)&lt;br /&gt;
	return tostring(y) .. &amp;quot;-&amp;quot; .. pad2(m) .. &amp;quot;-&amp;quot; .. pad2(d)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function parseISO(s)&lt;br /&gt;
	if type(s) ~= &amp;#039;string&amp;#039; then return nil end&lt;br /&gt;
	local y, m, d = s:match(&amp;quot;^(%d%d%d%d)%-(%d%d)%-(%d%d)$&amp;quot;)&lt;br /&gt;
	if not y then return nil end&lt;br /&gt;
	y, m, d = tonumber(y), tonumber(m), tonumber(d)&lt;br /&gt;
	if not y or not m or not d then return nil end&lt;br /&gt;
	if m &amp;lt; 1 or m &amp;gt; 12 then return nil end&lt;br /&gt;
	local dim = daysInMonth(y, m)&lt;br /&gt;
	if d &amp;lt; 1 or d &amp;gt; dim then return nil end&lt;br /&gt;
	return y, m, d&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Week number -&amp;gt; Week start ISO date (Friday)&lt;br /&gt;
local function weekToISO(weekNum)&lt;br /&gt;
	if not weekNum or weekNum &amp;lt; 1 then return nil end&lt;br /&gt;
	local base = daysFromCivil(WEEK1_Y, WEEK1_M, WEEK1_D)&lt;br /&gt;
	local z = base + (weekNum - 1) * 7&lt;br /&gt;
	local y, m, d = civilFromDays(z)&lt;br /&gt;
	return toISO(y, m, d)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- Helpers&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
local u = mw.ustring&lt;br /&gt;
&lt;br /&gt;
local TITLE_JOINERS = {&lt;br /&gt;
	[&amp;#039;of&amp;#039;] = true, [&amp;#039;the&amp;#039;] = true, [&amp;#039;and&amp;#039;] = true, [&amp;#039;a&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;an&amp;#039;] = true, [&amp;#039;in&amp;#039;] = true, [&amp;#039;on&amp;#039;] = true, [&amp;#039;to&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;for&amp;#039;] = true, [&amp;#039;with&amp;#039;] = true, [&amp;#039;from&amp;#039;] = true, [&amp;#039;by&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;at&amp;#039;] = true, [&amp;#039;or&amp;#039;] = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function titlecase(str)&lt;br /&gt;
	if not str or str == &amp;#039;&amp;#039; then&lt;br /&gt;
		return &amp;#039;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	str = mw.text.trim(str)&lt;br /&gt;
	str = u.lower(str)&lt;br /&gt;
&lt;br /&gt;
	local first = true&lt;br /&gt;
	local function up_word(word)&lt;br /&gt;
		local bare = u.match(word, &amp;#039;^%p*(.-)%p*$&amp;#039;) or word&lt;br /&gt;
		if not first and TITLE_JOINERS[bare] then&lt;br /&gt;
			return word            -- joiners stay lowercase mid-title&lt;br /&gt;
		end&lt;br /&gt;
		first = false&lt;br /&gt;
		-- capitalize the first LETTER, skipping leading punctuation&lt;br /&gt;
		-- so &amp;#039;(rock&amp;#039; becomes &amp;#039;(Rock&amp;#039;, not &amp;#039;(rock&amp;#039;&lt;br /&gt;
		local pre, ch, rest = u.match(word, &amp;#039;^(%A*)(%a)(.*)$&amp;#039;)&lt;br /&gt;
		if ch then&lt;br /&gt;
			return pre .. u.upper(ch) .. rest&lt;br /&gt;
		end&lt;br /&gt;
		return word&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	str = u.gsub(str, &amp;#039;%S+&amp;#039;, up_word)&lt;br /&gt;
	return str&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function normalizeArtistList(raw)&lt;br /&gt;
	if not raw or raw == &amp;#039;&amp;#039; then&lt;br /&gt;
		return &amp;#039;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	local parts = {}&lt;br /&gt;
	for part in mw.text.gsplit(raw, &amp;quot;,&amp;quot;) do&lt;br /&gt;
		part = mw.text.trim(part)&lt;br /&gt;
		part = titlecase(part)&lt;br /&gt;
		table.insert(parts, part)&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(parts, &amp;quot;, &amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function normalizeNo1Field(text, isAlbum)&lt;br /&gt;
	if not text or text == &amp;#039;&amp;#039; then&lt;br /&gt;
		return text&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local linkInner, afterLink = text:match(&amp;quot;%[%[([^%]]+)%]%](.*)&amp;quot;)&lt;br /&gt;
	if not linkInner then&lt;br /&gt;
		return titlecase(text)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local target, label = linkInner:match(&amp;quot;^(.-)|(.+)$&amp;quot;)&lt;br /&gt;
	if not target then&lt;br /&gt;
		target = linkInner&lt;br /&gt;
		label  = linkInner&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	label = titlecase(label)&lt;br /&gt;
&lt;br /&gt;
	if isAlbum then&lt;br /&gt;
		label = &amp;quot;&amp;#039;&amp;#039;&amp;quot; .. label .. &amp;quot;&amp;#039;&amp;#039;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local changed = false&lt;br /&gt;
&lt;br /&gt;
	local beforeSmall, insideParens, afterSmall =&lt;br /&gt;
		afterLink:match(&amp;quot;^(.-)&amp;lt;small&amp;gt;%s*%((.-)%)%s*&amp;lt;/small&amp;gt;(.*)$&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	if insideParens then&lt;br /&gt;
		local artistsText = normalizeArtistList(insideParens)&lt;br /&gt;
		afterLink = (beforeSmall or &amp;quot;&amp;quot;) ..&lt;br /&gt;
			&amp;quot;&amp;lt;small&amp;gt;(&amp;quot; .. artistsText .. &amp;quot;)&amp;lt;/small&amp;gt;&amp;quot; ..&lt;br /&gt;
			(afterSmall or &amp;quot;&amp;quot;)&lt;br /&gt;
		changed = true&lt;br /&gt;
	else&lt;br /&gt;
		local b2, insidePlain, a2 =&lt;br /&gt;
			afterLink:match(&amp;quot;^(.-)&amp;lt;small&amp;gt;%s*(.-)%s*&amp;lt;/small&amp;gt;(.*)$&amp;quot;)&lt;br /&gt;
		if insidePlain then&lt;br /&gt;
			local artistsText = normalizeArtistList(insidePlain)&lt;br /&gt;
			afterLink = (b2 or &amp;quot;&amp;quot;) ..&lt;br /&gt;
				&amp;quot;&amp;lt;small&amp;gt;&amp;quot; .. artistsText .. &amp;quot;&amp;lt;/small&amp;gt;&amp;quot; ..&lt;br /&gt;
				(a2 or &amp;quot;&amp;quot;)&lt;br /&gt;
			changed = true&lt;br /&gt;
		else&lt;br /&gt;
			local b3, artistsPlain, a3 =&lt;br /&gt;
				afterLink:match(&amp;quot;^(%s*)%(%s*(.-)%s*%)(.*)$&amp;quot;)&lt;br /&gt;
			if artistsPlain then&lt;br /&gt;
				local artistsText = normalizeArtistList(artistsPlain)&lt;br /&gt;
				afterLink = (b3 or &amp;quot;&amp;quot;) ..&lt;br /&gt;
					&amp;quot;(&amp;quot; .. artistsText .. &amp;quot;)&amp;quot; ..&lt;br /&gt;
					(a3 or &amp;quot;&amp;quot;)&lt;br /&gt;
				changed = true&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if not changed then&lt;br /&gt;
		local newAfter, n = afterLink:gsub(&amp;quot;%(([^()]+)%)&amp;quot;, function(inner)&lt;br /&gt;
			local artistsText = normalizeArtistList(inner)&lt;br /&gt;
			return &amp;quot;(&amp;quot; .. artistsText .. &amp;quot;)&amp;quot;&lt;br /&gt;
		end, 1)&lt;br /&gt;
		if n &amp;gt; 0 then&lt;br /&gt;
			afterLink = newAfter&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local link = &amp;quot;[[&amp;quot; .. target .. &amp;quot;|&amp;quot; .. label .. &amp;quot;]]&amp;quot;&lt;br /&gt;
	return link .. afterLink&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getMonthDay(args)&lt;br /&gt;
	local month = tonumber(args.month)&lt;br /&gt;
	local day   = tonumber(args.day)&lt;br /&gt;
&lt;br /&gt;
	if not month or not day then&lt;br /&gt;
		local lang = mw.getContentLanguage()&lt;br /&gt;
		month = tonumber(lang:formatDate(&amp;#039;m&amp;#039;))&lt;br /&gt;
		day   = tonumber(lang:formatDate(&amp;#039;d&amp;#039;))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return month, day&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function targetDateString(year, month, day)&lt;br /&gt;
	return string.format(&amp;#039;%04d-%02d-%02d&amp;#039;, year, month, day)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- Normalize entry date:&lt;br /&gt;
-- - If entry.date is valid ISO, keep it&lt;br /&gt;
-- - Else if entry.week exists, compute ISO date from week anchor&lt;br /&gt;
-- - Else nil&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
local function getEntryISODate(entry)&lt;br /&gt;
	local datestr = tostring(entry.date or &amp;#039;&amp;#039;)&lt;br /&gt;
	local y, m, d = parseISO(datestr)&lt;br /&gt;
	if y then&lt;br /&gt;
		return datestr&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local w = tonumber(entry.week)&lt;br /&gt;
	if w then&lt;br /&gt;
		return weekToISO(w)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- Build per-year index from the data module (using normalized dates)&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
local by_year = {}&lt;br /&gt;
local year_min, year_max = nil, nil&lt;br /&gt;
&lt;br /&gt;
for md, entries in pairs(data) do&lt;br /&gt;
	if type(entries) == &amp;#039;table&amp;#039; then&lt;br /&gt;
		for _, entry in ipairs(entries) do&lt;br /&gt;
			local iso = getEntryISODate(entry)&lt;br /&gt;
			if iso then&lt;br /&gt;
				local year = tonumber(iso:sub(1,4))&lt;br /&gt;
				if year then&lt;br /&gt;
					if not by_year[year] then&lt;br /&gt;
						by_year[year] = {}&lt;br /&gt;
					end&lt;br /&gt;
&lt;br /&gt;
					-- store a lightweight copy with a normalized date&lt;br /&gt;
					table.insert(by_year[year], {&lt;br /&gt;
						date = iso,&lt;br /&gt;
						week = entry.week,&lt;br /&gt;
						no1song = entry.no1song,&lt;br /&gt;
						no1album = entry.no1album&lt;br /&gt;
					})&lt;br /&gt;
&lt;br /&gt;
					if not year_min or year &amp;lt; year_min then year_min = year end&lt;br /&gt;
					if not year_max or year &amp;gt; year_max then year_max = year end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
for year, list in pairs(by_year) do&lt;br /&gt;
	table.sort(list, function(a, b)&lt;br /&gt;
		return tostring(a.date or &amp;#039;&amp;#039;) &amp;lt; tostring(b.date or &amp;#039;&amp;#039;)&lt;br /&gt;
	end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- For a given year and target month/day:&lt;br /&gt;
-- 1) pick the last entry whose date &amp;lt;= target&lt;br /&gt;
-- 2) if none, pick the first entry whose date &amp;gt;= target&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
local function pickNearestInYear(year, month, day)&lt;br /&gt;
	local list = by_year[year]&lt;br /&gt;
	if not list then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local target = targetDateString(year, month, day)&lt;br /&gt;
&lt;br /&gt;
	local chosenBefore = nil&lt;br /&gt;
	local firstAfter = nil&lt;br /&gt;
&lt;br /&gt;
	for _, entry in ipairs(list) do&lt;br /&gt;
		local d = tostring(entry.date or &amp;#039;&amp;#039;)&lt;br /&gt;
		if d ~= &amp;#039;&amp;#039; then&lt;br /&gt;
			if d &amp;lt;= target then&lt;br /&gt;
				chosenBefore = entry&lt;br /&gt;
			elseif not firstAfter and d &amp;gt;= target then&lt;br /&gt;
				firstAfter = entry&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return chosenBefore or firstAfter&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function formatEntryParts(entry)&lt;br /&gt;
	local datestr = tostring(entry.date or &amp;#039;&amp;#039;)&lt;br /&gt;
	local year = datestr:sub(1, 4)&lt;br /&gt;
	local week = tonumber(entry.week)&lt;br /&gt;
	local weekPart = &amp;#039;&amp;#039;&lt;br /&gt;
	if week then&lt;br /&gt;
		weekPart = string.format(&amp;#039;[[Week %d]] – &amp;#039;, week)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local songLine, albumLine&lt;br /&gt;
&lt;br /&gt;
	if entry.no1song and entry.no1song ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		local songText = normalizeNo1Field(entry.no1song, false)&lt;br /&gt;
		songLine = string.format(&lt;br /&gt;
			&amp;#039;* %s – %s%s is the #1 song on the charts&amp;#039;,&lt;br /&gt;
			year,&lt;br /&gt;
			weekPart,&lt;br /&gt;
			songText&lt;br /&gt;
		)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if entry.no1album and entry.no1album ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		local albumText = normalizeNo1Field(entry.no1album, true)&lt;br /&gt;
		albumLine = string.format(&lt;br /&gt;
			&amp;#039;* %s – %s%s is the #1 album on the charts&amp;#039;,&lt;br /&gt;
			year,&lt;br /&gt;
			weekPart,&lt;br /&gt;
			albumText&lt;br /&gt;
		)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return songLine, albumLine&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- Main entry point&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	-- prefer whichever frame actually carries month/day: template calls&lt;br /&gt;
	-- put them in parent.args, direct {{#invoke:}} puts them in frame.args&lt;br /&gt;
	local parent = frame:getParent()&lt;br /&gt;
	local pargs = parent and parent.args or {}&lt;br /&gt;
	local fargs = frame.args or {}&lt;br /&gt;
	local args = pargs&lt;br /&gt;
	if not (pargs.month or pargs.day) and (fargs.month or fargs.day) then&lt;br /&gt;
		args = fargs&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local month, day = getMonthDay(args)&lt;br /&gt;
	if not month or not day then&lt;br /&gt;
		return &amp;#039;OnThisDay: could not determine date.&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local lang = mw.getContentLanguage()&lt;br /&gt;
	local currentYear = tonumber(lang:formatDate(&amp;#039;Y&amp;#039;)) or 2025&lt;br /&gt;
&lt;br /&gt;
	-- Use actual min year found in data; fallback to 2020&lt;br /&gt;
	local startYear = year_min or 2020&lt;br /&gt;
&lt;br /&gt;
	local picks = {}&lt;br /&gt;
	for year = startYear, currentYear do&lt;br /&gt;
		local entry = pickNearestInYear(year, month, day)&lt;br /&gt;
		if entry then&lt;br /&gt;
			table.insert(picks, entry)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local songLines, albumLines = {}, {}&lt;br /&gt;
&lt;br /&gt;
	for _, entry in ipairs(picks) do&lt;br /&gt;
		local sLine, aLine = formatEntryParts(entry)&lt;br /&gt;
		if sLine then table.insert(songLines, sLine) end&lt;br /&gt;
		if aLine then table.insert(albumLines, aLine) end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local out = {}&lt;br /&gt;
	table.insert(out, &amp;quot;&amp;#039;&amp;#039;&amp;#039;On this day in King Warz Charts history:&amp;#039;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
	table.insert(out, &amp;#039;&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	if #songLines &amp;gt; 0 then&lt;br /&gt;
		table.insert(out, &amp;quot;&amp;#039;&amp;#039;Songs&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
		for _, line in ipairs(songLines) do&lt;br /&gt;
			table.insert(out, line)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if #albumLines &amp;gt; 0 then&lt;br /&gt;
		table.insert(out, &amp;#039;&amp;#039;)&lt;br /&gt;
		table.insert(out, &amp;quot;&amp;#039;&amp;#039;Albums&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
		for _, line in ipairs(albumLines) do&lt;br /&gt;
			table.insert(out, line)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- day-precise anniversaries from Module:OnThisDay/Extra&lt;br /&gt;
	local mdKey = string.format(&amp;#039;%02d-%02d&amp;#039;, month, day)&lt;br /&gt;
	local ex = extraEvents[mdKey]&lt;br /&gt;
	if ex then&lt;br /&gt;
		local function addSection(title, rows, cap, fmt)&lt;br /&gt;
			if not rows then return end&lt;br /&gt;
			local total = 0&lt;br /&gt;
			for _ in ipairs(rows) do total = total + 1 end&lt;br /&gt;
			if total == 0 then return end&lt;br /&gt;
			table.insert(out, &amp;#039;&amp;#039;)&lt;br /&gt;
			table.insert(out, &amp;quot;&amp;#039;&amp;#039;&amp;quot; .. title .. &amp;quot;&amp;#039;&amp;#039;&amp;quot;)&lt;br /&gt;
			local n = 0&lt;br /&gt;
			for _, r in ipairs(rows) do&lt;br /&gt;
				n = n + 1&lt;br /&gt;
				if cap and n &amp;gt; cap then&lt;br /&gt;
					table.insert(out, string.format(&lt;br /&gt;
						&amp;#039;* &amp;lt;small&amp;gt;...and %d more&amp;lt;/small&amp;gt;&amp;#039;, total - cap))&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(out, fmt(r))&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		addSection(&amp;#039;Artist birthdays&amp;#039;, ex.birthdays, nil, function(r)&lt;br /&gt;
			if r.dead then&lt;br /&gt;
				return string.format(&amp;#039;* %d – [[%s]] was born on this day&amp;#039;,&lt;br /&gt;
					r.year, r.artist)&lt;br /&gt;
			end&lt;br /&gt;
			return string.format(&lt;br /&gt;
				&amp;#039;* %d – [[%s]] was born on this day (turns %d)&amp;#039;,&lt;br /&gt;
				r.year, r.artist, currentYear - r.year)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;Certifications&amp;#039;, ex.certs, 6, function(r)&lt;br /&gt;
			return string.format(&amp;#039;* %d – [[%s]] reached %s&amp;#039;,&lt;br /&gt;
				r.year, r.page, r.label)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;First listens&amp;#039;, ex.firstplays, 5, function(r)&lt;br /&gt;
			return string.format(&amp;#039;* %d – First ever play of [[%s]]&amp;#039;,&lt;br /&gt;
				r.year, r.page)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;Chart debuts&amp;#039;, ex.debuts, 5, function(r)&lt;br /&gt;
			local nm = r.album and (&amp;quot;&amp;#039;&amp;#039;[[&amp;quot; .. r.page .. &amp;quot;]]&amp;#039;&amp;#039;&amp;quot;) or (&amp;quot;[[&amp;quot; .. r.page .. &amp;quot;]]&amp;quot;)&lt;br /&gt;
			local chart = r.album and &amp;#039;album chart&amp;#039; or &amp;#039;chart&amp;#039;&lt;br /&gt;
			return string.format(&amp;#039;* %d – %s debuted on the %s at #%d&amp;#039;,&lt;br /&gt;
				r.year, nm, chart, r.pos)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;First week at No. 1&amp;#039;, ex.crownings, nil, function(r)&lt;br /&gt;
			local nm = r.album and (&amp;quot;&amp;#039;&amp;#039;[[&amp;quot; .. r.page .. &amp;quot;]]&amp;#039;&amp;#039;&amp;quot;) or (&amp;quot;[[&amp;quot; .. r.page .. &amp;quot;]]&amp;quot;)&lt;br /&gt;
			local chart = r.album and &amp;#039; on the album chart&amp;#039; or &amp;#039;&amp;#039;&lt;br /&gt;
			return string.format(&lt;br /&gt;
				&amp;#039;* %d – %s hit #1 for the first time%s ([[Week %d]])&amp;#039;,&lt;br /&gt;
				r.year, nm, chart, r.week)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;Play milestones&amp;#039;, ex.playmilestones, 5, function(r)&lt;br /&gt;
			local nm = r.album and (&amp;quot;&amp;#039;&amp;#039;[[&amp;quot; .. r.page .. &amp;quot;]]&amp;#039;&amp;#039;&amp;quot;) or (&amp;quot;[[&amp;quot; .. r.page .. &amp;quot;]]&amp;quot;)&lt;br /&gt;
			return string.format(&amp;#039;* %d – %s logged its %s play&amp;#039;,&lt;br /&gt;
				r.year, nm, r.mlabel)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;Artist firsts&amp;#039;, ex.artistfirsts, 4, function(r)&lt;br /&gt;
			return string.format(&amp;#039;* %d – First ever play of a [[%s]] song&amp;#039;,&lt;br /&gt;
				r.year, r.artist)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;Listening records&amp;#039;, ex.recorddays, nil, function(r)&lt;br /&gt;
			if r.rank == 1 then&lt;br /&gt;
				return string.format(&lt;br /&gt;
					&amp;#039;* %d – Your busiest listening day on record (%s plays)&amp;#039;,&lt;br /&gt;
					r.year, r.plays)&lt;br /&gt;
			end&lt;br /&gt;
			return string.format(&lt;br /&gt;
				&amp;#039;* %d – Your %s-busiest listening day on record (%s plays)&amp;#039;,&lt;br /&gt;
				r.year, r.ranklabel, r.plays)&lt;br /&gt;
		end)&lt;br /&gt;
		addSection(&amp;#039;Certification crown&amp;#039;, ex.certcrowns, nil, function(r)&lt;br /&gt;
			if r.prev and r.prev ~= &amp;#039;&amp;#039; then&lt;br /&gt;
				return string.format(&lt;br /&gt;
					&amp;#039;* %d – [[%s]] overtook [[%s]] as the most certified song in chart history&amp;#039;,&lt;br /&gt;
					r.year, r.page, r.prev)&lt;br /&gt;
			end&lt;br /&gt;
			return string.format(&lt;br /&gt;
				&amp;#039;* %d – [[%s]] became the most certified song in chart history&amp;#039;,&lt;br /&gt;
				r.year, r.page)&lt;br /&gt;
		end)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if #out &amp;lt;= 2 then&lt;br /&gt;
		return string.format(&lt;br /&gt;
			&amp;#039;No chart history recorded for this date yet (month=%d, day=%d).&amp;#039;,&lt;br /&gt;
			month, day)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return table.concat(out, &amp;#039;\n&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
-- Debug helper&lt;br /&gt;
----------------------------------------------------------------&lt;br /&gt;
function p.debug(frame)&lt;br /&gt;
	local parent = frame:getParent()&lt;br /&gt;
	local args = parent and parent.args or frame.args or {}&lt;br /&gt;
	local month, day = getMonthDay(args)&lt;br /&gt;
&lt;br /&gt;
	local out = {}&lt;br /&gt;
	table.insert(out, string.format(&amp;#039;Debug OnThisDay: month=%d, day=%d&amp;#039;, month or -1, day or -1))&lt;br /&gt;
&lt;br /&gt;
	table.insert(out, string.format(&amp;#039;\nData loaded ok: %s&amp;#039;, tostring(ok)))&lt;br /&gt;
	table.insert(out, &amp;#039;\nTop-level data type: &amp;#039; .. type(data))&lt;br /&gt;
&lt;br /&gt;
	local years = {}&lt;br /&gt;
	for y, _ in pairs(by_year) do&lt;br /&gt;
		table.insert(years, y)&lt;br /&gt;
	end&lt;br /&gt;
	table.sort(years)&lt;br /&gt;
&lt;br /&gt;
	table.insert(out, &amp;#039;\n\nYears indexed in by_year: &amp;#039; .. (#years &amp;gt; 0 and table.concat(years, &amp;#039;, &amp;#039;) or &amp;#039;(none)&amp;#039;))&lt;br /&gt;
	table.insert(out, string.format(&amp;#039;\nYear min/max: %s / %s&amp;#039;, tostring(year_min), tostring(year_max)))&lt;br /&gt;
&lt;br /&gt;
	table.insert(out, string.format(&lt;br /&gt;
		&amp;#039;\n\nWeek1 anchor used: %04d-%02d-%02d&amp;#039;,&lt;br /&gt;
		WEEK1_Y, WEEK1_M, WEEK1_D&lt;br /&gt;
	))&lt;br /&gt;
&lt;br /&gt;
	local currentYear = tonumber(mw.getContentLanguage():formatDate(&amp;#039;Y&amp;#039;)) or 2025&lt;br /&gt;
	local startYear = year_min or 2020&lt;br /&gt;
&lt;br /&gt;
	table.insert(out, &amp;#039;\n\nPicks per year for this month/day:&amp;#039;)&lt;br /&gt;
	for year = startYear, currentYear do&lt;br /&gt;
		local e = pickNearestInYear(year, month, day)&lt;br /&gt;
		if e then&lt;br /&gt;
			table.insert(out, string.format(&lt;br /&gt;
				&amp;#039;\n%d: week=%s, date=%s, song=%s, album=%s&amp;#039;,&lt;br /&gt;
				year,&lt;br /&gt;
				tostring(e.week or &amp;#039;&amp;#039;),&lt;br /&gt;
				tostring(e.date or &amp;#039;&amp;#039;),&lt;br /&gt;
				tostring(e.no1song or &amp;#039;&amp;#039;),&lt;br /&gt;
				tostring(e.no1album or &amp;#039;&amp;#039;)&lt;br /&gt;
			))&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(out, string.format(&amp;#039;\n%d: (no entry)&amp;#039;, year))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return table.concat(out, &amp;#039;\n&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>KWCBot</name></author>
	</entry>
</feed>