Moduł:Kalendarium

Z Wikipedii, wolnej encyklopedii
Przejdź do nawigacji Przejdź do wyszukiwania
Template-info.png Dokumentacja modułu [zobacz] [edytuj] [historia] [odśwież]

Moduł wspomagający automatyzację treści strony głównej Wikipedii i niektórych portali.

Raport[edytuj kod]

Funkcja do przygotowania podglądu z listą wszystkich dostępnych wydarzeń na zadany dzień.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.

Rocznice[edytuj kod]

Funkcja generująca treść do sekcji Rocznice na stronie głównej.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.

Imieniny[edytuj kod]

Funkcja generująca listę imion na zadany dzień.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.
2 Liczba losowanych imion Np. 4, domyślnie 3.
3 Separator między imionami Można nie wypełniać, domyślnie przecinek.
4 Separator przed ostatnim imieniem Można nie wypełniać, domyślnie „i”.

Święta[edytuj kod]

Funkcja generująca listę świąt na zadany dzień.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.
2 Separator między świętami Najlepiej ;
3 Separator przed ostatnim świętem Najlepiej ;.

Wydarzenia[edytuj kod]

Funkcja generująca listę wydarzeń, których możliwie okrągła rocznica wypada z zadanym dniu.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.
2 Minimalna liczba wydarzeń domyślnie 5
3 Maksymalna liczba obrazków? (do sprawdzenia) domyślnie 2.
4 Maksymalna liczba wydarzeń domyślnie minimalna liczba wydarzeń

Statystyka[edytuj kod]

Funkcja generująca statystykę dla dokumentacji modułu z listą wydarzeń dla konkretnego dnia.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.

ministat[edytuj kod]

Funkcja generująca linię z ministatystyką dla wybranego dnia.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.

leap[edytuj kod]

Funkcja generująca najbliższy rok przestępny względem podanego roku.

Pole Do czego służy? Jak wypełnić?
1 Rok. Jeśli jest przestępny to jest wynikiem funkcji. Jeśli nie jest przestępny to wynikiem jest najbliższy przyszły rok przestępny. Np. 2019.

Wybierz z listy[edytuj kod]

Implementacja {{Wybierz z listy}}.

Ilustracja na medal[edytuj kod]

Funkcja „losująca” ilustrację na medal używana w przypadku, gdy nie jest zdefiniowana zaplanowana ilustracja na zadany dzień.

Pole Do czego służy? Jak wypełnić?
1 Data dzienna Np. 2019-12-11.

Ilustracje wybierane są z galerii Wikipedia:Ilustracja na medal. Bardzo płaskie lub wysokie i wąskie obrazy należy oznaczyć komentarzem <-- PANORAMA szerokość_x_wysokość -->, dzięki któremu pod lub obok ilustracji umieszczany jest pomocniczy pasek przewijania co pozwala na lepszą prezentację. Przykłady można obejrzeć w Wikipedia:Ilustracja na medal/geografia.

Błędy[edytuj kod]

Błędy należy zgłaszać na stronie Wikipedia:Kawiarenka/Kwestie techniczne.

Zobacz też[edytuj kod]

local resources = {
	removedFileMatch = "%s?%(%(%-%-%s*(.-)||(.-)%)%)",
	fileMatch = "%s?%(%(%s*(.-)||(.-)%)%)",
	fileMark = "%s?%(%(%s*.-||.-%)%)",
	fileHeight = "^%s*(%d%d?%d?)%s*|(.-)$",
	formatAnniversaries = '<div class="floatright" style="width: 100px; line-height: 1.5ex;">[[Plik:%2|border|100px|%1]]<small>[[:c:File:%2|%1]]</small></div>',
	formatReport = '<div class="floatright" style="width: 60px; line-height: 1.5ex;">[[Plik:%2|border|60px|%1]]<small>[[:c:File:%2|%1]]</small></div>',
	yearBCmatch = "^(%d%d?%d?%d?) p%.n%.e%.$",
}

local function isLeap(year)
	return ((year % 4) == 0) and (((year % 100) ~= 0) or ((year % 400) == 0))
end

