Module:Infobox calendar date
Appearance
| This module is rated as ready for general use. It has reached a mature state, is considered relatively stable and bug-free, and may be used wherever appropriate. It can be mentioned on help pages and other Wikipedia resources as an option for new users. To minimise server load and avoid disruptive output, improvements should be developed through sandbox testing rather than repeated trial-and-error editing. |
| Editing of this module by new or unregistered users is currently disabled. See the protection policy and protection log for more details. If you cannot edit this module and you wish to make a change, you can submit an edit request, discuss changes on the talk page, request unprotection, log in, or create an account. |
This module implements the template {{Infobox calendar date}}; refer to its documentation.
require('strict')
local getArgs = require('Module:Arguments').getArgs
local InfoboxBuilder = require('Module:InfoboxBuilder').InfoboxBuilder
local InfoboxImage = require('Module:InfoboxImage')
local p = {}
local function trim(value)
if value == nil then
return nil
end
value = mw.text.trim(tostring(value))
if value == '' then
return nil
end
return value
end
local function yes(value)
value = trim(value)
if not value then
return false
end
return value == 'true'
end
local function callTime(frame, format, date)
return frame:callParserFunction {
name = '#time',
args = { format, date }
}
end
local function expand(frame, title, args)
return frame:expandTemplate {
title = title,
args = args or {}
}
end
local function dateArgs(date)
return {
tostring(date.year),
tostring(date.month),
tostring(date.day)
}
end
local function renderTemplate(frame, date, title, extraArgs)
local args = dateArgs(date)
if extraArgs then
for key, value in pairs(extraArgs) do
args[key] = value
end
end
return expand(frame, title, args)
end
local function wikilink(target, display)
if display and display ~= target then
return string.format('[[%s|%s]]', target, display)
end
return string.format('[[%s]]', target)
end
local function yearLink(year, alwaysShowEra, commonEra, julian)
year = tonumber(year)
local displayYear = math.abs(year)
local target = displayYear
local prefix = ''
local suffix = ''
if year <= 0 then
if commonEra then
target = displayYear + 1 .. ' BC'
else
target = displayYear .. ' BC'
displayYear = julian and displayYear or displayYear + 1 -- The Gregorian calendar needs adjustment for BC years, but the Julian calendar doesn't
end
end
if commonEra then
if year < 0 then
suffix = ' ' .. wikilink('Common Era', 'BCE')
else
if alwaysShowEra then suffix = ' ' .. wikilink('Common Era', 'CE') end
end
else
if year <= 0 then
suffix = ' ' .. wikilink('Before Christ', 'BC')
else
if alwaysShowEra then prefix = wikilink('Anno Domini', 'AD') .. ' ' end
end
end
return prefix .. wikilink(target, displayYear) .. suffix
end
local function renderMainDate(frame, date)
local weekday = callTime(frame, 'l', date.timestamp)
local day = date.day
local month = callTime(frame, 'F', date.timestamp)
local year = date.year
return string.format("'''" .. '%s, %s %s' .. "'''" , wikilink(weekday), wikilink(day .. ' ' .. month), yearLink(year, false, true))
end
local calendars = {
{
key = 'akan',
label = '[[Akan calendar|Akan]]',
render = function(frame, date)
return renderTemplate(frame, date, 'AKANCALENDAR')
end
},
{
key = 'armenian',
label = '[[Armenian calendar|Armenian]]',
render = function(frame, date)
return renderTemplate(frame, date, 'ARMENIANDATE', { format = 'DMY' })
end,
earliest = { 552, 7, 13 }
},
{
key = 'aztec',
label = '[[Aztec calendar|Aztec]]',
render = function(frame, date)
return renderTemplate(frame, date, 'AZTECCALENDAR')
end
},
{
key = 'babylonian',
label = '[[Babylonian calendar|Babylonian]]',
render = function(frame, date)
return renderTemplate(frame, date, 'BABYLONIANDATE', { format = 'DMY' }) .. ' [[Seleucid era|SE]]'
end,
earliest = { -310, 3, 29 }
},
{
key = 'pawukon',
label = '[[Pawukon calendar|Balinese pawukon]]',
render = function(frame, date)
return renderTemplate(frame, date, 'BALINESEPAWUKONCALENDAR')
end
},
{
key = 'bengali',
label = '[[Bengali calendar|Bengali]]',
render = function(frame, date)
return string.format('%s, %s %s', renderTemplate(frame, date, 'BENGALIDATE'), wikilink('Bengali calendar', 'BS'), renderTemplate(frame, date, 'BENGALIYEAR'))
end,
earliest = { 594, 4, 3 }
},
{
key = 'chinese',
label = '[[Chinese calendar|Chinese]]',
render = function(frame, date)
return renderTemplate(frame, date, 'CHINESECALENDAR', { show_ganzhi = 'true', show_jieqi = 'true' })
end
},
{
key = 'common',
label = '[[Common Era]]',
render = function(frame, date)
local day = date.day
local month = callTime(frame, 'F', date.timestamp)
local year = date.year
return string.format('%s %s', wikilink(day .. ' ' .. month), yearLink(year, true, true))
end
},
{
key = 'coptic',
label = '[[Coptic calendar|Coptic]]',
render = function(frame, date)
local target = renderTemplate(frame, date, 'COPTICDATE', { format = 'MD' })
local display = renderTemplate(frame, date, 'COPTICDATE', { format = 'DM' })
local year = renderTemplate(frame, date, 'COPTICYEAR')
return string.format('[[%s|%s]], %s %s', target, display, wikilink('Era of the Martyrs', 'AM'), year)
end,
earliest = { 284, 8, 29 }
},
{
key = 'egyptian',
label = '[[Egyptian calendar|Egyptian]]',
render = function(frame, date)
return renderTemplate(frame, date, 'EGYPTIANDATE', { format = 'DMY' }) .. ' [[Nabonassar era|NE]]'
end,
earliest = { -746, 2, 18 }
},
{
key = 'ethiopian',
label = '[[Ethiopian calendar|Ethiopian]]',
render = function(frame, date)
return renderTemplate(frame, date, 'ETHIOPIANDATE', { format = 'DMY' }) .. ' Amätä Məhrät'
end,
earliest = { 8, 8, 27 }
},
{
key = 'french',
label = '[[French Republican calendar|French Republican]]',
render = function(frame, date)
return renderTemplate(frame, date, 'FRENCHREVOLUTIONARYDATE')
end,
earliest = { 1792, 9, 22 }
},
{
key = 'gregorian',
label = '[[Gregorian calendar|Gregorian]]',
render = function(frame, date)
local day = date.day
local month = callTime(frame, 'F', date.timestamp)
local year = date.year
return string.format('%s, %s', wikilink(day .. ' ' .. month), yearLink(year, true, false))
end
},
{
key = 'hebrew',
label = '[[Hebrew calendar|Hebrew]]',
render = function(frame, date)
local day = callTime(frame, 'xjj', date.timestamp)
local month = callTime(frame, 'xjF', date.timestamp)
local monthNumber = tonumber(callTime(frame, 'xjn', date.timestamp))
local year = callTime(frame, 'xjY', date.timestamp)
local monthTarget = month
if monthNumber == 10 then
monthTarget = 'Tammuz (Hebrew month)'
elseif monthNumber == 11 then
monthTarget = 'Av (month)'
end
local value = string.format('%s %s, %s %s', day, wikilink(monthTarget, month), wikilink('Anno Mundi', 'AM'), year)
local omer = trim(expand(frame, 'Sefirat HaOmer', { month, day }))
if omer and mw.ustring.lower(omer) ~= 'none' then
value = value .. '<br />' .. wikilink('Sefirat HaOmer', 'Omer') .. ' ' .. omer
end
return value
end,
earliest = { 0, 1, 1 } -- This is the earliest date that can be passed to MediaWiki's time function
},
{
key = 'hindu',
label = '[[Hindu calendar|Hindu]]',
render = function(frame, date)
return renderTemplate(frame, date, 'HINDUCALENDAR')
end,
earliest = { 79, 3, 13 }
},
{
key = 'icelandic',
label = '[[Icelandic calendar|Icelandic]]',
render = function(frame, date)
return string.format('%s, %s', renderTemplate(frame, date, 'ICELANDICDATE/weekday'), renderTemplate(frame, date, 'ICELANDICDATE'))
end,
earliest = { 1, 4, 19 }
},
{
key = 'islamic',
label = '[[Islamic calendar|Islamic]]',
render = function(frame, date)
local day = callTime(frame, 'xmj', date.timestamp)
local month = callTime(frame, 'xmF', date.timestamp)
local year = callTime(frame, 'xmY', date.timestamp)
return string.format('%s %s, %s %s<br />(using %s)', day, wikilink(month), wikilink('Hijri era', 'AH'), year, wikilink('Tabular Islamic calendar', 'tabular method'))
end,
earliest = { 622, 7, 16 }
},
{
key = 'iso',
label = '[[ISO week date]]',
render = function(frame, date)
return callTime(frame, 'o-\\WW-N', date.timestamp)
end,
earliest = { 0, 1, 1 } -- This is the earliest date that can be passed to MediaWiki's time function
},
{
key = 'japanese',
label = '[[Japanese calendar|Japanese]]',
render = function(frame, date)
return renderTemplate(frame, date, 'JAPANESECALENDAR', { format = 'DMY', show_sekki = 'true' })
end,
earliest = { 645, 1, 1 }
},
{
key = 'julian',
label = '[[Julian calendar|Julian]]',
render = function(frame, date)
local target = renderTemplate(frame, date, 'JULIANCALENDAR', { format = 'MD' })
local display = renderTemplate(frame, date, 'JULIANCALENDAR', { format = 'DM' })
local year = renderTemplate(frame, date, 'JULIANYEAR')
local annoMundi = renderTemplate(frame, date, 'ANNOMUNDI')
return string.format('[[%s (Eastern Orthodox liturgics)|%s]], %s (%s %s)', target, display, yearLink(year, true, false, true), wikilink('Anno Mundi', 'AM'), annoMundi)
end,
earliest = { -9999, 1, 1 }
},
{
key = 'julianday',
label = '[[Julian day]]',
render = function(frame, date)
return renderTemplate(frame, date, 'JD')
end,
earliest = { -4712, 1, 1 }
},
{
key = 'korean',
label = '[[Korean calendar|Korean]]',
render = function(frame, date)
return renderTemplate(frame, date, 'KOREANCALENDAR', { format = 'DMY' })
end,
earliest = { -2332, 2, 15 }
},
{
key = 'maya',
label = '[[Maya calendar|Maya]]',
render = function(frame, date)
return renderTemplate(frame, date, 'MAYANCALENDAR')
end,
earliest = { -9999, 1, 1 }
},
{
key = 'roman',
label = '[[Roman calendar|Roman]]',
render = function(frame, date)
return renderTemplate(frame, date, 'ROMANDATE', { format = 'DMY' }) .. ' [[Ab urbe condita|AUC]]'
end,
earliest = { -9999, 1, 1 }
},
{
key = 'solar',
label = '[[Solar Hijri calendar|Solar Hijri]]',
render = function(frame, date)
local day = callTime(frame, 'xij', date.timestamp)
local month = callTime(frame, 'xiF', date.timestamp)
local year = callTime(frame, 'xiY', date.timestamp)
local monthTarget = trim(expand(frame, 'IranianMonth', { month })) or month
return string.format('%s %s, SH %s', day, wikilink(monthTarget, month), year)
end,
earliest = { -9999, 1, 1 }
},
{
key = 'tibetan',
label = '[[Tibetan calendar|Tibetan]]',
render = function(frame, date)
return renderTemplate(frame, date, 'TIBETANDATE')
end,
earliest = { -9999, 1, 1 }
}
}
local function normaliseCalendar(value)
value = trim(value)
if not value then
return nil
end
value = mw.ustring.lower(value)
value = mw.ustring.gsub(value, '[_%-]+', ' ')
value = mw.ustring.gsub(value, '%s+', ' ')
return value
end
local function detectDefaultCalendar()
local title = mw.ustring.lower(mw.title.getCurrentTitle().text)
local bestMatch
local bestMatchLength = 0
for _, calendar in ipairs(calendars) do
local phrase = calendar.key
if mw.ustring.find(title, phrase, 1, true) then
local phraseLength = mw.ustring.len(phrase)
if phraseLength > bestMatchLength then
bestMatch = calendar.key
bestMatchLength = phraseLength
end
end
end
return bestMatch
end
local function dateIsBefore(date, limit)
if not limit then
return false
end
if date.year ~= limit[1] then
return date.year < limit[1]
end
if date.month ~= limit[2] then
return date.month < limit[2]
end
return date.day < limit[3]
end
local function dateIsAfter(date, limit)
if not limit then
return false
end
if date.year ~= limit[1] then
return date.year > limit[1]
end
if date.month ~= limit[2] then
return date.month > limit[2]
end
return date.day > limit[3]
end
local function calendarIsAvailable(calendar, date)
return not dateIsBefore(date, calendar.earliest) and not dateIsAfter(date, calendar.latest)
end
local function makeOtherCalendars(rows, expanded)
if #rows == 0 then
return nil
end
local outer = mw.html.create('div')
:css('width', '100%')
:css('text-align', 'left')
:css('border-top', '1px solid var(--border-color-base, #a2a9b1)')
:css('padding-top', '0.5em')
:css('margin-top', '0.5em')
local content = outer
if not expanded then
outer:addClass('mw-collapsible mw-collapsed')
outer:tag('div'):css('text-align', 'center'):css('font-weight', 'bold'):wikitext('Other calendars')
content = outer:tag('div'):addClass('mw-collapsible-content')
end
local tableNode = content:tag('table')
:css('width', '100%')
:css('background', 'transparent')
:css('color', 'inherit')
:css('margin-top', '0.5em')
for _, row in ipairs(rows) do
local tr = tableNode:tag('tr')
local th = tr:tag('th')
:attr('scope', 'row')
:css('text-align', 'left')
:css('font-weight', 'normal')
th:wikitext("'''" .. row.label .. "'''")
tr:tag('td')
:wikitext(row.value)
end
return tostring(outer) .. '<hr />'
end
local function defaultAbove(frame)
local note = expand(frame, 'Nobold', { '(at [[UTC+00:00|UTC+00]])' })
return 'Today<br />' .. expand(frame, 'Small', { note })
end
function p.main(frame)
local args = getArgs(frame)
local yearText = trim(args[1])
local monthText = trim(args[2])
local dayText = trim(args[3])
local year = tonumber(yearText)
local month = tonumber(monthText)
local day = tonumber(dayText)
if not year or not month or not day then
return expand(frame, 'Error', { 'Invalid or missing year, month or day' })
end
local date = { year = year, month = month, day = day, timestamp = yearText .. '-' .. monthText .. '-' .. dayText }
local defaultCalendar = normaliseCalendar(args.default_calendar)
if not defaultCalendar then
defaultCalendar = detectDefaultCalendar()
end
local defaultCalendar2 = normaliseCalendar(args.default_calendar2)
local selected = {}
if defaultCalendar then
selected[defaultCalendar] = true
end
if defaultCalendar2 then
selected[defaultCalendar2] = true
end
local rowsByKey = {}
local otherRows = {}
for _, calendar in ipairs(calendars) do
if calendarIsAvailable(calendar, date) then
local value = trim(calendar.render(frame, date))
if value then
local row = { label = calendar.label, value = value }
rowsByKey[calendar.key] = row
if not selected[calendar.key] then
otherRows[#otherRows + 1] = row
end
end
end
end
local builder = InfoboxBuilder.new()
:title(trim(args.name))
:above(trim(args.above) or defaultAbove(frame))
if trim(args.image) then
local imageFrame = frame:newChild { args = { image = args.image, upright = '1.1' } }
builder:image { image = InfoboxImage.InfoboxImage(imageFrame), caption = args.caption }
end
builder:subheader(renderMainDate(frame, date))
local addedPrimary = {}
local function addPrimary(key)
if not key or addedPrimary[key] then
return
end
addedPrimary[key] = true
local row = rowsByKey[key]
if row then
builder:row { label = row.label, data = row.value }
end
end
addPrimary(defaultCalendar)
addPrimary(defaultCalendar2)
local other = makeOtherCalendars(otherRows, yes(args.all))
if other then
builder:row { data = other }
end
builder:below('[' .. expand(frame, 'Purge', { 'refresh' }) .. ']')
return builder:build()
end
return p