Module:FRCalendar

local FRCalendar = {} --- imported libraries -- Parses invocation and template parameters, trims whitespace, and removes blanks. local getArgs = require('Dev:Arguments').getArgs -- Smarter boolean logic local yesno = require( 'Dev:Yesno' ) -- Hash table of 2400 year names. local getYearName = require ('Module:Years')._yearName -- Hash table of five moon phases, from full moon to full moon. local phaseSymbols = {"🌕", "🌗", "🌑", "🌓", "🌕" } -- Hash table of img tags local phaseImg = { ,   ,    ,    ,    '' } -- Hash table of six special days and what month of the calendar should be generated for them. local monthSpecial = { ['Midwinter']        = 'Hammer', ['Greengrass']       = 'Tarsakh', ['Midsummer']        = 'Flamerule', ['Shieldmeet']       = 'Flamerule', ['Highharvestide']   = 'Eleint', ['Feast of the Moon'] = 'Uktar' } -- The months of the year, as numbers. You can place alternate spellings or common aliases here. local monthNumber = { ['Hammer']    = 1, ['Alturiak']  = 2, ['Ches']      = 3, ['Tarsakh']   = 4, ['Mirtul']    = 5, ['Kythorn']   = 6, ['Flamerule'] = 7, ['Eleasis']   = 8, ['Eleasias']  = 8, ['Eleint']    = 9, ['Marpenoth'] = 10, ['Uktar']     = 11, ['Nightal']   = 12 } -- But do not alter this sequence. local months = {'Hammer','Alturiak','Ches','Tarsakh','Mirtul','Kythorn', 'Flamerule','Eleasis','Eleint','Marpenoth','Uktar','Nightal'}

-- This iterates through a comma-separated list of numbers and assigns phase images to them. local function addPhases(phases, phaseList) local _phases = mw.text.split(phaseList, ',', true) for phaseIndex,dayNum in ipairs(_phases) do       phases['D'..dayNum] = phaseImg[phaseIndex] end end

-- This takes a month name and an integer yearsSinceLeap and returns -- 1) a table of phase images corresponding to the month before, the current month, and the month after -- 2) the names of the previous and next months local function buildPhaseList(FRmonth, yearsSinceLeap) local f = mw.getCurrentFrame local currMonth = monthNumber[FRmonth] local currYear = 2004 + yearsSinceLeap -- 2004 is a known leap year local prevMonth, prevYear, nextMonth, nextYear = 0, 0, 0, 0 local currPhaseDate = tostring(currYear)..'-'..currMonth..'-15' -- arbitrarily chosen to be the 15th of the month -- This calls a template that computes the 5 phase days of a lunar cycle, from full moon to full moon local currPhaseList = f:expandTemplate({title = 'Selune phase',                                            args  = {mode='fivePhases', currPhaseDate}}) if currMonth - 1 < 1 then  -- the previous month is in the previous year prevMonth = 12 prevYear = currYear - 1 else prevMonth = currMonth - 1 prevYear = currYear end local prevPhaseDate = tostring(prevYear)..'-'..prevMonth..'-15' local prevPhaseList = f:expandTemplate({title = 'Selune phase',                                            args  = {mode='fivePhases', prevPhaseDate}}) if currMonth + 1 > 12 then  -- the next month is in the next year nextMonth = 1 nextYear = currYear + 1 else nextMonth = currMonth + 1 nextYear = currYear end local nextPhaseDate = tostring(nextYear)..'-'..nextMonth..'-15' local nextPhaseList = f:expandTemplate({title = 'Selune phase',                                            args  = {mode='fivePhases', nextPhaseDate}}) local phases = {} -- Merge the three lists addPhases(phases, prevPhaseList) addPhases(phases, currPhaseList) addPhases(phases, nextPhaseList) return phases, months[prevMonth], months[nextMonth] end

-- This returns an HTML and spanning 10 columns with a link to one of the special holidays -- and possibly a moon-phase symbol. local function addSpecial(rows, day, phase) local symbol = phase and string.format(' '..phase, day) or '' rows:tag('tr') :tag('td'):attr('colspan','10') :wikitext(..day..):wikitext(symbol) :done :done end

