Module:Age
- Description
- This module calculates age information from given years, with support for approximate years (using
c.or {{circa}}). - It provides five functions:
age– returns only the age range (e.g. "63–64").ageInYears– returns the age as a single value with "years" (e.g. "64 years" or "approx. 64 years"). Can take one parameter (birth year) or two parameters (birth year and reference year).birthDateAndAge– returns the birth year (orc. year) followed by the age in parentheses.deathDateAndAge– returns the death year (orc. year) followed by the age at death in parentheses.startDateAndAge– returns the start year (orc. year) followed by how many years ago in parentheses.
- Usage
{{#invoke:Age|age|YEAR}}{{#invoke:Age|ageInYears|BIRTH_YEAR}}{{#invoke:Age|ageInYears|BIRTH_YEAR|REFERENCE_YEAR}}{{#invoke:Age|birthDateAndAge|YEAR}}{{#invoke:Age|deathDateAndAge|BIRTH|DEATH}}{{#invoke:Age|startDateAndAge|YEAR}}
- Sample output
Age
{{#invoke:Age|age|4500}} → approx. 64
{{#invoke:Age|age|c. 4500}} → approx. 64
{{#invoke:Age|age|{{circa}} 4500}} → approx. 64
Age in years
{{#invoke:Age|ageInYears|4500}} → approx. 64 years
{{#invoke:Age|ageInYears|c. 4500}} → approx. 64 years
{{#invoke:Age|ageInYears|4500|4560}} → 60 years
{{#invoke:Age|ageInYears|c. 4500|4560}} → approx. 60 years
{{#invoke:Age|ageInYears|4500|c. 4560}} → approx. 60 years
Birth date and age
{{#invoke:Age|birthDateAndAge|4500}} → 4500 AR (age approx. 64)
{{#invoke:Age|birthDateAndAge|c. 4500}} → c. 4500 AR (age approx. 64)
{{#invoke:Age|birthDateAndAge|{{circa}} 4500}} → c. 4500 AR (age approx. 64)
Death date and age
{{#invoke:Age|deathDateAndAge|100|200}} → 200 AR (aged 99–100)
{{#invoke:Age|deathDateAndAge|c. 100|200}} → 200 AR (aged approx. 100)
{{#invoke:Age|deathDateAndAge|100|c. 200}} → c. 200 AR (aged approx. 100)
Start date and age
{{#invoke:Age|startDateAndAge|1000}} → 1000 AR (approx. 3,564 years ago)
{{#invoke:Age|startDateAndAge|c. 1000}} → c. 1000 AR (approx. 3,564 years ago)
{{#invoke:Age|startDateAndAge|{{circa}} 1000}} → c. 1000 AR (approx. 3,564 years ago)
-- Module:Age
local p = {}
-- Safely fetch a frame argument. Returns nil if missing or blank (after trim).
local function argTrim(frame, key)
local v = frame.args[key]
if v == nil then return nil end
v = mw.text.trim(tostring(v))
if v == "" then return nil end
return v
end
-- Helper: strip "c." or {{circa}} markup and return (isApprox, number)
local function parseYear(input)
if not input then return false, nil end
input = mw.text.trim(tostring(input))
if input == "" then return false, nil end
-- Strip HTML tags (e.g. from {{circa}})
local clean = mw.ustring.gsub(input, "<.->", "")
clean = mw.text.trim(clean)
-- Does it start with "c." ?
if mw.ustring.match(clean, "^c%.") then
local numstr = mw.ustring.gsub(clean, "^c%.%s*", "")
local num = tonumber(numstr)
return true, num
end
-- Otherwise try to parse a plain number
return false, tonumber(clean)
end
-- Helper: format numbers with commas based on threshold
-- num: number (or numeric string)
-- threshold: how many digits (not counting a leading '-') before commas are added (default 5)
local function formatNumber(num, threshold)
-- if num is nil, return empty string (keeps previous behavior)
if num == nil then return "" end
-- coerce to number if possible; otherwise return tostring(num)
local n = tonumber(num)
if not n then return tostring(num) end
-- floor to integer values for display (same as before)
n = math.floor(n)
-- prepare string and sign
local sign = ""
if n < 0 then
sign = "-"
n = -n
end
local s = tostring(n)
-- determine digits and threshold
local digits = #s
local thr = tonumber(threshold) or 5
-- if below threshold, return with sign and no commas
if digits < thr then
return sign .. s
end
-- insert commas every three digits from the right
-- reverse trick is simple and robust
local rev = s:reverse()
local with_commas = rev:gsub("(%d%d%d)","%1,"):reverse()
-- remove possible leading comma added by pattern
if with_commas:sub(1,1) == "," then
with_commas = with_commas:sub(2)
end
return sign .. with_commas
end
-- Helper: default Year expansion (trimmed); returns a string or nil
local function defaultYear(frame)
local y = frame:expandTemplate{ title = "Year" }
y = mw.text.trim(tostring(y or ""))
if y == "" then return nil end
return y
end
-- Age-only output (for {{Age}})
function p.age(frame)
local birthInput = argTrim(frame, 1) or argTrim(frame, "birth_year")
local yearInput = argTrim(frame, 2) or argTrim(frame, "year") or defaultYear(frame)
local birthApprox, birthYear = parseYear(birthInput)
local yearApprox, yearNum = parseYear(yearInput)
if not birthYear or not yearNum then
return "?"
end
if birthApprox or yearApprox then
local age = yearNum - birthYear
return frame:expandTemplate{title = "approx"} .. " " .. formatNumber(age, 4)
end
local minAge = yearNum - birthYear - 1
local maxAge = yearNum - birthYear
return formatNumber(minAge, 4) .. "–" .. formatNumber(maxAge, 4)
end
-- Age in years (for {{Age in years}})
function p.ageInYears(frame)
local birthInput = argTrim(frame, 1) or argTrim(frame, "birth_year")
local yearInput = argTrim(frame, 2) or argTrim(frame, "year") or defaultYear(frame)
local birthApprox, birthYear = parseYear(birthInput)
local yearApprox, yearNum = parseYear(yearInput)
if not birthYear or not yearNum then
return "?"
end
local age = yearNum - birthYear
if birthApprox or yearApprox then
return frame:expandTemplate{title = "approx"} .. " " .. formatNumber(age, 4) .. " years"
end
return formatNumber(age, 4) .. " years"
end
-- Birth year + age output (for {{Birth date and age}})
function p.birthDateAndAge(frame)
local birthInput = argTrim(frame, 1) or argTrim(frame, "birth_year")
local yearInput = argTrim(frame, 2) or argTrim(frame, "year") or defaultYear(frame)
local birthApprox, birthYear = parseYear(birthInput)
local yearApprox, yearNum = parseYear(yearInput)
if not birthYear or not yearNum then
return "?"
end
-- Approximate case
if birthApprox or yearApprox then
local age = yearNum - birthYear
local yearText
if birthApprox then
yearText = frame:expandTemplate{title = "circa"} .. " " .. formatNumber(birthYear) .. " [[AR]]"
else
yearText = formatNumber(birthYear) .. " [[AR]]"
end
local ageText = frame:expandTemplate{title = "approx"} .. " " .. formatNumber(age, 4)
return yearText .. " (age " .. ageText .. ")"
end
-- Exact case
local minAge = yearNum - birthYear - 1
local maxAge = yearNum - birthYear
return formatNumber(birthYear) .. " [[AR]] (age " .. formatNumber(minAge, 4) .. "–" .. formatNumber(maxAge, 4) .. ")"
end
-- Death year + age at death output (for {{Death date and age}})
function p.deathDateAndAge(frame)
local birthInput = argTrim(frame, 1) or argTrim(frame, "birth_year")
local deathInput = argTrim(frame, 2) or argTrim(frame, "death_year")
local birthApprox, birthYear = parseYear(birthInput)
local deathApprox, deathYear = parseYear(deathInput)
if not birthYear or not deathYear then
return "?"
end
-- Approximate case if either is approximate
if birthApprox or deathApprox then
local age = deathYear - birthYear
local deathText
if deathApprox then
deathText = frame:expandTemplate{title = "circa"} .. " " .. formatNumber(deathYear) .. " [[AR]]"
else
deathText = formatNumber(deathYear) .. " [[AR]]"
end
local ageText = frame:expandTemplate{title = "approx"} .. " " .. formatNumber(age, 4)
return deathText .. " (aged " .. ageText .. ")"
end
-- Exact case
local minAge = deathYear - birthYear - 1
local maxAge = deathYear - birthYear
return formatNumber(deathYear) .. " [[AR]] (aged " .. formatNumber(minAge, 4) .. "–" .. formatNumber(maxAge, 4) .. ")"
end
-- Start year + years ago output (for {{Start date and age}})
function p.startDateAndAge(frame)
local startInput = argTrim(frame, 1) or argTrim(frame, "start_year")
local yearInput = argTrim(frame, 2) or argTrim(frame, "year") or defaultYear(frame)
local startApprox, startYear = parseYear(startInput)
local yearApprox, yearNum = parseYear(yearInput)
if not startYear or not yearNum then
return "?"
end
-- Approximate case
if startApprox or yearApprox then
local yearsAgo = yearNum - startYear
local yearsText = frame:expandTemplate{title = "approx"} .. " " .. formatNumber(yearsAgo, 4)
local startText
if startApprox then
startText = frame:expandTemplate{title = "circa"} .. " " .. formatNumber(startYear) .. " [[AR]]"
else
startText = formatNumber(startYear) .. " [[AR]]"
end
return startText .. " (" .. yearsText .. " years ago)"
end
-- Exact case
local minYears = yearNum - startYear - 1
local maxYears = yearNum - startYear
return formatNumber(startYear) .. " [[AR]] (" .. formatNumber(minYears, 4) .. "–" .. formatNumber(maxYears, 4) .. " years ago)"
end
return p