local function loadOffsetDay(offset)
    if offset < -1 then
        return string.format(" %d days", offset)
    elseif offset == -1 then
        return " -1 day"
    elseif offset == 0 then
        return ""
    elseif offset == 1 then
        return " +1 day"
    else -- if offset > 1 then
        return string.format(" +%d days", offset)
    end
end
 
local function loadFeasts(params)
	local lang = mw.getContentLanguage()
	local feasts = mw.loadData( 'Module:Kalendarium/święta' )
	local date = params.year.."-"..params.base
	local romanEaster = false
	local orthodoxEaster = false
 
	local result = {}
	for _, v in ipairs(feasts) do
		if v.type == "fixed" then
			local info = v[params.base]
			if info then
				for _, w in ipairs(info) do
					table.insert(result, w)
				end
			end
		elseif v.type == "hebrew" then
			local info0 = v[params.hebrew0]
			if info0 then
				table.insert(result, info0)
			end
			local info1 = v[params.hebrew1]
			if info1 then
				table.insert(result, info1)
			end
		elseif v.type == "islamic" then
			local info0 = v[params.islamic0]
			if info0 then
				table.insert(result, info0)
			end
			local info1 = v[params.islamic1]
			if info1 then
				table.insert(result, info1)
			end
		elseif v.type == "other" then
			-- standard fixed date
			for _, w in ipairs(v) do
				if w == date then
					table.insert(result, v.info)
					break
				end
			end
		elseif (v.type == "easter") and (params.year >= 326) and (params.year <= 4099) then
			local date1 = false
			if v.info1 then
				if not orthodoxEaster then
					orthodoxEaster = require("Moduł:Wielkanoc").Wyznacz({args = { tostring(params.year), metoda = "wschodnia", format="nie", ["dzień"]=0, },})
				end
 
				date1 = lang:formatDate("Y-m-d", orthodoxEaster..loadOffsetDay(v.offset)) == date
			end
 
			local date2 = false
			if v.info2 then
				if not romanEaster then
					romanEaster = require("Moduł:Wielkanoc").Wyznacz({args = { tostring(params.year), metoda = "zachodnia", format="nie", ["dzień"]=0, },})
				end
 
				date2 = lang:formatDate("Y-m-d", romanEaster..loadOffsetDay(v.offset)) == date
			end
 
			if date1 and date2 and v.info3 then
				table.insert(result, v.info3)
			else
				if date1 then
					table.insert(result, v.info1)
				end
				if date2 then
					table.insert(result, v.info2)
				end
			end
		else
			-- type not supported
		end
	end
 
 	return #result > 0 and mw.text.listToText(result, params.feastSeparator, params.feastConjunction) or ""
end

local function prepareDataIndex(data, except)
	-- prepare set of excluded values
	local exceptions = {}
	for _, v in ipairs(except or {}) do
		exceptions[v] = true
	end
 
	local result = {}
	for i, _ in ipairs(data or {}) do
		if not exceptions[i] then
			table.insert(result, i)
		end
	end
 
	return result
end
 
