<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mindpowe.red/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AAge</id>
	<title>Module:Age - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mindpowe.red/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AAge"/>
	<link rel="alternate" type="text/html" href="https://mindpowe.red/wiki/index.php?title=Module:Age&amp;action=history"/>
	<updated>2026-04-06T06:54:53Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.34.2</generator>
	<entry>
		<id>https://mindpowe.red/wiki/index.php?title=Module:Age&amp;diff=1584&amp;oldid=prev</id>
		<title>imported&gt;Johnuniq: update from sandbox: implement show=M (minutes) and show=s (seconds)</title>
		<link rel="alternate" type="text/html" href="https://mindpowe.red/wiki/index.php?title=Module:Age&amp;diff=1584&amp;oldid=prev"/>
		<updated>2020-08-03T02:55:15Z</updated>

		<summary type="html">&lt;p&gt;update from sandbox: implement show=M (minutes) and show=s (seconds)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- Implement various &amp;quot;age of&amp;quot; and other date-related templates.&lt;br /&gt;
&lt;br /&gt;
local mtext = {&lt;br /&gt;
	-- Message and other text that should be localized.&lt;br /&gt;
	['mt-bad-param1'] =             'Invalid parameter $1',&lt;br /&gt;
	['mt-bad-param2'] =             'Parameter $1=$2 is invalid',&lt;br /&gt;
	['mt-bad-show'] =               'Parameter show=$1 is not supported here',&lt;br /&gt;
	['mt-cannot-add'] =             'Cannot add &amp;quot;$1&amp;quot;',&lt;br /&gt;
	['mt-conflicting-show'] =       'Parameter show=$1 conflicts with round=$2',&lt;br /&gt;
	['mt-date-wrong-order'] =       'The second date must be later in time than the first date',&lt;br /&gt;
	['mt-dd-future'] =              'Death date (first date) must not be in the future',&lt;br /&gt;
	['mt-dd-wrong-order'] =         'Death date (first date) must be later in time than the birth date (second date)',&lt;br /&gt;
	['mt-invalid-bd-age'] =         'Invalid birth date for calculating age',&lt;br /&gt;
	['mt-invalid-dates-age'] =      'Invalid dates for calculating age',&lt;br /&gt;
	['mt-invalid-end'] =            'Invalid end date in second parameter',&lt;br /&gt;
	['mt-invalid-start'] =          'Invalid start date in first parameter',&lt;br /&gt;
	['mt-need-jdn'] =               'Need valid Julian date number',&lt;br /&gt;
	['mt-need-valid-bd'] =          'Need valid birth date: year, month, day',&lt;br /&gt;
	['mt-need-valid-bd2'] =         'Need valid birth date (second date): year, month, day',&lt;br /&gt;
	['mt-need-valid-date'] =        'Need valid date',&lt;br /&gt;
	['mt-need-valid-dd'] =          'Need valid death date (first date): year, month, day',&lt;br /&gt;
	['mt-need-valid-ymd'] =         'Need valid year, month, day',&lt;br /&gt;
	['mt-need-valid-ymd-current'] = 'Need valid year|month|day or &amp;quot;currentdate&amp;quot;',&lt;br /&gt;
	['mt-need-valid-ymd2'] =        'Second date should be year, month, day',&lt;br /&gt;
	['mt-template-bad-name'] =      'The specified template name is not valid',&lt;br /&gt;
	['mt-template-x'] =             'The template invoking this must have &amp;quot;|template=x&amp;quot; where x is the wanted operation',&lt;br /&gt;
	['txt-age'] =                   '(age&amp;amp;nbsp;',&lt;br /&gt;
	['txt-aged'] =                  ' (aged&amp;amp;nbsp;',&lt;br /&gt;
	['txt-and'] =                   ' and ',&lt;br /&gt;
	['txt-comma-and'] =             ', and ',&lt;br /&gt;
	['txt-error'] =                 'Error: ',&lt;br /&gt;
	['txt-or'] =                    '&amp;amp;nbsp;or ',&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local translate, from_en, to_en, isZero&lt;br /&gt;
if translate then&lt;br /&gt;
	-- Functions to translate from en to local language and reverse go here.&lt;br /&gt;
	-- See example at [[:bn:Module:বয়স]].&lt;br /&gt;
else&lt;br /&gt;
	from_en = function (text)&lt;br /&gt;
		return text&lt;br /&gt;
	end&lt;br /&gt;
	isZero = function (text)&lt;br /&gt;
		return tonumber(text) == 0&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local _Date, _currentDate&lt;br /&gt;
local function getExports(frame)&lt;br /&gt;
	-- Return objects exported from the date module or its sandbox.&lt;br /&gt;
	if not _Date then&lt;br /&gt;
		local sandbox = frame:getTitle():find('sandbox', 1, true) and '/sandbox' or ''&lt;br /&gt;
		local datemod = require('Module:Date' .. sandbox)&lt;br /&gt;
		local realDate = datemod._Date&lt;br /&gt;
		_currentDate = datemod._current&lt;br /&gt;
		if to_en then&lt;br /&gt;
			_Date = function (...)&lt;br /&gt;
				local args = {}&lt;br /&gt;
				for i, v in ipairs({...}) do&lt;br /&gt;
					args[i] = to_en(v)&lt;br /&gt;
				end&lt;br /&gt;
				return realDate(unpack(args))&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			_Date = realDate&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return _Date, _currentDate&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local Collection  -- a table to hold items&lt;br /&gt;
Collection = {&lt;br /&gt;
	add = function (self, item)&lt;br /&gt;
		if item ~= nil then&lt;br /&gt;
			self.n = self.n + 1&lt;br /&gt;
			self[self.n] = item&lt;br /&gt;
		end&lt;br /&gt;
	end,&lt;br /&gt;
	join = function (self, sep)&lt;br /&gt;
		return table.concat(self, sep)&lt;br /&gt;
	end,&lt;br /&gt;
	remove = function (self, pos)&lt;br /&gt;
		if self.n &amp;gt; 0 and (pos == nil or (0 &amp;lt; pos and pos &amp;lt;= self.n)) then&lt;br /&gt;
			self.n = self.n - 1&lt;br /&gt;
			return table.remove(self, pos)&lt;br /&gt;
		end&lt;br /&gt;
	end,&lt;br /&gt;
	sort = function (self, comp)&lt;br /&gt;
		table.sort(self, comp)&lt;br /&gt;
	end,&lt;br /&gt;
	new = function ()&lt;br /&gt;
		return setmetatable({n = 0}, Collection)&lt;br /&gt;
	end&lt;br /&gt;
}&lt;br /&gt;
Collection.__index = Collection&lt;br /&gt;
&lt;br /&gt;
local function stripToNil(text)&lt;br /&gt;
	-- If text is a string, return its trimmed content, or nil if empty.&lt;br /&gt;
	-- Otherwise return text (which may, for example, be nil).&lt;br /&gt;
	if type(text) == 'string' then&lt;br /&gt;
		text = text:match('(%S.-)%s*$')&lt;br /&gt;
	end&lt;br /&gt;
	return text&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function yes(parameter)&lt;br /&gt;
	-- Return true if parameter should be interpreted as &amp;quot;yes&amp;quot;.&lt;br /&gt;
	-- Do not want to accept mixed upper/lowercase unless done by current templates.&lt;br /&gt;
	-- Need to accept &amp;quot;on&amp;quot; because &amp;quot;round=on&amp;quot; is wanted.&lt;br /&gt;
	return ({ y = true, yes = true, on = true })[parameter]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function message(msg, id)&lt;br /&gt;
	-- Return formatted message text for an error or warning.&lt;br /&gt;
	local function getText(msg)&lt;br /&gt;
		return mtext[msg] or error('Bug: message &amp;quot;' .. tostring(msg) .. '&amp;quot; not defined')&lt;br /&gt;
	end&lt;br /&gt;
	local text&lt;br /&gt;
	if type(msg) == 'table' then&lt;br /&gt;
		text = getText(msg[1])&lt;br /&gt;
		local rep = {}&lt;br /&gt;
		for i, v in ipairs(msg) do&lt;br /&gt;
			if i &amp;gt; 1 then&lt;br /&gt;
				rep['$' .. (i - 1)] = v&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		text = text:gsub('$%d+', rep)&lt;br /&gt;
	else&lt;br /&gt;
		text = getText(msg)&lt;br /&gt;
	end&lt;br /&gt;
	local categories = {&lt;br /&gt;
		error = '[[Category:Age error]]',&lt;br /&gt;
		warning = '[[Category:Age error]]',  -- same as error until determine whether 'Age warning' would be worthwhile&lt;br /&gt;
	}&lt;br /&gt;
	local a, b, category&lt;br /&gt;
	if id == 'warning' then&lt;br /&gt;
		a = '&amp;lt;sup&amp;gt;[&amp;lt;i&amp;gt;'&lt;br /&gt;
		b = '&amp;lt;/i&amp;gt;]&amp;lt;/sup&amp;gt;'&lt;br /&gt;
	else&lt;br /&gt;
		a = '&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;' .. getText('txt-error')&lt;br /&gt;
		b = '&amp;lt;/strong&amp;gt;'&lt;br /&gt;
	end&lt;br /&gt;
	if mw.title.getCurrentTitle():inNamespaces(0) then&lt;br /&gt;
		-- Category only in namespaces: 0=article.&lt;br /&gt;
		category = categories[id or 'error']&lt;br /&gt;
	end&lt;br /&gt;
	return&lt;br /&gt;
		a ..&lt;br /&gt;
		mw.text.nowiki(text) ..&lt;br /&gt;
		b ..&lt;br /&gt;
		(category or '')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function formatNumber(number)&lt;br /&gt;
	-- Return the given number formatted with commas as group separators,&lt;br /&gt;
	-- given that the number is an integer.&lt;br /&gt;
	local numstr = tostring(number)&lt;br /&gt;
	local length = #numstr&lt;br /&gt;
	local places = Collection.new()&lt;br /&gt;
	local pos = 0&lt;br /&gt;
	repeat&lt;br /&gt;
		places:add(pos)&lt;br /&gt;
		pos = pos + 3&lt;br /&gt;
	until pos &amp;gt;= length&lt;br /&gt;
	places:add(length)&lt;br /&gt;
	local groups = Collection.new()&lt;br /&gt;
	for i = places.n, 2, -1 do&lt;br /&gt;
		local p1 = length - places[i] + 1&lt;br /&gt;
		local p2 = length - places[i - 1]&lt;br /&gt;
		groups:add(numstr:sub(p1, p2))&lt;br /&gt;
	end&lt;br /&gt;
	return groups:join(',')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function spellNumber(number, options, i)&lt;br /&gt;
	-- Return result of spelling number, or&lt;br /&gt;
	-- return number (as a string) if cannot spell it.&lt;br /&gt;
	-- i == 1 for the first number which can optionally start with an uppercase letter.&lt;br /&gt;
	number = tostring(number)&lt;br /&gt;
	return require('Module:ConvertNumeric').spell_number(&lt;br /&gt;
		number,&lt;br /&gt;
		nil,                       -- fraction numerator&lt;br /&gt;
		nil,                       -- fraction denominator&lt;br /&gt;
		i == 1 and options.upper,  -- true: 'One' instead of 'one'&lt;br /&gt;
		not options.us,            -- true: use 'and' between tens/ones etc&lt;br /&gt;
		options.adj,               -- true: hyphenated&lt;br /&gt;
		options.ordinal            -- true: 'first' instead of 'one'&lt;br /&gt;
	) or number&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function makeExtra(args, flagCurrent)&lt;br /&gt;
	-- Return extra text that will be inserted before the visible result&lt;br /&gt;
	-- but after any sort key.&lt;br /&gt;
	local extra = args.prefix or ''&lt;br /&gt;
	if mw.ustring.len(extra) &amp;gt; 1 then&lt;br /&gt;
		-- Parameter &amp;quot;~&amp;quot; gives &amp;quot;~3&amp;quot; whereas &amp;quot;over&amp;quot; gives &amp;quot;over 3&amp;quot;.&lt;br /&gt;
		if extra:sub(-6, -1) ~= '&amp;amp;nbsp;' then&lt;br /&gt;
			extra = extra .. ' '&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if flagCurrent then&lt;br /&gt;
		extra = '&amp;lt;span class=&amp;quot;currentage&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;' .. extra&lt;br /&gt;
	end&lt;br /&gt;
	return extra&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function makeSort(value, sortable)&lt;br /&gt;
	-- Return a sort key if requested.&lt;br /&gt;
	-- Assume value is a valid number which has not overflowed.&lt;br /&gt;
	if sortable == 'sortable_table' or sortable == 'sortable_on' or sortable == 'sortable_debug' then&lt;br /&gt;
		local sortKey&lt;br /&gt;
		if value == 0 then&lt;br /&gt;
			sortKey = '5000000000000000000'&lt;br /&gt;
		else&lt;br /&gt;
			local mag = math.floor(math.log10(math.abs(value)) + 1e-14)&lt;br /&gt;
			if value &amp;gt; 0 then&lt;br /&gt;
				sortKey = 7000 + mag&lt;br /&gt;
			else&lt;br /&gt;
				sortKey = 2999 - mag&lt;br /&gt;
				value = value + 10^(mag+1)&lt;br /&gt;
			end&lt;br /&gt;
			sortKey = string.format('%d', sortKey) .. string.format('%015.0f', math.floor(value * 10^(14-mag)))&lt;br /&gt;
		end&lt;br /&gt;
		local result&lt;br /&gt;
		if sortable == 'sortable_table' then&lt;br /&gt;
			result = 'data-sort-value=&amp;quot;_SORTKEY_&amp;quot;|'&lt;br /&gt;
		elseif sortable == 'sortable_debug' then&lt;br /&gt;
			result = '&amp;lt;span data-sort-value=&amp;quot;_SORTKEY_♠&amp;quot;&amp;gt;&amp;lt;span style=&amp;quot;border:1px solid&amp;quot;&amp;gt;_SORTKEY_♠&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;'&lt;br /&gt;
		else&lt;br /&gt;
			result = '&amp;lt;span data-sort-value=&amp;quot;_SORTKEY_♠&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;'&lt;br /&gt;
		end&lt;br /&gt;
		return result:gsub('_SORTKEY_', sortKey)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local translateParameters = {&lt;br /&gt;
	abbr = {&lt;br /&gt;
		off = 'abbr_off',&lt;br /&gt;
		on = 'abbr_on',&lt;br /&gt;
	},&lt;br /&gt;
	disp = {&lt;br /&gt;
		age = 'disp_age',&lt;br /&gt;
		raw = 'disp_raw',&lt;br /&gt;
	},&lt;br /&gt;
	format = {&lt;br /&gt;
		raw = 'format_raw',&lt;br /&gt;
		commas = 'format_commas',&lt;br /&gt;
	},&lt;br /&gt;
	round = {&lt;br /&gt;
		on = 'on',&lt;br /&gt;
		yes = 'on',&lt;br /&gt;
		months = 'ym',&lt;br /&gt;
		weeks = 'ymw',&lt;br /&gt;
		days = 'ymd',&lt;br /&gt;
		hours = 'ymdh',&lt;br /&gt;
	},&lt;br /&gt;
	sep = {&lt;br /&gt;
		comma = 'sep_comma',&lt;br /&gt;
		[','] = 'sep_comma',&lt;br /&gt;
		serialcomma = 'sep_serialcomma',&lt;br /&gt;
		space = 'sep_space',&lt;br /&gt;
	},&lt;br /&gt;
	show = {&lt;br /&gt;
		hide = { id = 'hide' },&lt;br /&gt;
		y = { 'y', id = 'y' },&lt;br /&gt;
		ym = { 'y', 'm', id = 'ym' },&lt;br /&gt;
		ymd = { 'y', 'm', 'd', id = 'ymd' },&lt;br /&gt;
		ymw = { 'y', 'm', 'w', id = 'ymw' },&lt;br /&gt;
		ymwd = { 'y', 'm', 'w', 'd', id = 'ymwd' },&lt;br /&gt;
		yd = { 'y', 'd', id = 'yd', keepZero = true },&lt;br /&gt;
		m = { 'm', id = 'm' },&lt;br /&gt;
		md = { 'm', 'd', id = 'md' },&lt;br /&gt;
		w = { 'w', id = 'w' },&lt;br /&gt;
		wd = { 'w', 'd', id = 'wd' },&lt;br /&gt;
		h = { 'H', id = 'h' },&lt;br /&gt;
		hm = { 'H', 'M', id = 'hm' },&lt;br /&gt;
		hms = { 'H', 'M', 'S', id = 'hms' },&lt;br /&gt;
		M = { 'M', id = 'M' },&lt;br /&gt;
		s = { 'S', id = 's' },&lt;br /&gt;
		d = { 'd', id = 'd' },&lt;br /&gt;
		dh = { 'd', 'H', id = 'dh' },&lt;br /&gt;
		dhm = { 'd', 'H', 'M', id = 'dhm' },&lt;br /&gt;
		dhms = { 'd', 'H', 'M', 'S', id = 'dhms' },&lt;br /&gt;
		ymdh = { 'y', 'm', 'd', 'H', id = 'ymdh' },&lt;br /&gt;
		ymdhm = { 'y', 'm', 'd', 'H', 'M', id = 'ymdhm' },&lt;br /&gt;
		ymwdh = { 'y', 'm', 'w', 'd', 'H', id = 'ymwdh' },&lt;br /&gt;
		ymwdhm = { 'y', 'm', 'w', 'd', 'H', 'M', id = 'ymwdhm' },&lt;br /&gt;
	},&lt;br /&gt;
	sortable = {&lt;br /&gt;
		off = false,&lt;br /&gt;
		on = 'sortable_on',&lt;br /&gt;
		table = 'sortable_table',&lt;br /&gt;
		debug = 'sortable_debug',&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local spellOptions = {&lt;br /&gt;
	cardinal = {},&lt;br /&gt;
	Cardinal = { upper = true },&lt;br /&gt;
	cardinal_us = { us = true },&lt;br /&gt;
	Cardinal_us = { us = true, upper = true },&lt;br /&gt;
	ordinal = { ordinal = true },&lt;br /&gt;
	Ordinal = { ordinal = true, upper = true },&lt;br /&gt;
	ordinal_us = { ordinal = true, us = true },&lt;br /&gt;
	Ordinal_us = { ordinal = true, us = true, upper = true },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function dateExtract(frame)&lt;br /&gt;
	-- Return part of a date after performing an optional operation.&lt;br /&gt;
	local Date = getExports(frame)&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local parms = {}&lt;br /&gt;
	for i, v in ipairs(args) do&lt;br /&gt;
		parms[i] = v&lt;br /&gt;
	end&lt;br /&gt;
	if yes(args.fix) then&lt;br /&gt;
		table.insert(parms, 'fix')&lt;br /&gt;
	end&lt;br /&gt;
	if yes(args.partial) then&lt;br /&gt;
		table.insert(parms, 'partial')&lt;br /&gt;
	end&lt;br /&gt;
	local show = stripToNil(args.show) or 'dmy'&lt;br /&gt;
	local date = Date(unpack(parms))&lt;br /&gt;
	if not date then&lt;br /&gt;
		if show == 'format' then&lt;br /&gt;
			return 'error'&lt;br /&gt;
		end&lt;br /&gt;
		return message('mt-need-valid-date')&lt;br /&gt;
	end&lt;br /&gt;
	local add = stripToNil(args.add)&lt;br /&gt;
	if add then&lt;br /&gt;
		for item in add:gmatch('%S+') do&lt;br /&gt;
			date = date + item&lt;br /&gt;
			if not date then&lt;br /&gt;
				return message({ 'mt-cannot-add', item })&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local sortKey, result&lt;br /&gt;
	local sortable = translateParameters.sortable[args.sortable]&lt;br /&gt;
	if sortable then&lt;br /&gt;
		local value = (date.partial and date.partial.first or date).jdz&lt;br /&gt;
		sortKey = makeSort(value, sortable)&lt;br /&gt;
	end&lt;br /&gt;
	if show ~= 'hide' then&lt;br /&gt;
		result = date[show]&lt;br /&gt;
		if result == nil then&lt;br /&gt;
			result = from_en(date:text(show))&lt;br /&gt;
		elseif type(result) == 'boolean' then&lt;br /&gt;
			result = result and '1' or '0'&lt;br /&gt;
		else&lt;br /&gt;
			result = from_en(tostring(result))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return (sortKey or '') .. makeExtra(args) .. (result or '')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function rangeJoin(range)&lt;br /&gt;
	-- Return text to be used between a range of ages.&lt;br /&gt;
	return range == 'dash' and '–' or mtext['txt-or']&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function makeText(values, components, names, options, noUpper)&lt;br /&gt;
	-- Return wikitext representing an age or duration.&lt;br /&gt;
	local text = Collection.new()&lt;br /&gt;
	local count = #values&lt;br /&gt;
	local sep = names.sep or ''&lt;br /&gt;
	for i, v in ipairs(values) do&lt;br /&gt;
		-- v is a number (say 4 for 4 years), or a table ({4,5} for 4 or 5 years).&lt;br /&gt;
		local islist = type(v) == 'table'&lt;br /&gt;
		if (islist or v &amp;gt; 0) or (text.n == 0 and i == count) or (text.n &amp;gt; 0 and components.keepZero) then&lt;br /&gt;
			local fmt, vstr&lt;br /&gt;
			if options.spell then&lt;br /&gt;
				fmt = function(number)&lt;br /&gt;
					return spellNumber(number, options.spell, noUpper or i)&lt;br /&gt;
				end&lt;br /&gt;
			elseif i == 1 and options.format == 'format_commas' then&lt;br /&gt;
				-- Numbers after the first should be small and not need formatting.&lt;br /&gt;
				fmt = formatNumber&lt;br /&gt;
			else&lt;br /&gt;
				fmt = tostring&lt;br /&gt;
			end&lt;br /&gt;
			if islist then&lt;br /&gt;
				vstr = fmt(v[1]) .. rangeJoin(options.range)&lt;br /&gt;
				noUpper = true&lt;br /&gt;
				vstr = vstr .. fmt(v[2])&lt;br /&gt;
			else&lt;br /&gt;
				vstr = fmt(v)&lt;br /&gt;
			end&lt;br /&gt;
			local name = names[components[i]]&lt;br /&gt;
			if name then&lt;br /&gt;
				local plural = names.plural&lt;br /&gt;
				if not plural or (islist and v[2] or v) == 1 then&lt;br /&gt;
					plural = ''&lt;br /&gt;
				end&lt;br /&gt;
				text:add(vstr .. sep .. name .. plural)&lt;br /&gt;
			else&lt;br /&gt;
				text:add(vstr)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local first, last&lt;br /&gt;
	if options.join == 'sep_space' then&lt;br /&gt;
		first = ' '&lt;br /&gt;
		last = ' '&lt;br /&gt;
	elseif options.join == 'sep_comma' then&lt;br /&gt;
		first = ', '&lt;br /&gt;
		last = ', '&lt;br /&gt;
	elseif options.join == 'sep_serialcomma' and text.n &amp;gt; 2 then&lt;br /&gt;
		first = ', '&lt;br /&gt;
		last = mtext['txt-comma-and']&lt;br /&gt;
	else&lt;br /&gt;
		first = ', '&lt;br /&gt;
		last = mtext['txt-and']&lt;br /&gt;
	end&lt;br /&gt;
	for i, v in ipairs(text) do&lt;br /&gt;
		if i &amp;lt; text.n then&lt;br /&gt;
			text[i] = v .. (i + 1 &amp;lt; text.n and first or last)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local sign = ''&lt;br /&gt;
	if options.isnegative then&lt;br /&gt;
		-- Do not display negative zero.&lt;br /&gt;
		if text.n &amp;gt; 1 or (text.n == 1 and text[1]:sub(1, 1) ~= '0' ) then&lt;br /&gt;
			if options.format == 'format_raw' then&lt;br /&gt;
				sign = '-'  -- plain hyphen so result can be used in a calculation&lt;br /&gt;
			else&lt;br /&gt;
				sign = '−'  -- Unicode U+2212 MINUS SIGN&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return&lt;br /&gt;
		(options.sortKey or '') ..&lt;br /&gt;
		(options.extra or '') ..&lt;br /&gt;
		sign ..&lt;br /&gt;
		text:join() ..&lt;br /&gt;
		(options.suffix or '')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function dateDifference(parms)&lt;br /&gt;
	-- Return a formatted date difference using the given parameters&lt;br /&gt;
	-- which have been validated.&lt;br /&gt;
	local names = {&lt;br /&gt;
		abbr_off = {&lt;br /&gt;
			plural = 's',&lt;br /&gt;
			sep = '&amp;amp;nbsp;',&lt;br /&gt;
			y = 'year',&lt;br /&gt;
			m = 'month',&lt;br /&gt;
			w = 'week',&lt;br /&gt;
			d = 'day',&lt;br /&gt;
			H = 'hour',&lt;br /&gt;
			M = 'minute',&lt;br /&gt;
			S = 'second',&lt;br /&gt;
		},&lt;br /&gt;
		abbr_on = {&lt;br /&gt;
			y = 'y',&lt;br /&gt;
			m = 'm',&lt;br /&gt;
			w = 'w',&lt;br /&gt;
			d = 'd',&lt;br /&gt;
			H = 'h',&lt;br /&gt;
			M = 'm',&lt;br /&gt;
			S = 's',&lt;br /&gt;
		},&lt;br /&gt;
		abbr_infant = {      -- for {{age for infant}}&lt;br /&gt;
			plural = 's',&lt;br /&gt;
			sep = '&amp;amp;nbsp;',&lt;br /&gt;
			y = 'yr',&lt;br /&gt;
			m = 'mo',&lt;br /&gt;
			w = 'wk',&lt;br /&gt;
			d = 'day',&lt;br /&gt;
			H = 'hr',&lt;br /&gt;
			M = 'min',&lt;br /&gt;
			S = 'sec',&lt;br /&gt;
		},&lt;br /&gt;
		abbr_raw = {},&lt;br /&gt;
	}&lt;br /&gt;
	local diff = parms.diff  -- must be a valid date difference&lt;br /&gt;
	local show = parms.show  -- may be nil; default is set below&lt;br /&gt;
	local abbr = parms.abbr or 'abbr_off'&lt;br /&gt;
	local defaultJoin&lt;br /&gt;
	if abbr ~= 'abbr_off' then&lt;br /&gt;
		defaultJoin = 'sep_space'&lt;br /&gt;
	end&lt;br /&gt;
	if not show then&lt;br /&gt;
		show = 'ymd'&lt;br /&gt;
		if parms.disp == 'disp_age' then&lt;br /&gt;
			if diff.years &amp;lt; 3 then&lt;br /&gt;
				defaultJoin = 'sep_space'&lt;br /&gt;
				if diff.years &amp;gt;= 1 then&lt;br /&gt;
					show = 'ym'&lt;br /&gt;
				else&lt;br /&gt;
					show = 'md'&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				show = 'y'&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if type(show) ~= 'table' then&lt;br /&gt;
		show = translateParameters.show[show]&lt;br /&gt;
	end&lt;br /&gt;
	if parms.disp == 'disp_raw' then&lt;br /&gt;
		defaultJoin = 'sep_space'&lt;br /&gt;
		abbr = 'abbr_raw'&lt;br /&gt;
	elseif parms.wantSc then&lt;br /&gt;
		defaultJoin = 'sep_serialcomma'&lt;br /&gt;
	end&lt;br /&gt;
	local diffOptions = {&lt;br /&gt;
		round = parms.round,&lt;br /&gt;
		duration = parms.wantDuration,&lt;br /&gt;
		range = parms.range and true or nil,&lt;br /&gt;
	}&lt;br /&gt;
	local sortKey&lt;br /&gt;
	if parms.sortable then&lt;br /&gt;
		local value = diff.age_days + (parms.wantDuration and 1 or 0)  -- days and fraction of a day&lt;br /&gt;
		if diff.isnegative then&lt;br /&gt;
			value = -value&lt;br /&gt;
		end&lt;br /&gt;
		sortKey = makeSort(value, parms.sortable)&lt;br /&gt;
	end&lt;br /&gt;
	local textOptions = {&lt;br /&gt;
		extra = parms.extra,&lt;br /&gt;
		format = parms.format,&lt;br /&gt;
		join = parms.sep or defaultJoin,&lt;br /&gt;
		isnegative = diff.isnegative,&lt;br /&gt;
		range = parms.range,&lt;br /&gt;
		sortKey = sortKey,&lt;br /&gt;
		spell = parms.spell,&lt;br /&gt;
		suffix = parms.suffix,  -- not currently used&lt;br /&gt;
	}&lt;br /&gt;
	if show.id == 'hide' then&lt;br /&gt;
		return sortKey or ''&lt;br /&gt;
	end&lt;br /&gt;
	local values = { diff:age(show.id, diffOptions) }&lt;br /&gt;
	if values[1] then&lt;br /&gt;
		return makeText(values, show, names[abbr], textOptions)&lt;br /&gt;
	end&lt;br /&gt;
	if diff.partial then&lt;br /&gt;
		-- Handle a more complex range such as&lt;br /&gt;
		-- {{age_yd|20 Dec 2001|2003|range=yes}} → 1 year, 12 days or 2 years, 11 days&lt;br /&gt;
		local opt = {&lt;br /&gt;
			format = textOptions.format,&lt;br /&gt;
			join = textOptions.join,&lt;br /&gt;
			isnegative = textOptions.isnegative,&lt;br /&gt;
			spell = textOptions.spell,&lt;br /&gt;
		}&lt;br /&gt;
		return&lt;br /&gt;
			(textOptions.sortKey or '') ..&lt;br /&gt;
			makeText({ diff.partial.mindiff:age(show.id, diffOptions) }, show, names[abbr], opt) ..&lt;br /&gt;
			rangeJoin(textOptions.range) ..&lt;br /&gt;
			makeText({ diff.partial.maxdiff:age(show.id, diffOptions) }, show, names[abbr], opt, true) ..&lt;br /&gt;
			(textOptions.suffix or '')&lt;br /&gt;
	end&lt;br /&gt;
	return message({ 'mt-bad-show', show.id })&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function getDates(frame, getopt)&lt;br /&gt;
	-- Parse template parameters and return one of:&lt;br /&gt;
	-- * date         (a date table, if single)&lt;br /&gt;
	-- * date1, date2 (two date tables, if not single)&lt;br /&gt;
	-- * text         (a string error message)&lt;br /&gt;
	-- A missing date is optionally replaced with the current date.&lt;br /&gt;
	-- If wantMixture is true, a missing date component is replaced&lt;br /&gt;
	-- from the current date, so can get a bizarre mixture of&lt;br /&gt;
	-- specified/current y/m/d as has been done by some &amp;quot;age&amp;quot; templates.&lt;br /&gt;
	-- Some results may be placed in table getopt.&lt;br /&gt;
	local Date, currentDate = getExports(frame)&lt;br /&gt;
	getopt = getopt or {}&lt;br /&gt;
	local function flagCurrent(text)&lt;br /&gt;
		-- This allows the calling template to detect if the current date has been used,&lt;br /&gt;
		-- that is, whether both dates have been entered in a template expecting two.&lt;br /&gt;
		-- For example, an infobox may want the age when an event occurred, not the current age.&lt;br /&gt;
		-- Don't bother detecting if wantMixture is used because not needed and it is a poor option.&lt;br /&gt;
		if not text then&lt;br /&gt;
			if getopt.noMissing then&lt;br /&gt;
				return nil  -- this gives a nil date which gives an error&lt;br /&gt;
			end&lt;br /&gt;
			text = 'currentdate'&lt;br /&gt;
			if getopt.flag == 'usesCurrent' then&lt;br /&gt;
				getopt.usesCurrent = true&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return text&lt;br /&gt;
	end&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local fields = {}&lt;br /&gt;
	local isNamed = args.year or args.year1 or args.year2 or&lt;br /&gt;
		args.month or args.month1 or args.month2 or&lt;br /&gt;
		args.day or args.day1 or args.day2&lt;br /&gt;
	if isNamed then&lt;br /&gt;
		fields[1] = args.year1 or args.year&lt;br /&gt;
		fields[2] = args.month1 or args.month&lt;br /&gt;
		fields[3] = args.day1 or args.day&lt;br /&gt;
		fields[4] = args.year2&lt;br /&gt;
		fields[5] = args.month2&lt;br /&gt;
		fields[6] = args.day2&lt;br /&gt;
	else&lt;br /&gt;
		for i = 1, 6 do&lt;br /&gt;
			fields[i] = args[i]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local imax = 0&lt;br /&gt;
	for i = 1, 6 do&lt;br /&gt;
		fields[i] = stripToNil(fields[i])&lt;br /&gt;
		if fields[i] then&lt;br /&gt;
			imax = i&lt;br /&gt;
		end&lt;br /&gt;
		if getopt.omitZero and i % 3 ~= 1 then  -- omit zero months and days as unknown values but keep year 0 which is 1 BCE&lt;br /&gt;
			if isZero(fields[i]) then&lt;br /&gt;
				fields[i] = nil&lt;br /&gt;
				getopt.partial = true&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local fix = getopt.fix and 'fix' or ''&lt;br /&gt;
	local partialText = getopt.partial and 'partial' or ''&lt;br /&gt;
	local dates = {}&lt;br /&gt;
	if isNamed or imax &amp;gt;= 3 then&lt;br /&gt;
		local nrDates = getopt.single and 1 or 2&lt;br /&gt;
		if getopt.wantMixture then&lt;br /&gt;
			-- Cannot be partial since empty fields are set from current.&lt;br /&gt;
			local components = { 'year', 'month', 'day' }&lt;br /&gt;
			for i = 1, nrDates * 3 do&lt;br /&gt;
				fields[i] = fields[i] or currentDate[components[i &amp;gt; 3 and i - 3 or i]]&lt;br /&gt;
			end&lt;br /&gt;
			for i = 1, nrDates do&lt;br /&gt;
				local index = i == 1 and 1 or 4&lt;br /&gt;
				local y, m, d = fields[index], fields[index+1], fields[index+2]&lt;br /&gt;
				if (m == 2 or m == '2') and (d == 29 or d == '29') then&lt;br /&gt;
					-- Workaround error with following which attempt to use invalid date 2001-02-29.&lt;br /&gt;
					-- {{age_ymwd|year1=2001|year2=2004|month2=2|day2=29}}&lt;br /&gt;
					-- {{age_ymwd|year1=2001|month1=2|year2=2004|month2=1|day2=29}}&lt;br /&gt;
					-- TODO Get rid of wantMixture because even this ugly code does not handle&lt;br /&gt;
					-- 'Feb' or 'February' or 'feb' or 'february'.&lt;br /&gt;
					if not ((y % 4 == 0 and y % 100 ~= 0) or y % 400 == 0) then&lt;br /&gt;
						d = 28&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
				dates[i] = Date(y, m, d)&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			-- If partial dates are allowed, accept&lt;br /&gt;
			--     year only, or&lt;br /&gt;
			--     year and month only&lt;br /&gt;
			-- Do not accept year and day without a month because that makes no sense&lt;br /&gt;
			-- (and because, for example, Date('partial', 2001, nil, 12) sets day = nil, not 12).&lt;br /&gt;
			for i = 1, nrDates do&lt;br /&gt;
				local index = i == 1 and 1 or 4&lt;br /&gt;
				local y, m, d = fields[index], fields[index+1], fields[index+2]&lt;br /&gt;
				if (getopt.partial and y and (m or not d)) or (y and m and d) then&lt;br /&gt;
					dates[i] = Date(fix, partialText, y, m, d)&lt;br /&gt;
				elseif not y and not m and not d then&lt;br /&gt;
					dates[i] = Date(flagCurrent())&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		getopt.textdates = true  -- have parsed each date from a single text field&lt;br /&gt;
		dates[1] = Date(fix, partialText, flagCurrent(fields[1]))&lt;br /&gt;
		if not getopt.single then&lt;br /&gt;
			dates[2] = Date(fix, partialText, flagCurrent(fields[2]))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if not dates[1] then&lt;br /&gt;
		return message(getopt.missing1 or 'mt-need-valid-ymd')&lt;br /&gt;
	end&lt;br /&gt;
	if getopt.single then&lt;br /&gt;
		return dates[1]&lt;br /&gt;
	end&lt;br /&gt;
	if not dates[2] then&lt;br /&gt;
		return message(getopt.missing2 or 'mt-need-valid-ymd2')&lt;br /&gt;
	end&lt;br /&gt;
	return dates[1], dates[2]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ageGeneric(frame)&lt;br /&gt;
	-- Return the result required by the specified template.&lt;br /&gt;
	-- Can use sortable=x where x = on/table/off/debug in any supported template.&lt;br /&gt;
	-- Some templates default to sortable=on but can be overridden.&lt;br /&gt;
	local name = frame.args.template&lt;br /&gt;
	if not name then&lt;br /&gt;
		return message('mt-template-x')&lt;br /&gt;
	end&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local specs = {&lt;br /&gt;
		age_days = {                -- {{age in days}}&lt;br /&gt;
			show = 'd',&lt;br /&gt;
			disp = 'disp_raw',&lt;br /&gt;
		},&lt;br /&gt;
		age_days_nts = {            -- {{age in days nts}}&lt;br /&gt;
			show = 'd',&lt;br /&gt;
			disp = 'disp_raw',&lt;br /&gt;
			format = 'format_commas',&lt;br /&gt;
			sortable = 'on',&lt;br /&gt;
		},&lt;br /&gt;
		duration_days = {           -- {{duration in days}}&lt;br /&gt;
			show = 'd',&lt;br /&gt;
			disp = 'disp_raw',&lt;br /&gt;
			duration = true,&lt;br /&gt;
		},&lt;br /&gt;
		duration_days_nts = {       -- {{duration in days nts}}&lt;br /&gt;
			show = 'd',&lt;br /&gt;
			disp = 'disp_raw',&lt;br /&gt;
			format = 'format_commas',&lt;br /&gt;
			sortable = 'on',&lt;br /&gt;
			duration = true,&lt;br /&gt;
		},&lt;br /&gt;
		age_full_years = {          -- {{age}}&lt;br /&gt;
			show = 'y',&lt;br /&gt;
			abbr = 'abbr_raw',&lt;br /&gt;
			flag = 'usesCurrent',&lt;br /&gt;
			omitZero = true,&lt;br /&gt;
			range = 'no',&lt;br /&gt;
		},&lt;br /&gt;
		age_full_years_nts = {      -- {{age nts}}&lt;br /&gt;
			show = 'y',&lt;br /&gt;
			abbr = 'abbr_raw',&lt;br /&gt;
			format = 'format_commas',&lt;br /&gt;
			sortable = 'on',&lt;br /&gt;
		},&lt;br /&gt;
		age_in_years = {            -- {{age in years}}&lt;br /&gt;
			show = 'y',&lt;br /&gt;
			abbr = 'abbr_raw',&lt;br /&gt;
			negative = 'error',&lt;br /&gt;
			range = 'dash',&lt;br /&gt;
		},&lt;br /&gt;
		age_in_years_nts = {        -- {{age in years nts}}&lt;br /&gt;
			show = 'y',&lt;br /&gt;
			abbr = 'abbr_raw',&lt;br /&gt;
			negative = 'error',&lt;br /&gt;
			range = 'dash',&lt;br /&gt;
			format = 'format_commas',&lt;br /&gt;
			sortable = 'on',&lt;br /&gt;
		},&lt;br /&gt;
		age_infant = {              -- {{age for infant}}&lt;br /&gt;
			-- Do not set show because special processing is done later.&lt;br /&gt;
			abbr = yes(args.abbr) and 'abbr_infant' or 'abbr_off',&lt;br /&gt;
			disp = 'disp_age',&lt;br /&gt;
			sep = 'sep_space',&lt;br /&gt;
			sortable = 'on',&lt;br /&gt;
		},&lt;br /&gt;
		age_m = {                   -- {{age in months}}&lt;br /&gt;
			show = 'm',&lt;br /&gt;
			disp = 'disp_raw',&lt;br /&gt;
		},&lt;br /&gt;
		age_w = {                   -- {{age in weeks}}&lt;br /&gt;
			show = 'w',&lt;br /&gt;
			disp = 'disp_raw',&lt;br /&gt;
		},&lt;br /&gt;
		age_wd = {                  -- {{age in weeks and days}}&lt;br /&gt;
			show = 'wd',&lt;br /&gt;
		},&lt;br /&gt;
		age_yd = {                  -- {{age in years and days}}&lt;br /&gt;
			show = 'yd',&lt;br /&gt;
			format = 'format_commas',&lt;br /&gt;
			sep = args.sep ~= 'and' and 'sep_comma' or nil,&lt;br /&gt;
		},&lt;br /&gt;
		age_yd_nts = {              -- {{age in years and days nts}}&lt;br /&gt;
			show = 'yd',&lt;br /&gt;
			format = 'format_commas',&lt;br /&gt;
			sep = args.sep ~= 'and' and 'sep_comma' or nil,&lt;br /&gt;
			sortable = 'on',&lt;br /&gt;
		},&lt;br /&gt;
		age_ym = {                  -- {{age in years and months}}&lt;br /&gt;
			show = 'ym',&lt;br /&gt;
			sep = 'sep_comma',&lt;br /&gt;
		},&lt;br /&gt;
		age_ymd = {                 -- {{age in years, months and days}}&lt;br /&gt;
			show = 'ymd',&lt;br /&gt;
			range = true,&lt;br /&gt;
		},&lt;br /&gt;
		age_ymwd = {                -- {{age in years, months, weeks and days}}&lt;br /&gt;
			show = 'ymwd',&lt;br /&gt;
			wantMixture = true,&lt;br /&gt;
		},&lt;br /&gt;
	}&lt;br /&gt;
	local spec = specs[name]&lt;br /&gt;
	if not spec then&lt;br /&gt;
		return message('mt-template-bad-name')&lt;br /&gt;
	end&lt;br /&gt;
	if name == 'age_days' then&lt;br /&gt;
		local su = stripToNil(args['show unit'])&lt;br /&gt;
		if su then&lt;br /&gt;
			if su == 'abbr' or su == 'full' then&lt;br /&gt;
				spec.disp = nil&lt;br /&gt;
				spec.abbr = su == 'abbr' and 'abbr_on' or nil&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local partial, autofill&lt;br /&gt;
	local range = stripToNil(args.range) or spec.range&lt;br /&gt;
	if range then&lt;br /&gt;
		-- Suppose partial dates are used and age could be 11 or 12 years.&lt;br /&gt;
		-- &amp;quot;|range=&amp;quot; (empty value) has no effect (spec is used).&lt;br /&gt;
		-- &amp;quot;|range=yes&amp;quot; or spec.range == true sets range = true (gives &amp;quot;11 or 12&amp;quot;)&lt;br /&gt;
		-- &amp;quot;|range=dash&amp;quot; or spec.range == 'dash' sets range = 'dash' (gives &amp;quot;11–12&amp;quot;).&lt;br /&gt;
		-- &amp;quot;|range=no&amp;quot; or spec.range == 'no' sets range = nil and fills each date in the diff (gives &amp;quot;12&amp;quot;).&lt;br /&gt;
		--     (&amp;quot;on&amp;quot; is equivalent to &amp;quot;yes&amp;quot;, and &amp;quot;off&amp;quot; is equivalent to &amp;quot;no&amp;quot;).&lt;br /&gt;
		-- &amp;quot;|range=OTHER&amp;quot; sets range = nil and rejects partial dates.&lt;br /&gt;
		range = ({ dash = 'dash', off = 'no', no = 'no', [true] = true })[range] or yes(range)&lt;br /&gt;
		if range then&lt;br /&gt;
			partial = true  -- accept partial dates with a possible age range for the result&lt;br /&gt;
			if range == 'no' then&lt;br /&gt;
				autofill = true  -- missing month/day in first or second date are filled from other date or 1&lt;br /&gt;
				range = nil&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local getopt = {&lt;br /&gt;
		fix = yes(args.fix),&lt;br /&gt;
		flag = stripToNil(args.flag) or spec.flag,&lt;br /&gt;
		omitZero = spec.omitZero,&lt;br /&gt;
		partial = partial,&lt;br /&gt;
		wantMixture = spec.wantMixture,&lt;br /&gt;
	}&lt;br /&gt;
	local date1, date2 = getDates(frame, getopt)&lt;br /&gt;
	if type(date1) == 'string' then&lt;br /&gt;
		return date1&lt;br /&gt;
	end&lt;br /&gt;
	local format = stripToNil(args.format)&lt;br /&gt;
	local spell = spellOptions[format]&lt;br /&gt;
	if format then&lt;br /&gt;
		format = 'format_' .. format&lt;br /&gt;
	elseif name == 'age_days' and getopt.textdates then&lt;br /&gt;
		format = 'format_commas'&lt;br /&gt;
	end&lt;br /&gt;
	local parms = {&lt;br /&gt;
		diff = date2:subtract(date1, { fill = autofill }),&lt;br /&gt;
		wantDuration = spec.duration or yes(args.duration),&lt;br /&gt;
		range = range,&lt;br /&gt;
		wantSc = yes(args.sc),&lt;br /&gt;
		show = args.show == 'hide' and 'hide' or spec.show,&lt;br /&gt;
		abbr = spec.abbr,&lt;br /&gt;
		disp = spec.disp,&lt;br /&gt;
		extra = makeExtra(args, getopt.usesCurrent and format ~= 'format_raw'),&lt;br /&gt;
		format = format or spec.format,&lt;br /&gt;
		round = yes(args.round),&lt;br /&gt;
		sep = spec.sep,&lt;br /&gt;
		sortable = translateParameters.sortable[args.sortable or spec.sortable],&lt;br /&gt;
		spell = spell,&lt;br /&gt;
	}&lt;br /&gt;
	if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then&lt;br /&gt;
		return message('mt-date-wrong-order')&lt;br /&gt;
	end&lt;br /&gt;
	return from_en(dateDifference(parms))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function bda(frame)&lt;br /&gt;
	-- Implement [[Template:Birth date and age]].&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local options = {&lt;br /&gt;
		missing1 = 'mt-need-valid-bd',&lt;br /&gt;
		noMissing = true,&lt;br /&gt;
		single = true,&lt;br /&gt;
	}&lt;br /&gt;
	local date = getDates(frame, options)&lt;br /&gt;
	if type(date) == 'string' then&lt;br /&gt;
		return date  -- error text&lt;br /&gt;
	end&lt;br /&gt;
	local Date = getExports(frame)&lt;br /&gt;
	local diff = Date('currentdate') - date&lt;br /&gt;
	if diff.isnegative or diff.years &amp;gt; 150 then&lt;br /&gt;
		return message('mt-invalid-bd-age')&lt;br /&gt;
	end&lt;br /&gt;
	local disp, show = 'disp_raw', 'y'&lt;br /&gt;
	if diff.years &amp;lt; 2 then&lt;br /&gt;
		disp = 'disp_age'&lt;br /&gt;
		if diff.years == 0 and diff.months == 0 then&lt;br /&gt;
			show = 'd'&lt;br /&gt;
		else&lt;br /&gt;
			show = 'm'&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)&lt;br /&gt;
	local result = '(&amp;lt;span class=&amp;quot;bday&amp;quot;&amp;gt;%-Y-%m-%d&amp;lt;/span&amp;gt;) &amp;lt;/span&amp;gt;' ..&lt;br /&gt;
					(df and '%-d %B %-Y' or '%B %-d, %-Y')&lt;br /&gt;
	result = from_en('&amp;lt;span style=&amp;quot;display:none&amp;quot;&amp;gt; ' ..&lt;br /&gt;
		date:text(result) ..&lt;br /&gt;
		'&amp;lt;span class=&amp;quot;noprint ForceAgeToShow&amp;quot;&amp;gt; ' ..&lt;br /&gt;
		mtext['txt-age'] ..&lt;br /&gt;
		dateDifference({&lt;br /&gt;
			diff = diff,&lt;br /&gt;
			show = show,&lt;br /&gt;
			abbr = 'abbr_off',&lt;br /&gt;
			disp = disp,&lt;br /&gt;
			sep = 'sep_space',&lt;br /&gt;
		}) ..&lt;br /&gt;
		')&amp;lt;/span&amp;gt;')&lt;br /&gt;
	local warnings = tonumber(frame.args.warnings)&lt;br /&gt;
	if warnings and warnings &amp;gt; 0 then&lt;br /&gt;
		local good = {&lt;br /&gt;
			df = true,&lt;br /&gt;
			mf = true,&lt;br /&gt;
			day = true,&lt;br /&gt;
			day1 = true,&lt;br /&gt;
			month = true,&lt;br /&gt;
			month1 = true,&lt;br /&gt;
			year = true,&lt;br /&gt;
			year1 = true,&lt;br /&gt;
		}&lt;br /&gt;
		local invalid&lt;br /&gt;
		local imax = options.textdates and 1 or 3&lt;br /&gt;
		for k, _ in pairs(args) do&lt;br /&gt;
			if type(k) == 'number' then&lt;br /&gt;
				if k &amp;gt; imax then&lt;br /&gt;
					invalid = tostring(k)&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				if not good[k] then&lt;br /&gt;
					invalid = k&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if invalid then&lt;br /&gt;
			result = result .. message({ 'mt-bad-param1', invalid }, 'warning')&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function dda(frame)&lt;br /&gt;
	-- Implement [[Template:Death date and age]].&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local options = {&lt;br /&gt;
		missing1 = 'mt-need-valid-dd',&lt;br /&gt;
		missing2 = 'mt-need-valid-bd2',&lt;br /&gt;
		noMissing = true,&lt;br /&gt;
		partial = true,&lt;br /&gt;
	}&lt;br /&gt;
	local date1, date2 = getDates(frame, options)&lt;br /&gt;
	if type(date1) == 'string' then&lt;br /&gt;
		return date1&lt;br /&gt;
	end&lt;br /&gt;
	local diff = date1 - date2&lt;br /&gt;
	if diff.isnegative then&lt;br /&gt;
		return message('mt-dd-wrong-order')&lt;br /&gt;
	end&lt;br /&gt;
	local Date = getExports(frame)&lt;br /&gt;
	local today = Date('currentdate') + 1  -- one day in future allows for timezones&lt;br /&gt;
	if date1 &amp;gt; today then&lt;br /&gt;
		return message('mt-dd-future')&lt;br /&gt;
	end&lt;br /&gt;
	local years&lt;br /&gt;
	if diff.partial then&lt;br /&gt;
		years = diff.partial.years&lt;br /&gt;
		years = type(years) == 'table' and years[2] or years&lt;br /&gt;
	else&lt;br /&gt;
		years = diff.years&lt;br /&gt;
	end&lt;br /&gt;
	if years &amp;gt; 150 then&lt;br /&gt;
		return message('mt-invalid-dates-age')&lt;br /&gt;
	end&lt;br /&gt;
	local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)&lt;br /&gt;
	local result&lt;br /&gt;
	if date1.day then  -- y, m, d known&lt;br /&gt;
		result = (df and&lt;br /&gt;
			'%-d %B %-Y' or&lt;br /&gt;
			'%B %-d, %-Y') ..&lt;br /&gt;
			'&amp;lt;span style=&amp;quot;display:none&amp;quot;&amp;gt;(%-Y-%m-%d)&amp;lt;/span&amp;gt;'&lt;br /&gt;
	elseif date1.month then  -- y, m known; d unknown&lt;br /&gt;
		result =&lt;br /&gt;
			'%B %-Y' ..&lt;br /&gt;
			'&amp;lt;span style=&amp;quot;display:none&amp;quot;&amp;gt;(%-Y-%m-00)&amp;lt;/span&amp;gt;'&lt;br /&gt;
	else  -- y known; m, d unknown&lt;br /&gt;
		result =&lt;br /&gt;
			'%-Y' ..&lt;br /&gt;
			'&amp;lt;span style=&amp;quot;display:none&amp;quot;&amp;gt;(%-Y-00-00)&amp;lt;/span&amp;gt;'&lt;br /&gt;
	end&lt;br /&gt;
	result = from_en(date1:text(result) ..&lt;br /&gt;
		mtext['txt-aged'] ..&lt;br /&gt;
		dateDifference({&lt;br /&gt;
			diff = diff,&lt;br /&gt;
			show = 'y',&lt;br /&gt;
			abbr = 'abbr_off',&lt;br /&gt;
			disp = 'disp_raw',&lt;br /&gt;
			range = 'dash',&lt;br /&gt;
			sep = 'sep_space',&lt;br /&gt;
		}) ..&lt;br /&gt;
		')')&lt;br /&gt;
	local warnings = tonumber(frame.args.warnings)&lt;br /&gt;
	if warnings and warnings &amp;gt; 0 then&lt;br /&gt;
		local good = {&lt;br /&gt;
			df = true,&lt;br /&gt;
			mf = true,&lt;br /&gt;
		}&lt;br /&gt;
		local invalid&lt;br /&gt;
		local imax = options.textdates and 2 or 6&lt;br /&gt;
		for k, _ in pairs(args) do&lt;br /&gt;
			if type(k) == 'number' then&lt;br /&gt;
				if k &amp;gt; imax then&lt;br /&gt;
					invalid = tostring(k)&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				if not good[k] then&lt;br /&gt;
					invalid = k&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if invalid then&lt;br /&gt;
			result = result .. message({ 'mt-bad-param1', invalid }, 'warning')&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function dateToGsd(frame)&lt;br /&gt;
	-- Implement [[Template:Gregorian serial date]].&lt;br /&gt;
	-- Return Gregorian serial date of the given date, or the current date.&lt;br /&gt;
	-- The returned value is negative for dates before 1 January 1 AD&lt;br /&gt;
	-- despite the fact that GSD is not defined for such dates.&lt;br /&gt;
	local date = getDates(frame, { wantMixture=true, single=true })&lt;br /&gt;
	if type(date) == 'string' then&lt;br /&gt;
		return date&lt;br /&gt;
	end&lt;br /&gt;
	return tostring(date.gsd)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function jdToDate(frame)&lt;br /&gt;
	-- Return formatted date from a Julian date.&lt;br /&gt;
	-- The result includes a time if the input includes a fraction.&lt;br /&gt;
	-- The word 'Julian' is accepted for the Julian calendar.&lt;br /&gt;
	local Date = getExports(frame)&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local date = Date('juliandate', args[1], args[2])&lt;br /&gt;
	if date then&lt;br /&gt;
		return from_en(date:text())&lt;br /&gt;
	end&lt;br /&gt;
	return message('mt-need-jdn')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function dateToJd(frame)&lt;br /&gt;
	-- Return Julian date (a number) from a date which may include a time,&lt;br /&gt;
	-- or the current date ('currentdate') or current date and time ('currentdatetime').&lt;br /&gt;
	-- The word 'Julian' is accepted for the Julian calendar.&lt;br /&gt;
	local Date = getExports(frame)&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local date = Date(args[1], args[2], args[3], args[4], args[5], args[6], args[7])&lt;br /&gt;
	if date then&lt;br /&gt;
		return tostring(date.jd)&lt;br /&gt;
	end&lt;br /&gt;
	return message('mt-need-valid-ymd-current')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function timeInterval(frame)&lt;br /&gt;
	-- Implement [[Template:Time interval]].&lt;br /&gt;
	-- There are two positional arguments: date1, date2.&lt;br /&gt;
	-- The default for each is the current date and time.&lt;br /&gt;
	-- Result is date2 - date1 formatted.&lt;br /&gt;
	local Date = getExports(frame)&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	local parms = {&lt;br /&gt;
		extra = makeExtra(args),&lt;br /&gt;
		wantDuration = yes(args.duration),&lt;br /&gt;
		range = yes(args.range) or (args.range == 'dash' and 'dash' or nil),&lt;br /&gt;
		wantSc = yes(args.sc),&lt;br /&gt;
	}&lt;br /&gt;
	local fix = yes(args.fix) and 'fix' or ''&lt;br /&gt;
	local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime')&lt;br /&gt;
	if not date1 then&lt;br /&gt;
		return message('mt-invalid-start')&lt;br /&gt;
	end&lt;br /&gt;
	local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime')&lt;br /&gt;
	if not date2 then&lt;br /&gt;
		return message('mt-invalid-end')&lt;br /&gt;
	end&lt;br /&gt;
	parms.diff = date2 - date1&lt;br /&gt;
	for argname, translate in pairs(translateParameters) do&lt;br /&gt;
		local parm = stripToNil(args[argname])&lt;br /&gt;
		if parm then&lt;br /&gt;
			parm = translate[parm]&lt;br /&gt;
			if parm == nil then  -- test for nil because false is a valid setting&lt;br /&gt;
				return message({ 'mt-bad-param2', argname, args[argname] })&lt;br /&gt;
			end&lt;br /&gt;
			parms[argname] = parm&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if parms.round then&lt;br /&gt;
		local round = parms.round&lt;br /&gt;
		local show = parms.show&lt;br /&gt;
		if round ~= 'on' then&lt;br /&gt;
			if show then&lt;br /&gt;
				if show.id ~= round then&lt;br /&gt;
					return message({ 'mt-conflicting-show', args.show, args.round })&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				parms.show = translateParameters.show[round]&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		parms.round = true&lt;br /&gt;
	end&lt;br /&gt;
	return from_en(dateDifference(parms))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
	age_generic = ageGeneric,           -- can emulate several age templates&lt;br /&gt;
	birth_date_and_age = bda,           -- Template:Birth_date_and_age&lt;br /&gt;
	death_date_and_age = dda,           -- Template:Death_date_and_age&lt;br /&gt;
	gsd = dateToGsd,                    -- Template:Gregorian_serial_date&lt;br /&gt;
	extract = dateExtract,              -- Template:Extract&lt;br /&gt;
	jd_to_date = jdToDate,              -- Template:?&lt;br /&gt;
	JULIANDAY = dateToJd,               -- Template:JULIANDAY&lt;br /&gt;
	time_interval = timeInterval,       -- Template:Time_interval&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>imported&gt;Johnuniq</name></author>
		
	</entry>
</feed>