Module:IriniaCalendar: Difference between revisions
From DSRPG
No edit summary |
No edit summary |
||
| Line 3: | Line 3: | ||
local p = {} | local p = {} | ||
-- Normalize arguments for any #invoke call | |||
local function getArgs(frame) | |||
-- Prefer named args on this frame | |||
if frame.args and next(frame.args) then | |||
return frame.args | |||
end | |||
-- Otherwise fall back to parent frame args | |||
if frame.getParent then | |||
local parent = frame:getParent() | |||
if parent and parent.args then | |||
return parent.args | |||
end | |||
end | |||
return {} | |||
end | |||
-- Configurable variables for the calendar system | -- Configurable variables for the calendar system | ||
local calendar = { | local calendar = { | ||
-- Current date in the world | -- Current date in the world | ||
current = { | current = { day = 4, month = 5, year = 1236 }, | ||
-- Month names in order | -- Month names in order | ||
months = { | months = { | ||
[1] = "Frost Moon", | [1] = "Frost Moon", [2] = "Shadow Moon", [3] = "Storm Moon", | ||
[4] = "Mist Moon", [5] = "Bloom Moon", [6] = "Sun Moon", | |||
[7] = "Thunder Moon", [8] = "Harvest Moon", [9] = "Ember Moon", | |||
[4] = "Mist Moon", | [10] = "Frost Moon", [11] = "Twilight Moon", [12] = "Star Moon" | ||
[7] = "Thunder Moon", | |||
[10] = "Frost Moon", | |||
}, | }, | ||
-- Days in each month | -- Days in each month | ||
days_in_month = { | days_in_month = { | ||
[1] = 30, | [1] = 30, [2] = 28, [3] = 30, [4] = 29, | ||
[5] = 31, [6] = 31, [7] = 30, [8] = 31, | |||
[9] = 30, [10] = 31, [11] = 28, [12] = 30 | |||
[5] = 31, | |||
[9] = 30, | |||
}, | }, | ||
-- | -- Weekday names | ||
weekdays = { | weekdays = { | ||
[1] = "Silverday", | [1] = "Silverday", [2] = "Brassday", [3] = "Woodday", | ||
[4] = "Stoneday", [5] = "Glassday", [6] = "Steamday", | |||
[4] = "Stoneday", | |||
[7] = "Gearsday" | [7] = "Gearsday" | ||
}, | }, | ||
-- Seasons | -- Seasons by month | ||
seasons = { | seasons = { | ||
[1] = "Winter", | [1] = "Winter", [2] = "Winter", [3] = "Spring", | ||
[4] = "Spring", [5] = "Spring", [6] = "Summer", | |||
[7] = "Summer", [8] = "Summer", [9] = "Autumn", | |||
[4] = "Spring", | [10] = "Autumn", [11] = "Autumn", [12] = "Winter" | ||
[7] = "Summer", | |||
[10] = "Autumn", | |||
}, | }, | ||
era = "AE", | era = "AE", | ||
-- | -- Total days per year | ||
days_in_year = 359 | days_in_year = 359 | ||
} | } | ||
-- Update the current date | -- Update the current in-world date | ||
function p.updateCurrentDate(frame) | function p.updateCurrentDate(frame) | ||
local args = frame | local args = getArgs(frame) | ||
calendar.current.day = tonumber(args.day) or calendar.current.day | |||
calendar.current.day = tonumber(args.day) or calendar.current.day | |||
calendar.current.month = tonumber(args.month) or calendar.current.month | calendar.current.month = tonumber(args.month) or calendar.current.month | ||
calendar.current.year = tonumber(args.year) or calendar.current.year | calendar.current.year = tonumber(args.year) or calendar.current.year | ||
return "Current date updated to " .. p.formatDate(calendar.current) | return "Current date updated to " .. p.formatDate(calendar.current) | ||
end | end | ||
-- Get the current date | -- Get the formatted current date | ||
function p.getCurrentDate(frame) | function p.getCurrentDate(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local format | local fmt = args.format or "full" | ||
return p.formatDate(calendar.current, fmt) | |||
return p.formatDate(calendar.current, | |||
end | end | ||
-- Convert | -- Convert numeric month to name | ||
function p.getMonthName(frame) | function p.getMonthName(frame) | ||
local args = frame. | local args = getArgs(frame) | ||
local m = tonumber(args.month) or 1 | |||
-- | local cnt = #calendar.months | ||
if | if m < 1 or m > cnt then m = ((m-1)%cnt)+1 end | ||
args = | return calendar.months[m] | ||
end | |||
-- Format a date (either a table or via #invoke frame) | |||
function p.formatDate(dateOrFrame, format) | |||
local date, fmt | |||
if dateOrFrame and dateOrFrame.args then | |||
-- invoked via #invoke, grab named args | |||
local args = getArgs(dateOrFrame) | |||
date = { | |||
day = tonumber(args.day) or calendar.current.day, | |||
month = tonumber(args.month) or calendar.current.month, | |||
year = tonumber(args.year) or calendar.current.year | |||
} | |||
fmt = args.format or "full" | |||
else | |||
-- called programmatically | |||
date = dateOrFrame or {} | |||
fmt = format or "full" | |||
end | end | ||
local day = date.day or 1 | |||
local day = date.day or 1 | |||
local month = date.month or 1 | local month = date.month or 1 | ||
local year = date.year or 0 | local year = date.year or 0 | ||
if month < 1 or month > #calendar.months then | if month < 1 or month > #calendar.months then | ||
month = ((month - 1) % #calendar.months) + 1 | month = ((month-1)%#calendar.months)+1 | ||
end | end | ||
local monthName = calendar.months[month] | local monthName = calendar.months[month] | ||
if fmt == "full" then | |||
if | |||
return day .. " " .. monthName .. ", " .. year .. " " .. calendar.era | return day .. " " .. monthName .. ", " .. year .. " " .. calendar.era | ||
elseif | elseif fmt == "short" then | ||
return day .. " " .. monthName .. ", " .. year | return day .. " " .. monthName .. ", " .. year | ||
elseif | elseif fmt == "month-year" then | ||
return monthName .. ", " .. year .. " " .. calendar.era | return monthName .. ", " .. year .. " " .. calendar.era | ||
elseif | elseif fmt == "day-month" then | ||
return day .. " " .. monthName | return day .. " " .. monthName | ||
elseif | elseif fmt == "numeric" then | ||
return day .. "/" .. month .. "/" .. year .. " " .. calendar.era | return day .. "/" .. month .. "/" .. year .. " " .. calendar.era | ||
else | else | ||
| Line 166: | Line 127: | ||
end | end | ||
-- Parse a date string into a | -- Parse a date string back into a table | ||
function p.parseDate(dateStr) | function p.parseDate(dateStr) | ||
local day, | local day, monName, year = string.match(dateStr, "(%d+)%s+(%a+%s*%a*),%s*(%d+)") | ||
if not day or not monName or not year then return nil end | |||
if not day or not | local month | ||
for i,name in ipairs(calendar.months) do | |||
if string.lower(name) == string.lower(monName) then month = i break end | |||
end | end | ||
if not month then return nil end | |||
return { day = tonumber(day), month = month, year = tonumber(year) } | |||
if not month then | |||
return { | |||
end | end | ||
-- | -- Total days since year 0 | ||
function p.totalDays( | function p.totalDays(d) | ||
if not | if not d or not d.day or not d.month or not d.year then return 0 end | ||
local total = d.year * calendar.days_in_year | |||
for i=1, d.month-1 do total = total + calendar.days_in_month[i] end | |||
total = total + d.day | |||
local total = | |||
for i = 1, | |||
total = total + | |||
return total | return total | ||
end | end | ||
-- | -- Compare two dates | ||
function p. | function p.compareDate(a,b) | ||
-- | if a.year ~= b.year then return a.year < b.year and -1 or 1 end | ||
if a.month ~= b.month then return a.month < b.month and -1 or 1 end | |||
if a.day ~= b.day then return a.day < b.day and -1 or 1 end | |||
return 0 | |||
end | |||
-- Compute difference between two dates | |||
function p.dateDifference(d1, d2) | |||
local negative = false | local negative = false | ||
if p.compareDate( | if p.compareDate(d1,d2) > 0 then d1,d2 = d2,d1; negative = true end | ||
local years = d2.year - d1.year | |||
local years = | |||
local months = 0 | local months = 0 | ||
local days = 0 | local days = 0 | ||
if d2.month >= d1.month then | |||
months = d2.month - d1.month | |||
else | |||
if | |||
months = | |||
years = years - 1 | years = years - 1 | ||
months = 12 - | months = 12 - d1.month + d2.month | ||
end | end | ||
if d2.day >= d1.day then | |||
if | days = d2.day - d1.day | ||
days = | |||
else | else | ||
if months > 0 then | if months > 0 then | ||
months = months - 1 | months = months - 1 | ||
else | else | ||
years = years - 1 | |||
months = 11 | |||
end | end | ||
local prevMon = d2.month - 1 | |||
if prevMon < 1 then prevMon = 12 end | |||
days = calendar.days_in_month[prevMon] - d1.day + d2.day | |||
local | |||
if | |||
days = calendar.days_in_month[ | |||
end | end | ||
local parts = {} | |||
local | if years ~= 0 then table.insert(parts, years .. " year" .. (years~=1 and "s" or "")) end | ||
if years ~= 0 then | if months~= 0 then table.insert(parts, months .. " month" .. (months~=1 and "s" or "")) end | ||
if days ~= 0 or #parts == 0 then table.insert(parts, days .. " day" .. (days~=1 and "s" or "")) end | |||
local result = table.concat(parts, ", ") | |||
if #parts > 1 then | |||
result = string.gsub(result, ", ([^,]*)$", " and %1") | |||
end | end | ||
if | if negative then result = result .. " ago" end | ||
return result | |||
return | |||
end | end | ||
-- | -- Time since a given date | ||
function p.timeSince(frame) | function p.timeSince(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 } | |||
return p.dateDifference(d, calendar.current) | |||
local day = tonumber(args.day) or 1 | |||
return p.dateDifference( | |||
end | end | ||
-- | -- Time until a given date | ||
function p.timeUntil(frame) | function p.timeUntil(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 } | |||
return p.dateDifference(calendar.current, d) | |||
local day = tonumber(args.day) or 1 | |||
return p.dateDifference(calendar.current, | |||
end | end | ||
-- | -- Timeline entry formatter | ||
function p.timelineEntry(frame) | function p.timelineEntry(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 } | |||
local fmt = args.format or "full" | |||
local dateStr= p.formatDate(d, fmt) | |||
local ago = p.dateDifference(d, calendar.current) | |||
local text = args.text or "" | |||
return string.format("<div class='timeline-entry'><span class='timeline-date'>%s</span> <span class='timeline-ago'>(%s ago)</span>: <span class='timeline-text'>%s</span></div>", dateStr, ago, text) | |||
local day = tonumber(args.day) or 1 | |||
local | |||
local dateStr = p.formatDate( | |||
return "<div class='timeline-entry'><span class='timeline-date'> | |||
end | end | ||
-- | -- Single-month calendar display | ||
function p.calendarDisplay(frame) | function p.calendarDisplay(frame) | ||
local args = frame.args | local args = getArgs(frame) | ||
local m = tonumber(args.month) or calendar.current.month | |||
-- | local y = tonumber(args.year) or calendar.current.year | ||
if m<1 or m>#calendar.months then m = ((m-1)%#calendar.months)+1 end | |||
local daysInM = calendar.days_in_month[m] | |||
local monName = calendar.months[m] | |||
local out = { string.format("<table class='calendar-table'><caption>%s %d %s</caption>", monName, y, calendar.era), "<tr class='calendar-header'>" } | |||
for _,wd in ipairs(calendar.weekdays) do table.insert(out, "<th>"..wd.."</th>") end | |||
table.insert(out, "</tr>") | |||
local firstWeekday = (p.totalDays({ day=1, month=m, year=y }) - 1) % #calendar.weekdays + 1 | |||
table.insert(out, "<tr>") | |||
for i=1, firstWeekday-1 do table.insert(out, "<td class='calendar-empty'></td>") end | |||
local cw = firstWeekday | |||
for d=1, daysInM do | |||
local cls = (d==calendar.current.day and m==calendar.current.month and y==calendar.current.year) | |||
and 'calendar-current-day' or 'calendar-day' | |||
table.insert(out, string.format("<td class='%s'>%d</td>", cls, d)) | |||
cw = cw + 1 | |||
if cw > #calendar.weekdays and d < daysInM then table.insert(out, "</tr><tr>"); cw = 1 end | |||
end | end | ||
for i=cw,#calendar.weekdays do table.insert(out, "<td class='calendar-empty'></td>") end | |||
table.insert(out, "</tr></table>") | |||
return table.concat(out) | |||
for | |||
return | |||
end | end | ||
-- | -- Full-year calendar display | ||
function p.yearCalendar(frame) | function p.yearCalendar(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local y = tonumber(args.year) or calendar.current.year | |||
local out = { string.format("<div class='year-calendar'><h2>Calendar for Year %d %s</h2>", y, calendar.era) } | |||
for m=1,#calendar.months do | |||
local subArgs = { args={ month=tostring(m), year=tostring(y) } } | |||
table.insert(out, "<div class='month-calendar'>" .. p.calendarDisplay(subArgs) .. "</div>") | |||
local | |||
local | |||
for | |||
end | end | ||
table.insert(out, "</div>") | |||
return table.concat(out) | |||
return | |||
end | end | ||
-- | -- Season lookup | ||
function p.getSeason(frame) | function p.getSeason(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local m = tonumber(args.month) or calendar.current.month | |||
if m<1 or m>#calendar.months then m = ((m-1)%#calendar.months)+1 end | |||
return calendar.seasons[m] | |||
local | |||
if | |||
return calendar.seasons[ | |||
end | end | ||
-- Calculate | -- Calculate age in years | ||
function p.calculateAge(frame) | function p.calculateAge(frame) | ||
local args | local args = getArgs(frame) | ||
local bd = tonumber(args.day) or 1 | |||
local bm = tonumber(args.month) or 1 | |||
local by = tonumber(args.year) or 0 | |||
local | local ageStr = p.dateDifference({ day=bd, month=bm, year=by }, calendar.current) | ||
local | local y = string.match(ageStr, "^(%d+) years?") | ||
local | return y or "0" | ||
local | |||
local | |||
return | |||
end | end | ||
-- Event time ago with optional prefix/suffix | |||
-- | |||
function p.eventTimeAgo(frame) | function p.eventTimeAgo(frame) | ||
local args = frame | local args = getArgs(frame) | ||
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 } | |||
local | |||
local prefix = args.prefix or "" | local prefix = args.prefix or "" | ||
local suffix = args.suffix or " ago" | local suffix = args.suffix or " ago" | ||
return prefix .. p.dateDifference(d, calendar.current) .. suffix | |||
end | end | ||
return p | return p | ||
Revision as of 19:54, 4 May 2025
Documentation for this module may be created at Module:IriniaCalendar/doc
-- Module:IriniaCalendar
-- A module for handling the custom calendar of Irinia
local p = {}
-- Normalize arguments for any #invoke call
local function getArgs(frame)
-- Prefer named args on this frame
if frame.args and next(frame.args) then
return frame.args
end
-- Otherwise fall back to parent frame args
if frame.getParent then
local parent = frame:getParent()
if parent and parent.args then
return parent.args
end
end
return {}
end
-- Configurable variables for the calendar system
local calendar = {
-- Current date in the world
current = { day = 4, month = 5, year = 1236 },
-- Month names in order
months = {
[1] = "Frost Moon", [2] = "Shadow Moon", [3] = "Storm Moon",
[4] = "Mist Moon", [5] = "Bloom Moon", [6] = "Sun Moon",
[7] = "Thunder Moon", [8] = "Harvest Moon", [9] = "Ember Moon",
[10] = "Frost Moon", [11] = "Twilight Moon", [12] = "Star Moon"
},
-- Days in each month
days_in_month = {
[1] = 30, [2] = 28, [3] = 30, [4] = 29,
[5] = 31, [6] = 31, [7] = 30, [8] = 31,
[9] = 30, [10] = 31, [11] = 28, [12] = 30
},
-- Weekday names
weekdays = {
[1] = "Silverday", [2] = "Brassday", [3] = "Woodday",
[4] = "Stoneday", [5] = "Glassday", [6] = "Steamday",
[7] = "Gearsday"
},
-- Seasons by month
seasons = {
[1] = "Winter", [2] = "Winter", [3] = "Spring",
[4] = "Spring", [5] = "Spring", [6] = "Summer",
[7] = "Summer", [8] = "Summer", [9] = "Autumn",
[10] = "Autumn", [11] = "Autumn", [12] = "Winter"
},
era = "AE",
-- Total days per year
days_in_year = 359
}
-- Update the current in-world date
function p.updateCurrentDate(frame)
local args = getArgs(frame)
calendar.current.day = tonumber(args.day) or calendar.current.day
calendar.current.month = tonumber(args.month) or calendar.current.month
calendar.current.year = tonumber(args.year) or calendar.current.year
return "Current date updated to " .. p.formatDate(calendar.current)
end
-- Get the formatted current date
function p.getCurrentDate(frame)
local args = getArgs(frame)
local fmt = args.format or "full"
return p.formatDate(calendar.current, fmt)
end
-- Convert numeric month to name
function p.getMonthName(frame)
local args = getArgs(frame)
local m = tonumber(args.month) or 1
local cnt = #calendar.months
if m < 1 or m > cnt then m = ((m-1)%cnt)+1 end
return calendar.months[m]
end
-- Format a date (either a table or via #invoke frame)
function p.formatDate(dateOrFrame, format)
local date, fmt
if dateOrFrame and dateOrFrame.args then
-- invoked via #invoke, grab named args
local args = getArgs(dateOrFrame)
date = {
day = tonumber(args.day) or calendar.current.day,
month = tonumber(args.month) or calendar.current.month,
year = tonumber(args.year) or calendar.current.year
}
fmt = args.format or "full"
else
-- called programmatically
date = dateOrFrame or {}
fmt = format or "full"
end
local day = date.day or 1
local month = date.month or 1
local year = date.year or 0
if month < 1 or month > #calendar.months then
month = ((month-1)%#calendar.months)+1
end
local monthName = calendar.months[month]
if fmt == "full" then
return day .. " " .. monthName .. ", " .. year .. " " .. calendar.era
elseif fmt == "short" then
return day .. " " .. monthName .. ", " .. year
elseif fmt == "month-year" then
return monthName .. ", " .. year .. " " .. calendar.era
elseif fmt == "day-month" then
return day .. " " .. monthName
elseif fmt == "numeric" then
return day .. "/" .. month .. "/" .. year .. " " .. calendar.era
else
return day .. " " .. monthName .. ", " .. year .. " " .. calendar.era
end
end
-- Parse a date string back into a table
function p.parseDate(dateStr)
local day, monName, year = string.match(dateStr, "(%d+)%s+(%a+%s*%a*),%s*(%d+)")
if not day or not monName or not year then return nil end
local month
for i,name in ipairs(calendar.months) do
if string.lower(name) == string.lower(monName) then month = i break end
end
if not month then return nil end
return { day = tonumber(day), month = month, year = tonumber(year) }
end
-- Total days since year 0
function p.totalDays(d)
if not d or not d.day or not d.month or not d.year then return 0 end
local total = d.year * calendar.days_in_year
for i=1, d.month-1 do total = total + calendar.days_in_month[i] end
total = total + d.day
return total
end
-- Compare two dates
function p.compareDate(a,b)
if a.year ~= b.year then return a.year < b.year and -1 or 1 end
if a.month ~= b.month then return a.month < b.month and -1 or 1 end
if a.day ~= b.day then return a.day < b.day and -1 or 1 end
return 0
end
-- Compute difference between two dates
function p.dateDifference(d1, d2)
local negative = false
if p.compareDate(d1,d2) > 0 then d1,d2 = d2,d1; negative = true end
local years = d2.year - d1.year
local months = 0
local days = 0
if d2.month >= d1.month then
months = d2.month - d1.month
else
years = years - 1
months = 12 - d1.month + d2.month
end
if d2.day >= d1.day then
days = d2.day - d1.day
else
if months > 0 then
months = months - 1
else
years = years - 1
months = 11
end
local prevMon = d2.month - 1
if prevMon < 1 then prevMon = 12 end
days = calendar.days_in_month[prevMon] - d1.day + d2.day
end
local parts = {}
if years ~= 0 then table.insert(parts, years .. " year" .. (years~=1 and "s" or "")) end
if months~= 0 then table.insert(parts, months .. " month" .. (months~=1 and "s" or "")) end
if days ~= 0 or #parts == 0 then table.insert(parts, days .. " day" .. (days~=1 and "s" or "")) end
local result = table.concat(parts, ", ")
if #parts > 1 then
result = string.gsub(result, ", ([^,]*)$", " and %1")
end
if negative then result = result .. " ago" end
return result
end
-- Time since a given date
function p.timeSince(frame)
local args = getArgs(frame)
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 }
return p.dateDifference(d, calendar.current)
end
-- Time until a given date
function p.timeUntil(frame)
local args = getArgs(frame)
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 }
return p.dateDifference(calendar.current, d)
end
-- Timeline entry formatter
function p.timelineEntry(frame)
local args = getArgs(frame)
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 }
local fmt = args.format or "full"
local dateStr= p.formatDate(d, fmt)
local ago = p.dateDifference(d, calendar.current)
local text = args.text or ""
return string.format("<div class='timeline-entry'><span class='timeline-date'>%s</span> <span class='timeline-ago'>(%s ago)</span>: <span class='timeline-text'>%s</span></div>", dateStr, ago, text)
end
-- Single-month calendar display
function p.calendarDisplay(frame)
local args = getArgs(frame)
local m = tonumber(args.month) or calendar.current.month
local y = tonumber(args.year) or calendar.current.year
if m<1 or m>#calendar.months then m = ((m-1)%#calendar.months)+1 end
local daysInM = calendar.days_in_month[m]
local monName = calendar.months[m]
local out = { string.format("<table class='calendar-table'><caption>%s %d %s</caption>", monName, y, calendar.era), "<tr class='calendar-header'>" }
for _,wd in ipairs(calendar.weekdays) do table.insert(out, "<th>"..wd.."</th>") end
table.insert(out, "</tr>")
local firstWeekday = (p.totalDays({ day=1, month=m, year=y }) - 1) % #calendar.weekdays + 1
table.insert(out, "<tr>")
for i=1, firstWeekday-1 do table.insert(out, "<td class='calendar-empty'></td>") end
local cw = firstWeekday
for d=1, daysInM do
local cls = (d==calendar.current.day and m==calendar.current.month and y==calendar.current.year)
and 'calendar-current-day' or 'calendar-day'
table.insert(out, string.format("<td class='%s'>%d</td>", cls, d))
cw = cw + 1
if cw > #calendar.weekdays and d < daysInM then table.insert(out, "</tr><tr>"); cw = 1 end
end
for i=cw,#calendar.weekdays do table.insert(out, "<td class='calendar-empty'></td>") end
table.insert(out, "</tr></table>")
return table.concat(out)
end
-- Full-year calendar display
function p.yearCalendar(frame)
local args = getArgs(frame)
local y = tonumber(args.year) or calendar.current.year
local out = { string.format("<div class='year-calendar'><h2>Calendar for Year %d %s</h2>", y, calendar.era) }
for m=1,#calendar.months do
local subArgs = { args={ month=tostring(m), year=tostring(y) } }
table.insert(out, "<div class='month-calendar'>" .. p.calendarDisplay(subArgs) .. "</div>")
end
table.insert(out, "</div>")
return table.concat(out)
end
-- Season lookup
function p.getSeason(frame)
local args = getArgs(frame)
local m = tonumber(args.month) or calendar.current.month
if m<1 or m>#calendar.months then m = ((m-1)%#calendar.months)+1 end
return calendar.seasons[m]
end
-- Calculate age in years
function p.calculateAge(frame)
local args = getArgs(frame)
local bd = tonumber(args.day) or 1
local bm = tonumber(args.month) or 1
local by = tonumber(args.year) or 0
local ageStr = p.dateDifference({ day=bd, month=bm, year=by }, calendar.current)
local y = string.match(ageStr, "^(%d+) years?")
return y or "0"
end
-- Event time ago with optional prefix/suffix
function p.eventTimeAgo(frame)
local args = getArgs(frame)
local d = { day=tonumber(args.day) or 1, month=tonumber(args.month) or 1, year=tonumber(args.year) or 0 }
local prefix = args.prefix or ""
local suffix = args.suffix or " ago"
return prefix .. p.dateDifference(d, calendar.current) .. suffix
end
return p