-- Load dependencies.
local getArgs = require('Module:Arguments').getArgs
local numToRoman = require( 'Module:Roman' ).main
local getOlympiad = require( 'Module:Ancient Olympiads' )._main
local getDynasty = require( 'Module:Ancient Egypt era' )._main
local getPharaoh = require( 'Module:Ancient Egypt kings' )._main
local numToArmenian = require( 'Module:Armenian' ).main
local getRegnal = require( 'Module:British regnal year' ).main
local japaneseEra = require( 'Module:Japanese calendar' ).era  
-- Define constants.
local lang = mw.language.getContentLanguage
local currentYear = tonumber( lang:formatDate( 'Y' ) )  
---
-- Helper functions
---  
local function isInteger( num )
-- Checks if a value is an integer. If so, returns the value converted to a number.
-- If not, returns false.
num = tonumber( num )
if num and math.floor( num ) == num and num ~= math.huge then
return num
else
return false
end
end  
local function BCToNum( s )
-- Converts strings of the format "n BC" to their corresponding
-- numerical values.
if type( s ) ~= 'string' then
return nil
end
s = mw.ustring.match( mw.ustring.upper( s ), '^([1-9]%d*)%s*கிமு$' )
if not s then
return nil
end
local num = tonumber( s )
num = ( num - 1 ) * -1
return num
end  
local function numToBC( num )
-- For BC years, returns a string with the year name appended with " BC".
-- Otherwise returns nil.
num = isInteger( num )
if not num then return end
if num <= 0 then
return string.format( '%d BC', 1 - num )
end
end  
local function ADToNum( s )
-- Converts strings of the format "AD n"
-- to their corresponding numerical values.
if type( s ) ~= 'string' then
return nil
end
s = mw.ustring.match( mw.ustring.upper( s ), '^AD%s*([1-9]%d*)$' )
if not s then
return nil
end
local num = tonumber( s )
return num
end  
local function numToAD( num )
-- For AD years up to 100, returns a string with the year name prepended with "AD ".
-- Otherwise returns nil.
num = isInteger( num )
if not num then return end
if (num <= 100) then
return string.format( 'AD %d', num )
end
end  
local function formatNegative(s)
-- Replaces hyphens in a string with minus signs if the hyphen comes before a number.
s = mw.ustring.gsub( s, '%-(%d)', '−%1' )
return s
end  
---
-- Calendar box class definition
---  
local calendarBox = {}
calendarBox.__index = calendarBox  
function calendarBox:new( init )
init = type( init ) == 'table' and init or {}
local obj = {}
local pagename = mw.title.getCurrentTitle.text  
-- Set the year. If the year is specified as an argument, use that.
-- Otherwise, use the page name if it is valid. If the pagename isn't
-- valid, use the current year.
local yearNum = isInteger( init.year )
local yearBC = BCToNum( init.year )
local yearAD = ADToNum( init.year )
local pageNum = isInteger( pagename )
local pageBC = BCToNum( pagename )
local pageAD = ADToNum( pagename )
if yearNum then -- First, see if the year parameter is a number.
self.year = yearNum
elseif yearBC then -- Second, see if the year parameter is a "yyyy BC" string.
self.year = yearBC
elseif yearAD then -- Third, see if the year parameter is an AD/CE/year string.
self.year = yearAD
elseif pageNum then -- Fourth, see if the pagename is an integer.
self.year = pageNum
elseif pageBC then -- Fifth, see if the pagename is a "yyyy BC" string.
self.year = pageBC
elseif pageAD then -- Sixth, see if the pagename is an AD/CE/year string.
self.year = pageAD
else
self.year = currentYear -- If of the above apply, use the current year.
end  
-- Set year text values.
self.BCYearName = numToBC( self.year )
self.ADYearName = numToAD( self.year )
if self.BCYearName then
self.yearText = self.BCYearName
elseif self.ADYearName then
self.yearText = self.ADYearName
else
self.yearText = tostring( self.year )
end  
-- Set other fields.
self.caption = self.yearText
self.footnotes = init.footnotes  
return setmetatable( obj, {
__index = self
})
end  
function calendarBox:setCaption( s )
-- Sets the calendar box caption.
if type( s ) ~= 'string' or s == '' then return end
self.caption = s
end  
function calendarBox:addCalendar( obj )
-- Adds a calendar or a calendar group.
if type( obj ) ~= 'table' and type( obj.new ) ~= 'function' then return end -- Exit if the object is invalid.
self.calendars = self.calendars or {}
table.insert( self.calendars, obj )
end  
-- Add an alias for adding calendar groups. The function is the same, but it might be confusing for users
-- to have to use the name "addCalendar" for a calendar group.
calendarBox.addCalendarGroup = calendarBox.addCalendar  
function calendarBox:export
-- Outputs the calendar box wikitext.
local root = mw.html.create( 'table' )
-- Export the calendar box headers.
root
:addClass( 'infobox vevent' )
:css( 'width', '22em' )
:tag( 'caption' )
:css( 'font-size', '125%' )
:tag( 'span' )
:addClass( 'summary dtstart' )
:wikitext( self.caption )  
-- Export the calendars and calendar groups. "calendar:export" works for both kinds
-- of objects. Some export functions can return nil, so we need to check for that.
if type( self.calendars ) == 'table' then
for _, calendar in ipairs( self.calendars ) do
local calendarText = calendar:export
if type( calendarText ) == 'string' then
root:wikitext( calendarText )
end
end
end  
-- Add footnotes.
if type( self.footnotes ) == 'string' and self.footnotes ~= '' then
root
:tag( 'tr' )
:tag( 'td' )
:attr( 'colspan', '2' )
:wikitext( string.format( '%s', self.footnotes ) )
end  
return tostring( root )
end  
---
-- Calendar group class definition
---  
-- Calendar groups are used to group different calendars together.
-- Previously, the template did this by including a table row with
-- no year value. By using objects we can do the same thing more
-- semantically.  
local calendarGroup = {}
calendarGroup.__index = calendarGroup  
function calendarGroup:new( init )
init = type( init ) == 'table' and init or {}
local obj = {}  
-- Get the heading and throw an error if it is invalid.
obj.heading = init.heading
if type( obj.heading ) ~= 'string' then
error( 'calendarGroup: no heading detected' )
end  
-- Set the metatable and return the object.
self.__index = self
return setmetatable( obj, {
__index = self
})
end  
function calendarGroup:addCalendar( calendar )
-- Adds a calendar object to the calendar group.
self.calendars = self.calendars or {}
if type( calendar ) == 'table' and type( calendar.getLink ) == 'function' then
table.insert( self.calendars, calendar )
end
end  
function calendarGroup:export
-- Exports the calendar group's wikitext.
-- Indent and italicise each calendar's link if it exists.
for i, calendar in ipairs( self.calendars ) do
local link = calendar:getLink
if type( link ) == 'string' then
self.calendars[ i ]:setRawLink( string.format( "&nbsp;- *%s*", link ) )
end
end
-- Create the heading row html and export the calendar objects.
local ret = mw.html.create
ret
:tag( 'tr' )
:tag( 'td' )
:wikitext( self.heading )
:done
:tag( 'td' ) -- Use a blank tag to make the html look nice.
:allDone
for _, calendar in ipairs( self.calendars ) do
ret:wikitext( calendar:export )
end
return tostring( ret )
end  
---
-- Calendar class definition
---  
local calendar = {}
calendar.__index = calendar
calendar.type = 'calendar'  
function calendar:new
local obj = {}
return setmetatable( obj, {
__index = self
})
end  
function calendar:setLink( link, display )
-- Sets the calendar's wikilink, with optional display text and italics.
if type( link ) ~= 'string' or link == '' then return end
display = type( display ) == 'string' and display ~= '' and display
if display then
self.link = string.format( '%s', link, display )
else
self.link = string.format( '%s', link )
end
end  
function calendar:setRawLink( s )
-- Sets the calendar's wikilink as raw wikitext.
if type( s ) ~= 'string' or s == '' then return end
self.link = s
end  
function calendar:getLink
-- Returns the calendar's link value.
return self.link
end  
function calendar:setYear( year )