-- This module generates the monthly archives .-- See a sample archive at .
---------------------------------------------------------------------------------- Helper functions--------------------------------------------------------------------------------
-- Return true if num is a positive integer; otherwise return falselocal function isPositiveInteger(num) return num > 0 and num
-- Make an ordinal number from an integer.local function makeOrdinalNumber(num) local suffix local rem100 = num % 100 if rem100
12 or rem100
1 then suffix = 'st' elseif rem10
3 then suffix = 'rd' else suffix = 'th' end end return tostring(num) .. suffixend
-- Try to parse the year and month from the current title.-- This template is usually used on pages with titles like--, so our general approach will be to-- pass the subpage name to lang:formatDate and see if we get something that's-- not an error.local function parseYearAndMonthFromCurrentTitle local title = mw.title.getCurrentTitle local lang = mw.language.getContentLanguage -- Detect if we are on a sandbox page, and if so, use the base page. if title.subpageText:find('^[sS]andbox%d*$') then title = title.basePageTitle end -- Try to parse the date. local success, date = pcall(function -- lang:formatDate throws errors if it gets strange input, -- so use pcall to catch them, as random subpage names will -- usually not be well-formed dates. return lang:formatDate('Y-m', title.subpageText) end) if not success then -- We couldn't parse the date, so return nil. return nil, nil end -- Parse the year and month numbers from the date we got from -- lang:formatDate. If we can't parse them, then something has gone -- wrong with either lang:formatDate or our pattern. local year, month = date:match('^(%d%d%d%d)%-(%d%d)$') year = tonumber(year) month = tonumber(month) if not year or not month then error('Internal error in : couldn\'t match date ' .. 'from lang:formatDate output' ) end return year, monthend
---------------------------------------------------------------------------------- Date info--------------------------------------------------------------------------------
-- Get a table of information about the date for the monthly archive.local function getDateInfo(year, month) local lang = mw.language.getContentLanguage local dateFuncs = local dateInfo = setmetatable
function dateFuncs.currentYear -- The current year (number) return tonumber(os.date('%Y')) end
function dateFuncs.currentMonthNumber -- The current month (number) return tonumber(os.date('%m')) end
function dateFuncs.year -- The year (number) return tonumber(year) or dateInfo.currentYear end
function dateFuncs.monthNumber -- The month (number) return tonumber(month) or dateInfo.currentMonthNumber end
function dateFuncs.monthNumberZeroPadded -- The month, zero-padded to two digits (string) return string.format('%02d', dateInfo.monthNumber) end
function dateFuncs.date -- The date in YYYY-MM-DD format (string) return string.format('%04d-%02d-01', dateInfo.year, dateInfo.monthNumber ) end
function dateFuncs.monthName -- The month name, e.g. "September" (string) return lang:formatDate('F', dateInfo.date) end
function dateFuncs.monthOrdinal -- The ordinal month as an English word (string) local ordinals = return ordinals[dateInfo.monthNumber] end
function dateFuncs.beVerb -- If the month is the current month or a month in the future, then this -- is the string "is"; otherwise, "was" (string) if dateInfo.year > dateInfo.currentYear or (dateInfo.year
function dateFuncs.leapDesc -- The year's leap year status; either "common", "leap" or -- "century leap" (string) local isLeapYear = tonumber(lang:formatDate('L', dateInfo.date))
0 then return 'century leap' elseif isLeapYear then return 'leap' else return 'common' end end
function dateFuncs.decadeNote -- If the month is the first or last of a decade, century, or -- millennium, a note to that effect; otherwise the empty string -- (string) local function getMillennium(year) return math.floor((year - 1) / 1000) + 1 -- Fenceposts end
local function getCentury(year) return math.floor((year - 1) / 100) + 1 -- Fenceposts end
local year = dateInfo.year local month = dateInfo.monthNumber local firstOrLast = month
if year % 1000
12 or year % 1000
1 then local millennium = makeOrdinalNumber(getMillennium(year)) local century = makeOrdinalNumber(getCentury(year)) return string.format(--Millenniums always overlap centuries. "It %s the %s month of the %s millennium and the %s century.", dateInfo.beVerb, firstOrLast, millennium, century ) elseif year % 100
12 or year % 100
1 then local century = makeOrdinalNumber(getCentury(year)) return string.format("It %s the %s month of the %s century.", dateInfo.beVerb, firstOrLast, century ) elseif year % 10
12 or year % 10
1 then local decadeNumber = math.floor(dateInfo.year / 10) * 10 return string.format("It %s the %s month of the %ds decade.", dateInfo.beVerb, firstOrLast, decadeNumber ) end
return end
function dateFuncs.moonNote -- If the month had no full moon, a note to that effect; otherwise the -- empty string (string) if dateInfo.monthNumber
1961 or year
2018 or year
2067 or year
return end
function dateFuncs.firstDayOfMonth -- Weekday of the first day of the month, e.g. "Tuesday" (string) return lang:formatDate('l', dateInfo.date) end
function dateFuncs.lastDayOfMonth -- Weekday of the last day of the month, e.g. "Thursday" (string) return lang:formatDate('l', dateInfo.date .. ' +1 month -1 day') end
function dateFuncs.daysInMonth -- Number of days in the month (number) return tonumber(lang:formatDate('j', dateInfo.date .. ' +1 month -1 day') ) end
function dateFuncs.mainContent -- The rendered content of all the current events portal pages for the -- month (string) local ret = local frame = mw.getCurrentFrame local year = dateInfo.year local monthName = dateInfo.monthName for date = 1, 31 do local portalTitle = mw.title.new(string.format('Portal:Current events/%d %s %d', year, monthName, date )) if portalTitle.exists then table.insert(ret, frame:expandTemplate ) end end return table.concat(ret, '\n') end
return dateInfoend
---------------------------------------------------------------------------------- Exports--------------------------------------------------------------------------------
local p =
function p.main(frame) -- Get the arguments local args = require('Module:Arguments').getArgs(frame,) local year = tonumber(args.year) local month = tonumber(args.month)
-- Validate the arguments if year and not isPositiveInteger(year) then error('invalid year argument (must be a positive integer)', 2) end if month then if not isPositiveInteger(month) then error('invalid month argument (must be a positive integer)', 2) elseif month > 12 then error('invalid month argument (must be 12 or less)', 2) end end
-- If we weren't passed a month or a year, try to get them from the -- page title. if not year and not month then year, month = parseYearAndMonthFromCurrentTitle end
-- Convert the dateInfo table values into arguments to pass to the current -- events monthly archive display template local dateInfo = getDateInfo(year, month) local displayArgs = displayArgs['year'] = dateInfo.year displayArgs['month-name'] = dateInfo.monthName displayArgs['month-number'] = dateInfo.monthNumber displayArgs['month-number-zero-padded'] = dateInfo.monthNumberZeroPadded displayArgs['be-verb'] = dateInfo.beVerb displayArgs['month-ordinal'] = dateInfo.monthOrdinal displayArgs['leap-desc'] = dateInfo.leapDesc displayArgs['moon-note'] = dateInfo.moonNote displayArgs['decade-note'] = dateInfo.decadeNote displayArgs['first-day-of-month'] = dateInfo.firstDayOfMonth displayArgs['last-day-of-month'] = dateInfo.lastDayOfMonth displayArgs['days-in-month'] = dateInfo.daysInMonth displayArgs['main-content'] = dateInfo.mainContent
-- Expand the display template with the arguments from dateInfo, and return -- it return frame:expandTemplateend
-- Export getDateInfo so that we can use it in unit tests.p.getDateInfo = getDateInfo
return p