-- This handles the special case where we need to insert Highharvestide into the 3-month calendar for Uktar. -- Otherwise it gets left off because it falls on the cusp between two 3-month calendars. local function handleHHT(FRmonth, phaseDay, phases) local rows = mw.html.create('') if FRmonth == 'Uktar' then phaseDay = phaseDay - 31 addSpecial(rows, 'Highharvestide', phases['D'..phaseDay]) end return tostring(rows) end

-- This returns zero to two HTML tags with links to special holidays, as needed. -- It decrements the phaseDay by 1 for each special holiday inserted before the current month. -- It then computes the phaseDay of the beginning of the previous month, wrapping around the -- four-year cycle if necessary. local function getPrevSpecial(FRmonth, phaseDay, phases, leapYear) local rows = mw.html.create('')

if FRmonth == 'Alturiak' then phaseDay = phaseDay - 1 addSpecial(rows, 'Midwinter', phases['D'..phaseDay]) elseif FRmonth == 'Mirtul' then phaseDay = phaseDay - 1 addSpecial(rows, 'Greengrass', phases['D'..phaseDay]) elseif FRmonth == 'Eleasis' then phaseDay = phaseDay - (leapYear and 2 or 1) -- subtract 2 days if it is a leap year addSpecial(rows, 'Midsummer', phases['D'..phaseDay]) if leapYear then addSpecial(rows, 'Shieldmeet', phases['D'..phaseDay+1]) -- add second special day end elseif FRmonth == 'Marpenoth' then phaseDay = phaseDay - 1 addSpecial(rows, 'Highharvestide', phases['D'..phaseDay]) elseif FRmonth == 'Nightal' then phaseDay = phaseDay - 1 addSpecial(rows, 'Feast of the Moon', phases['D'..phaseDay]) end phaseDay = phaseDay - 30 -- jump to the previous month if phaseDay < 0 then phaseDay = phaseDay + 1461 -- jump to the last month of the 4-year cycle end return phaseDay, tostring(rows) end

-- This computes the phaseDay of the beginning of the next month, wrapping around the four-year cycle if necessary. -- It then returns zero to two HTML tags with links to special holidays, as needed. -- It increments the phaseDay by 1 for each special holiday inserted after the current month. local function getNextSpecial(FRmonth, phaseDay, phases, leapYear) local rows = mw.html.create('') phaseDay = phaseDay + 30 -- Jump to the next month if phaseDay >= 1461 then phaseDay = phaseDay - 1461 -- jump to the first month of the 4-year cycle end

if FRmonth == 'Hammer' then addSpecial(rows, 'Midwinter', phases['D'..phaseDay]) phaseDay = phaseDay + 1 elseif FRmonth == 'Tarsakh' then addSpecial(rows, 'Greengrass', phases['D'..phaseDay]) phaseDay = phaseDay + 1 elseif FRmonth == 'Flamerule' then addSpecial(rows, 'Midsummer', phases['D'..phaseDay]) phaseDay = phaseDay + 1 if leapYear then addSpecial(rows, 'Shieldmeet', phases['D'..phaseDay]) phaseDay = phaseDay + 1 end elseif FRmonth == 'Eleint' then addSpecial(rows, 'Highharvestide', phases['D'..phaseDay]) phaseDay = phaseDay + 1 elseif FRmonth == 'Uktar' then addSpecial(rows, 'Feast of the Moon', phases['D'..phaseDay]) phaseDay = phaseDay + 1 end return phaseDay, tostring(rows) end

-- This produces three HTML tags with 10 tags in each row to make a 30-day calendar. -- The days are numbered 1 to 30 but the text is replaced by the phase of the moon symbol on the -- days of a principle phase (new moon, 1st quarter, half, 3rd quarter, and full moon). local function getMonthHtml(FRmonth, phaseDay, phases) local link, altLink = ,  local rows = mw.html.create('') for tenday = 0, 2 do       rows:tag('tr') for day = 1, 10 do           altLink = tostring(day + 10*tenday) link = FRmonth..' '..altLink if phases['D'..phaseDay] then rows:tag('td') :wikitext(string.format(phases['D'..phaseDay], link)) :done else rows:tag('td') :wikitext(..altLink..) :done end phaseDay = phaseDay + 1 end rows:done end return tostring(rows) end