local function selectRandomItems(index, count)
	if #index <= count then
		return index
	end
 
	local result = {}
	while count > 0 do
		local i = math.random(1, #index)
		table.insert(result, index[i])
		table.remove(index, i)
		count = count - 1
	end
 
	return result
end
 
local function selectRandomData(data, count, except)
	-- prepare source index
	local fullIndex = prepareDataIndex(data, except)
	local index = selectRandomItems(fullIndex, count)
	table.sort(index)
 
	-- prepare result
	local result = {}
	for _, i in ipairs(index) do
		table.insert(result, data[i])
	end
 
	return result
end

local function loadDate(date)
	local lang = mw.getContentLanguage()
	-- minimize usage of #time function due to 6000 limit of output characters
	local stamp = lang:formatDate("U", date, true)
	local ymd = lang:formatDate("Ymd", date or ("@"..stamp), true)
	local year = tonumber(string.sub(ymd, 1, 4))
	return {
		date = date,
		seed = tonumber(stamp) + year, -- better seed
		year = year,
		base = string.sub(ymd, 5, 6).."-"..string.sub(ymd, 7, 8),
		hebrew0 = lang:formatDate("xjn-xjj", date, true),
		hebrew1 = lang:formatDate("xjn+xjj", date.." +1 day", true),
		islamic0 = lang:formatDate("xmn+xmj", date, true),
		islamic1 = lang:formatDate("xmn-xmj", date.." +1 day", true),
	}
end

local function chooseNames(params)
	local data = mw.loadData("Module:Kalendarium/imieniny")
	math.randomseed(params.seed)
	local result = {}
	for _, v in ipairs(data[params.base].zawsze or {}) do
		table.insert(result, v)
	end

	local count = params.namesCount - #result
	for _, v in ipairs(count <= 0 and {} or selectRandomData(data[params.base], count)) do
		table.insert(result, v)
	end
	
	if #result > 1 then
		local sort = function(a,b)
			local lang = mw.getContentLanguage()
			return lang:caseFold(a or "") < lang:caseFold(b or "")
		end
		table.sort(result, sort)
	end
	
	return mw.text.listToText(result, params.namesSeparator, params.namesConjunction)
end

local function loadYear(year)
	if type(year) == "number" then
		return ((year > 0) and (year <= 9999)) and year or false
	elseif type(year) ~= "string" then
		return false
	end

	local yearpne = string.match(year, resources.yearBCmatch)
	if not yearpne then
		return false
	end

	return 1-tonumber(yearpne)
end

local function loadEvents(events, data, accept, maximumFiles, maximumEvents, fileFormat)
	-- extract events that satisfy the selection rule
	local files = {}
	local candidates = {}
	for _, v in ipairs(data) do
		local event = {}
		event.year = loadYear(v.rok)
		event.priority = v.priorytet
		event.alone = v.oddzielny or false
		event.files = {}
		if event.year and v.tekst and accept(event) then
			table.insert(candidates, event)
			local text = v.tekst
			while true do
				local info, file = mw.ustring.match(text, resources.fileMatch)
				if not file then
					event.text = text
					break
				end

				local size = false
				local size2, info2 = mw.ustring.match(info, resources.fileHeight)
				size2 = tonumber(size2)
				if size2 then
					size = size2 / 140 -- normalize the scale
					info = info2
				end

				local fileInfo = {
					index = #candidates,
					info = info,
					size = size or 1,
					file = file,
				}

				text = mw.ustring.gsub(text, resources.fileMark, "(**)", 1)
				table.insert(files, fileInfo)
				table.insert(event.files, fileInfo)
			end
		end
	end

	local currentFiles = 0
	for _, v in ipairs(events) do
		currentFiles = currentFiles + v.size
	end

	local selected = {}

	-- select files for presentation
	local usedEventsWithImagesMap = {}
	while (#events < maximumEvents) and (currentFiles < maximumFiles) and (#files > 0) do
		local i = (#files > 1) and math.random(1, #files) or 1
		local f = files[i]
		table.remove(files,i)
		local space = maximumFiles - currentFiles
		if (f.size <= space) or (currentFiles == 0) then
			-- use the file
			f.used = true
			currentFiles = currentFiles + f.size
			local event = candidates[f.index]
			if not usedEventsWithImagesMap[f.index] then
				usedEventsWithImagesMap[f.index] = true
				table.insert(events, event)
				table.insert(selected, event)
			end
			-- render the final info and files
			f.info = " <small>(''"..f.info.."'')</small>"
			f.file = mw.ustring.gsub(f.file, "([^|]+)|([^|]+)|?", fileFormat)
		end
	end

	-- select remaining events for presentation
	if #events < maximumEvents then
		local index = {}
		for i, _ in ipairs(candidates) do
			if not usedEventsWithImagesMap[i] then
				table.insert(index, i)
			end
		end
	
		while (#events < maximumEvents) and (#index > 0) do
			local i = (#index > 1) and math.random(1, #index) or 1
			local e = candidates[index[i]]
			table.remove(index, i)
			table.insert(events, e)
			table.insert(selected, e)
		end
	end
	
	-- render selected events
	for i, v in ipairs(selected) do
		local size = 0
		local files = {}
		for j, f in ipairs(v.files) do
			if f.used then
				v.text = mw.ustring.gsub(v.text, "%(%*%*%)", f.info, 1)
				table.insert(files, f.file)
				size = size + f.size
			else
				v.text = mw.ustring.gsub(v.text, "%(%*%*%)", "", 1)
			end
		end
		v.size = size
		if #files > 0 then
			v.image = table.concat(files, "")
		end
	end
	
	return #selected
end

local function compactEvents(events)
	local function join(i,j)
		if i == j then
			-- nothing to compact - simple copy is enough
			return events[i]
		end
 
		-- split events into merge categories
		local bi = {} local bt = {} local bf=0 local bm=0
		local di = {} local dt = {} local df=0 local dm=0
		local oi = {} local ot = {}
		while i <= j do
			local text = events[i].text
			local ba, b = mw.ustring.match(text, "^urodził(a?) się(.+)$")
			local da, d = mw.ustring.match(text, "^zmarł(a?)(.+)$")
			if b then
				if ba == "a" then bf = bf + 1 else bm = bm + 1 end
				table.insert(bi, i)
				table.insert(bt, b)
			elseif d then
				if da == "a" then df = df + 1 else dm = dm + 1 end
				table.insert(di, i)
				table.insert(dt, d)
			else
				table.insert(oi, i)
				table.insert(ot, text)
			end
 
			i = i + 1
		end
 
		local images = {}
		for _, v in ipairs(bi) do
			local image = events[v].image
			if image then
				table.insert(images, image)
			end
		end
		for _, v in ipairs(di) do
			local image = events[v].image
			if image then
				table.insert(images, image)
			end
		end
		for _, v in ipairs(oi) do
			local image = events[v].image
			if image then
				table.insert(images, image)
			end
		end
	
		local buffer = {}
		if #bt == 1 then
			table.insert(buffer, events[bi[1]].text)
		elseif #bt > 1 then
			local phrase = (bm ~= 0 and "urodzili się: " or "urodziły się: ")..mw.text.listToText(bt, ", ", ", oraz ")
			table.insert(buffer, phrase)
		end
		if #dt == 1 then
			table.insert(buffer, events[di[1]].text)
		elseif #dt > 1 then
			local phrase = (dm ~= 0 and "zmarli: " or "zmarły: ")..mw.text.listToText(dt, ", ", ", oraz ")
			table.insert(buffer, phrase)
		end
		for _, v in ipairs(ot) do
			table.insert(buffer, v)
		end
 
		return {
			year = events[j].year,
			text = mw.text.listToText(buffer, "; ", "; tegoż dnia "),
			image = #images > 0 and table.concat(images, "") or nil,
		}
	end
 
	local result = {}
 
	local i0 = false
	local lastYear = false
 
	for i, v in ipairs(events) do
		if v.alone or v.year ~= lastYear then
			if i0 then
				table.insert(result, join(i0, i-1))
			end
			i0 = i
			lastYear = not v.alone and v.year
		end
	end
	if i0 then
		table.insert(result, join(i0,#events))
	end
 
	return result
end

local function expandEmbeddedTemplates(events)
	local frame = mw.getCurrentFrame()
 
	local function expand(substring)
		return mw.getCurrentFrame():preprocess(substring)
	end
 
	for i, v in ipairs(events) do
		v.text = mw.ustring.gsub(v.text, "{{.-}}", expand)
	end
end

local function chooseEvents(params)
	local data = {}
	for _, v in ipairs(mw.loadData( 'Module:Kalendarium/'..params.base )) do
		local event = {
			rok = v.rok,
			oddzielny = v.oddzielny,
			priorytet = v.priorytet,
		}
		if v.tekst then
			event.tekst = mw.ustring.gsub(v.tekst, resources.removedFileMatch, "")
		end
		
		table.insert(data, event)
	end
	
	math.randomseed(params.seed)
	local events = {}
	local count10 = loadEvents(events, data, function(event) local delta = params.year - event.year return (delta > 0) and ((delta % 10) == 0) and event.priority end, params.maximumImages, params.maximumEvents, params.format)
	local count5 = loadEvents(events, data, function(event) local delta = params.year - event.year return (delta > 0) and ((delta % 10) == 5) and event.priority end, params.maximumImages, params.maximumEvents, params.format)
	local count1 = 0
	if #events < params.minimumEvents then
		count10 = count10 + loadEvents(events, data, function(event) local delta = params.year - event.year return (delta > 0) and ((delta % 10) == 0) and not event.priority end, params.maximumImages, params.maximumEvents, params.format)
	end

	if #events < params.minimumEvents then
		count5 = count5 + loadEvents(events, data, function(event) local delta = params.year - event.year return (delta > 0) and ((delta % 10) == 5) and not event.priority end, params.maximumImages, params.maximumEvents, params.format)
	end

	if #events < params.minimumEvents then
		count1 = loadEvents(events, data, function(event) local delta = params.year - event.year return (delta > 0) and ((delta % 5) ~= 0) end, params.maximumImages, params.maximumEvents, params.format)
	end

	local info = "Wybrane rocznice:"
	if count1 > 0 then
	elseif count5 > 0 then
		info = "Okrągłe, pięcioletnie rocznice:"
	elseif count10 > 0 then
		info = "Okrągłe, dziesięcioletnie rocznice:"
	end
	
	-- sort events by year, move "alone" events to first position
	table.sort(events, function(a,b) return (a and (a.year+(a.alone and 0 or 0.5)) or 0) < (b and (b.year+(b.alone and 0 or 0.5)) or 0) end)
	
	local events = compactEvents(events)
	
	expandEmbeddedTemplates(events)
	
	-- convert years to text
	for _, v in ipairs(events) do
		v.year = v.year > 0 and tostring(v.year) or tostring(1-v.year).." p.n.e."
	end
	
	-- format output
	local result = {}
	if #events > 0 then
		-- print header
		table.insert(result, info)
		-- print images
		for i, v in ipairs(events) do
			if v.image then
				table.insert(result,"\n")
				table.insert(result,v.image)
			end
		end
		-- print events
		local lastYear = nil
		for i, v in ipairs(events) do
			table.insert(result, "\n* ")
			if v.year ~= lastYear then
				table.insert(result, "[[")
			end
			table.insert(result, v.year)
			if v.year ~= lastYear then
				table.insert(result, "]]")
			end
			table.insert(result, " – ")
			table.insert(result, v.text)
			lastYear = v.year
		end
	end

	return table.concat(result, "")
end

local function anniversaries(params)
	params.namesSeparator = ", "
	params.namesConjunction = " i "
	params.feastSeparator = "; "
	params.feastConjunction = "; "

	-- format output
	local result = {}
	table.insert(result, "'''[[")
	table.insert(result, mw.getContentLanguage():formatDate("j xg", params.year.."-"..params.base, true))
	table.insert(result, "]]''': [[imieniny]] obchodzą m.in.: ")
	table.insert(result, chooseNames(params))
	local feasts = loadFeasts(params)
	if #feasts > 0 then
		table.insert(result, params.feastSeparator)
		table.insert(result, feasts)
	end
	local events = chooseEvents(params)
	if #events > 0 then
		table.insert(result, "<br/>")
		table.insert(result, events)
	end
	table.insert(result, "<br style=\"clear:right;\" />")
	
	return table.concat(result, "")
end

local function statistics(params)
	local success, data = pcall(mw.loadData, 'Module:Kalendarium/'..params.base)
	if not success then
		data = {}
	end
	local stats =
	{
		[0] = { count = 0, text = 0, images = 0 },
		[1] = { count = 0, text = 0, images = 0 },
		[2] = { count = 0, text = 0, images = 0 },
		[3] = { count = 0, text = 0, images = 0 },
		[4] = { count = 0, text = 0, images = 0 },
		[5] = { count = 0, text = 0, images = 0 },
	}
	for _, v in ipairs(data) do
		local year = loadYear(v.rok)
		local stat = stats[year and (((year - params.year) % 5) + 1) or 0]
		local text, count = mw.ustring.gsub(v.tekst, "%(%(.-%)%)", "")
		text = mw.ustring.gsub(text, "{{.-}}", "")
		stat.count = stat.count + 1
		stat.text = stat.text + mw.ustring.len(text)
		stat.images = stat.images + count
	end

	local years = {
		params.year,
		params.year + 1,
		params.year + 2,
		params.year + 3,
		params.year + 4,
	}
	if params.base == "02-29" then
		for i = 0, 4 do
			local baseYear = params.year
			while not isLeap(baseYear) or (((baseYear - params.year) % 5) ~= i) do
				baseYear = baseYear + 1
			end
			years[i + 1] = baseYear
		end
	end

	local infos = {}
	local lang = mw.getContentLanguage()
	for i, v in ipairs(stats) do
		local year = tostring(years[i])
		local hint = string.format(params.statisticsHint, year % 5, 5 + (year % 5))
		local count = params.statisticsCount and string.format(lang:convertPlural(v.count, params.statisticsCount), v.count) or tostring(v.count)
		local text = params.statisticsSize and string.format(lang:convertPlural(v.text, params.statisticsSize), v.text) or tostring(v.text)
		local images = params.statisticsFile and string.format(lang:convertPlural(v.images, params.statisticsFile), v.images) or tostring(v.images)
		local status = v.count < params.minimumEvents and params.statisticsNotReady or v.images <= 0 and params.statisticsNoFiles or v.count <= params.maximumEvents and params.statisticsMinimum or params.statisticsReady
		local info = string.format(params.statisticsFormat, year, hint, count, images, text, status)
		table.insert(infos, { year, info })
	end

	table.sort(infos, function(a,b) return a[1] < b[1] end)

	local result = {}
	for _, v in ipairs(infos) do
		table.insert(result, v[2])
	end
 
	if stats[0].count > 0 then
		local v = stats[0]
		local count = params.statisticsCount and string.format(lang:convertPlural(v.count, params.statisticsCount), v.count) or tostring(v.count)
		local text = params.statisticsSize and string.format(lang:convertPlural(v.text, params.statisticsSize), v.text) or tostring(v.text)
		local images = params.statisticsFile and string.format(lang:convertPlural(v.images, params.statisticsFile), v.images) or tostring(v.images)
		local info = string.format(params.statisticsOther, count, images, text)
		table.insert(result, info)
	end
 
	return table.concat(result, params.statisticsSeparator)
end

function selectFewFromList(params)
	if not params.text then
		return nil
	end
	
	local lines = mw.text.split(params.text, params.splitter, false)
	if params.trimPrefix then
		if #lines[1] == 0 then
			table.remove(lines,1)
		elseif #params.trimPrefix > 0 then
			lines[1] = mw.ustring.gsub(lines[1], params.trimPrefix, "", 1)
		end
	end

	if (#lines > 1) and params.trimSuffix then
		lines[#lines] = mw.ustring.gsub(lines[#lines], params.trimSuffix, "", 1)
	end

	local i = 1
	while i <= #lines do
		if params.drop and mw.ustring.match(lines[i], params.drop) then
			table.remove(lines, i)
		elseif params.accept and not mw.ustring.match(lines[i], params.accept) then
			table.remove(lines, i)
		else
			i = i + 1
		end
	end

	local count = #lines <= params.count and #lines or params.count
	if not params.seed then
		return params.prefix..table.concat(lines, params.joiner, 1, count)..params.suffix
	end

	math.randomseed(params.seed)
	local result = selectRandomItems(lines, count)
	return params.prefix..table.concat(result, params.joiner)..params.suffix
end

return {
	["Raport"] = function(frame)
		local params = loadDate(frame.args[1])
		params.namesCount = 1e10
		params.minimumEvents = 1e10
		params.maximumImages = 1e10
		params.maximumEvents = 1e10
		params.format = resources.formatReport
		return anniversaries(params)
	end,
	
	["Rocznice"] = function(frame)
		local params = loadDate(frame.args[1])
		params.namesCount = 3
		params.minimumEvents = 6
		params.maximumImages = 2
		params.maximumEvents = 7
		params.format = resources.formatAnniversaries
		return anniversaries(params)
	end,
	
	["Imieniny"] = function(frame)
		local params = loadDate(frame.args[1])
		params.namesCount = tonumber(frame.args[2]) or 3
		params.namesSeparator = frame.args[3]
		params.namesConjunction = frame.args[4]
		return chooseNames(params)
	end,
	
	["Święta"] = function(frame)
		local params = loadDate(frame.args[1])
		params.feastSeparator = frame.args[2]
		params.feastConjunction = frame.args[3]
		return loadFeasts(params)
	end,
	
	["Wydarzenia"] = function(frame)
		local params = loadDate(frame.args[1])
		params.minimumEvents = tonumber(frame.args[2]) or 5
		params.maximumImages = tonumber(frame.args[3]) or 2
		params.maximumEvents = tonumber(frame.args[4]) or params.minimumEvents
		params.format = resources.formatAnniversaries
		return chooseEvents(params)
	end,
	
	["Statystyka"] = function(frame)
		local params = loadDate(frame.args[1])
		params.minimumEvents = 6
		params.maximumEvents = 7
		params.statisticsFormat = "\n*'''%s'''%s – %s, %s, %s%s"
		params.statisticsOther = "\n*'''INNE''' – %s, %s, %s"
		params.statisticsHint = " (<small>lata zakończone na</small> '''%d''' <small>lub</small> '''%d''')"
		params.statisticsSeparator = ""
		params.statisticsCount = { "%d wydarzenie", "%d wydarzenia", "%d wydarzeń" }
		params.statisticsSize =  { "%d znak", "%d znaki", "%d znaków" }
		params.statisticsFile = { "%d grupa plików", "%d grupy plików", "%d grup plików" }
		params.statisticsReady = ""
		params.statisticsMinimum = " → wymagane minimum"
		params.statisticsNotReady = " → <span style=\"color:red\">WYMAGA UZUPEŁNIENIA</span>"
		params.statisticsNoFiles = " → <span style=\"color:blue\">BRAK PLIKÓW</span>"
		return statistics(params)
	end,
 
	["ministat"] = function(frame)
		local params = loadDate(frame.args[1])
		params.minimumEvents = 6
		params.maximumEvents = 7
		params.statisticsFormat = "'''%s'''%s: %s, %s, <small>%s</small> %s"
		params.statisticsOther = "'''INNE''': %s, %s, <small>%s</small>"
		params.statisticsHint = ""
		params.statisticsSeparator = "; "
		params.statisticsReady = ""
		params.statisticsMinimum = " → <span style=\"color:#2EB82E\">MINI</span>"
		params.statisticsNotReady = " → <span style=\"color:red\">UZUPEŁNIĆ</span>"
		params.statisticsNoFiles = " → <span style=\"color:blue\">BRAK PLIKÓW</span>"
		return statistics(params)
	end,

	["leap"] = function(frame)
		local year = tonumber(frame.args[1])
		if not year or ((year % 1) ~= 0) then
			return
		end
		
		while not isLeap(year) do
			year = year + 1
		end

		return year
	end,

	["Wybierz z listy"] = function(frame)
		local f = frame:getParent()
		local params = {}
		params.seed = tonumber(f.args.los)
		params.count = tonumber(f.args.ile) or 5
		params.accept = f.args.akceptuj
		params.drop = f.args["odrzuć"]
		params.text = f.args[1]
		params.splitter =  "%s-\n%*%s*"
		params.joiner = "\n*"
		params.prefix = params.count > 1 and "*" or ""
		params.suffix = ""
		params.trimPrefix = "^%*%s*"
		params.trimSuffix = "%s+$"
		return selectFewFromList(params)
	end,
	["Ilustracja na medal"] = function (frame)
 
		local function PrepareDisplayQueue(sources)
			local buffer = {}
			for _, v in ipairs(sources) do
				table.insert(buffer, v)
			end
 
			table.sort(buffer, function(a,b) return a.frequency < b.frequency end)
			local result = {}
			for _, v in ipairs(buffer) do
				local c = v.frequency
				local n = c + #result
				local d = n/c
				local i = 0
				while i < c do
					local t = math.floor(i*d) + 1
					table.insert(result, t, { frequency=v.frequency, index=i, namespace=v.namespace, title=v.title})
					i = i + 1
				end
			end
 
			return result
		end
 
		local function LoadGalleries(source)
			local sections = mw.text.split(mw.title.makeTitle(source.namespace, source.title):getContent(), "=+[^=]+=+\n")
			local result = {}
			for i, v in ipairs(sections) do
				local gallery = string.match(v, "<gallery.->(.+)</gallery>")
				if gallery then
					table.insert(result, gallery)
				end
			end
 
			return result
		end
 
		local function LoadFiles(gallery)
			local files = mw.text.split(gallery, "\n")
			local result = {}
			for i, v in ipairs(files) do
				local file, description = string.match(v, "^(Plik:.-)|(.+)")
				if file then
					table.insert(result, { file = file, description = description })
				end
			end
 
			return result
		end
 
		local function ChooseImage(date, sources)
			local lang = mw.getContentLanguage()
			local timestamp = lang:formatDate("U", date, true)
			local daystamp = math.floor(math.abs(timestamp) / 86400) -- seconds per day -> number of days since epoch
			local queue = PrepareDisplayQueue(sources)
			local sourceIndex = 1 + math.fmod(daystamp, #queue)
			local gallerystamp = math.floor(daystamp / #queue) -- number of full periods
			local selectedSource = queue[sourceIndex]
			mw.log(selectedSource.title.." ("..daystamp..", "..gallerystamp..", "..sourceIndex..")")
			local galleries = LoadGalleries(selectedSource)
			local sourceDisplays = (gallerystamp * selectedSource.frequency) + selectedSource.index
			local galleryIndex = 1 + math.fmod(sourceDisplays, #galleries)
			local filestamp = math.floor(sourceDisplays / #galleries) -- number of full periods in selected source
			mw.log("frequency: "..selectedSource.frequency..", index: "..selectedSource.index..", displays: "..sourceDisplays..", galleryIndex: "..galleryIndex..", filestamp: "..filestamp)
			local files = LoadFiles(galleries[galleryIndex])
			local fileIndex = 1 + math.fmod(filestamp, #files)
			local filerecord = files[fileIndex]
			mw.log(filerecord.file.."|"..filerecord.description.."| ("..galleryIndex..", "..filestamp..", "..fileIndex..")")
			local width, height = string.match(filerecord.description, "<!%-%-PANORAMA ([1-9]%d?%d?%d?%d?)x([1-9]%d?%d?%d?%d?)%-%->")
			if width then
				width = tonumber(width)
				height = tonumber(height)
			end
			local image = false
			if width and (height > 0) and (width > (2 * height)) then
				image = string.format("<div class=\"thumb\" style=\"width:100%%;margin-left: auto; margin-right:auto;\"><div class=\"thumbinner\"><div style=\"overflow:auto; overflow-y:hidden; overflow-x:auto;\">[[%s|%dpx]]</div></div></div>", filerecord.file, math.floor(250*tonumber(width)/tonumber(height)))
			elseif width and ((2 * width) < height) then
				image = string.format("<div class=\"thumb\" style=\"width:280px; margin-left: auto; margin-right:auto;\"><div class=\"thumbinner\"><div style=\"height:400px; overflow:auto; overflow-x:hidden; overflow-x:auto;\">[[%s|250px]]</div></div></div>", filerecord.file)
			else
				image = string.format("[[%s|400x400px]]", filerecord.file)
			end
			return string.format("<div style=\"text-align:center; clear:both;\">%s<div style=\"margin-top:5px;\">%s<br /></div></div>", image, filerecord.description)
		end
 
		local sources = {
			{ frequency = 3, namespace = "Wikipedia", title = "Ilustracja na medal/architektura", },
			{ frequency = 7, namespace = "Wikipedia", title = "Ilustracja na medal/biologia", },
			{ frequency = 1, namespace = "Wikipedia", title = "Ilustracja na medal/chemia", },
			{ frequency = 5, namespace = "Wikipedia", title = "Ilustracja na medal/geografia", },
			{ frequency = 1, namespace = "Wikipedia", title = "Ilustracja na medal/heraldyka", },
			{ frequency = 1, namespace = "Wikipedia", title = "Ilustracja na medal/ludzie", },
			{ frequency = 1, namespace = "Wikipedia", title = "Ilustracja na medal/rysunki i schematy", },
			{ frequency = 2, namespace = "Wikipedia", title = "Ilustracja na medal/sztuka", },
			{ frequency = 2, namespace = "Wikipedia", title = "Ilustracja na medal/technika", },
			{ frequency = 1, namespace = "Wikipedia", title = "Ilustracja na medal/inne", }
		}
 
		local status, result = pcall(ChooseImage, frame.args[1], sources)
		mw.log("status: "..(status and "SUCCEEDED" or "FAILED")..", result: "..(result or ""))
		return status and result or nil
	end,
}