-- This takes an integer year and creates a link to the year page, -- replacing a possible hyphen with a minus sign in the link text. local function formatYearLink( year ) local repl, matches = string.gsub(year, '-', '−') return ..repl..' DR' or )..'' end

-- This is the main function to generate a 3-month calendar with the principal phases marked by images -- and any special holidays inserted between the months. It expects a Forgotten Realms date in the format -- produced by the template (for example, "3 Hammer" or "Midsummer") and a second argument of -- an integer year. It uses this date/year combination to call the template to get the -- number of days since the last leap year. This is necessary because the phases of the moon are on a four-year -- cycle. It then produces a calendar of the current month, the one preceding it, and the one following it. -- It also inserts any special holidays that should be displayed in the 3-month span of the calendar. function FRCalendar.ThreeMonthsArgs( args ) local f = mw.getCurrentFrame local FRdate = args[1] local FRyear = tonumber(args[2]) local FRday, FRmonth = string.match(FRdate, '(%d+)%s+(%a+)') if FRday == nil then FRday  = FRdate == 'Shieldmeet' and 32 or 31 FRmonth = monthSpecial[FRdate] else FRday  = tonumber(FRday) FRmonth = tostring(FRmonth) end local yearsSinceLeap = ((FRyear % 4) + 4) % 4 local leapYear = (yearsSinceLeap == 0) if FRdate == 'Shieldmeet' and not leapYear then return mw.html.create('p'):css('color','red') :wikitext('Shieldmeet only occurs on leap years.'):done end local daysSinceLeap = f:expandTemplate({title = 'Date number', args = {FRdate, FRyear}}) local phaseTable, prevMonth, nextMonth = buildPhaseList(FRmonth, yearsSinceLeap)

local title = getYearName(FRyear) == '' and 'Calendar of Harptos' or getYearName(FRyear)

local currPhaseDay = daysSinceLeap - FRday + 1 local extraSpecial = handleHHT(FRmonth, currPhaseDay, phaseTable) local prevPhaseDay, prevSpecials = getPrevSpecial(FRmonth, currPhaseDay, phaseTable, leapYear) local nextPhaseDay, nextSpecials = getNextSpecial(FRmonth, currPhaseDay, phaseTable, leapYear)

local output = mw.html.create('div'):addClass('RoMcontainer') :tag('div'):addClass('RoMtop'):wikitext(''):done :tag('div'):addClass('RoMcenter') :tag('div'):addClass('RoMmiddle') :tag('table'):addClass('rollOfMonths') :tag('tr') :tag('th'):attr('colspan','10') :wikitext(title) :done :done :tag('tr') :tag('th'):attr('colspan','10') :wikitext(formatYearLink(FRyear)) :done :done :wikitext(extraSpecial) :tag('tr') :tag('th'):attr('colspan','10') :wikitext(prevMonth) :done :done :wikitext(getMonthHtml(prevMonth, prevPhaseDay, phaseTable)) :wikitext(prevSpecials) :tag('tr') :tag('th'):attr('colspan','10') :wikitext(FRmonth) :done :done :wikitext(getMonthHtml(FRmonth, currPhaseDay, phaseTable)) :wikitext(nextSpecials) :tag('tr') :tag('th'):attr('colspan','10') :wikitext(nextMonth) :done :done :wikitext(getMonthHtml(nextMonth, nextPhaseDay, phaseTable)) :done :done :done :tag('div'):addClass('RoMbottom'):wikitext(''):done :allDone return tostring(output) end

-- This is the function that should be #invoked function FRCalendar.ThreeMonths( frame ) local args = getArgs(frame) return FRCalendar.ThreeMonthsArgs( args ) end

return FRCalendar