Nota Importante: Este módulo está en proceso de adaptación de Module:Age al español para poder soportar la funcionalidad de todas las plantillas de edad que se utilizan en la Wikipedia en español. La idea es que sea un reemplazo único para todas las plantillas de edad existentes para juntar toda la implementación en un sitio y hacerla más fácil de mantener. Se agradece muchísimo el impecable trabajo original de User:Johnuniq sin el cual esto no sería posible.

Plantillas implementadas

Módulo:Edad implementa las siguientes plantillas: Las últimas dos columnas representan:

  • Adaptado a español: si la implementación del módulo se ha adaptado para aceptar los parámetros de la actual implementación de sus respectivas plantillas.
  • En uso: si la implementación actual de esa plantilla en español utiliza éste módulo.
Plantilla Wikitexto de la plantilla Adaptado a español En uso
{{intervalo tiempo}} {{#invoke:edad|time_interval}} Completo
{{edad en días}} {{#invoke:edad|age_generic|template=age_days}} Parcial No
{{duración en días}} {{#invoke:edad|age_generic|template=duration_days}} Completo
{{edad}} {{#invoke:edad|age_generic|template=age_full_years}} Completo Pendiente
{{edad en años}} {{#invoke:edad|age_generic|template=age_in_years}} Completo Pendiente
{{edad en meses}} {{#invoke:edad|age_generic|template=age_m}} Completo
{{edad en semanas}} {{#invoke:edad|age_generic|template=age_w}} Completo
{{edad en años y días}} {{#invoke:edad|age_generic|template=age_yd}} Completo Pendiente
{{edad en años y meses}} {{#invoke:edad|age_generic|template=age_ym}} Completo
{{edad en años, meses y días}} {{#invoke:edad|age_generic|template=age_ymd}} Parcial Pendiente

Las plantillas de edad esperan recibir la fecha más antigua primero Las implementaciones de age_in_years y age_in_years_nts muestran un mensaje de error si no es así. Si se necesita una comprobación similar en otras plantillas, se puede añadir negative=error al invoke. Por ejemplo, {{edad}} podría usar:

  • {{#invoke:edad|age_generic|template=age_full_years|negative=error}}

Si no se aplica negative=error la diferencia negativa se muestra con un signo menos (−).

Formatos de Fecha

Las fechas pueden usar parámetros numerados o nombrados para especificar el día/mes/año. De forma alternativa, una fecha completa se puede introducir en una variedad de formatos. Por ejemplo:

  • {{edad en años y meses|a1=2001|m1=1|d1=10|a2=2012|m2=2|d2=20}} → 11 años y 1 mes
  • {{edad en años y meses|a=2001|m=1|d=10|a2=2012|m2=2|d2=20}} → 11 años y 1 mes
  • {{edad en años y meses|10|1|2001|20|2|2012}} → 11 años y 1 mes
  • {{edad en años y meses|2001-1-10|2012-2-20}} → 11 años y 1 mes
  • {{edad en años y meses|10 ene 2001|20 feb 2012}} → 11 años y 1 mes
  • {{edad en años y meses|10 de enero de 2001|20 de febrero de 2012}} → 11 años y 1 mes
  • {{edad en años y meses|enero 10, 2001|feb 20, 2012}} → 11 años y 1 mes

Si se omite una de las dos fechas, se utiliza la actual en su lugar. Por ejemplo:

  • {{edad en años y meses|a2=2012|m2=2|d2=20}} → −12 años y 8 meses
  • {{edad en años y meses||||20|2|2012}} → −12 años y 8 meses
  • {{edad en años y meses||2012-2-20}} → −12 años y 8 meses
  • {{edad en años y meses||20 feb 2012}} → −12 años y 8 meses
  • {{edad en años y meses||feb 20, 2012}} → −12 años y 8 meses
  • {{edad en años y meses|a1=2001|m1=1|d1=10}} → 23 años y 9 meses
  • {{edad en años y meses|year=2001|month=1|day=10}}
  • {{edad en años y meses|10|1|2001|}} → 23 años y 9 meses
  • {{edad en años y meses|2001-1-10}} → 23 años y 9 meses
  • {{edad en años y meses|10 ene 2001}} → 23 años y 9 meses
  • {{edad en años y meses|10 de enero de 2001}} → 23 años y 9 meses
  • {{edad en años y meses|enero 10, 2001}} → 23 años y 9 meses

Parámetros

The following options are available:

Parámetro Descripción
duration=on La fecha final está incluida en el resultado añadiendo un día a la edad.
fix=on Ajusta unidades de tiempo no válidas Ver "Template:Extract#Fix" (Pendiente de traducir).
format=commas Un valor de 1000 o mayor aparece con comas como separadores.
format=raw Los números se muestran sin comas y los negativos con un guión para {{#expr}}. Además, {{age}} devuelve el número tal cual sin el span con información.
format=cardinal Mostrar el número resultante en texto utilizando palabras como "cinco" en lugar de 5. Ver más abajo.
format=ordinal Mostrar el número resultante utilizando palabras como "quinto" en vez de 5. Ver más abajo.
prefix=texto Inserta el texto antes del resultado pero después de una clave de ordenación. Por ejemplo, {{edad|23 de julio de 1910|14 de julio de 1976|prefix=sobre|sortable=on}} devuelve una clave de ordenación oculta seguida de "sobre 65".
range=dash Aceptar un año solo o año y mes y devolver un rango de edades separadas por un guión largo (–).
range=yes Aceptar un año o mes/año y mostrar el rango separado por "or".
range=no Aceptar un año o mes/año, pero mostrar solo una edad como si se hubiesen introducido fechas completas.
round=on La edad se redondea a la unidad de tiempo menos significativa más cercana.
sc=on Se utiliza la (solo útil si se muestran tres o más valores).
sc=yes Igual que sc=on.
show=hide No se muestra la edad; puede ser útil combinado con sortable=on.
sortable=on Inserta una clave de ordenación oculta antes del resultado (para utilizar con tablas ordenables).
sortable=table Inserta una clave de ordenación utilizando la sintaxis de tabla data-sort-value="value"|.
sortable=debug Igual que sortable=on pero mostrando la clave de ordenación. Usado solo para testear.
sortable=off Sin clave de ordenación, si se incluye puede sobreescribir el valor por defecto de plantillas como {{edad nts}}).
ceros=yes Muestra los campos que dan cero en la diferencia ('1 año, 0 meses y 3 días' VS '1 año y 3 días').

Algunos ejemplos de uso del parámetro range se muestran a continuación.

  • {{edad en años y meses|year=2001|month=1|a2=2012|m2=2|range=yes}} → −12 años y 8 o 9 meses
  • {{edad en años y meses||1|2001|2|2012|range=yes}}Error: La segunda fecha debería ser año, mes, día
  • {{edad en años y meses|ene 2001|feb 2012|range=yes}} → 11 años y 0 o 1 mes
  • {{edad en años y meses|ene 2001|febrero de 2012|range=dash}} → 11 años y 0–1 mes
  • {{edad en años y meses|ene 2001|feb 2012|range=no}} → 11 años y 1 mes No se está usando la plantilla de forma adecuada.
  • {{edad en años y meses|12 ene 2001|feb 2012|range=no}} → 11 años y 1 mes No se está usando la plantilla de forma adecuada.
  • {{edad en años y meses|2001|2012|range=no}} → 11 años No se está usando la plantilla de forma adecuada.
  • {{edad en años y meses|2001|23 feb 2012|range=no}} → 11 años No se está usando la plantilla de forma adecuada.

La clave de ordenación se basa en la edad en días y fracciones de los mismos si se especifica una hora.

  • {{edad en años y meses|10 ene 2001|20 feb 2012|sortable=debug}}7003405800000000000♠11 años y 1 mes
  • {{edad en años y meses|10 ene 2001|6:00 am 20 feb 2012|sortable=debug}}7003405825000000000♠11 años y 1 mes
  • {{edad en años y meses|10 ene 2001|6:00 am 20 feb 2012|sortable=debug|show=hide}}7003405825000000000♠

Un día extra añadido para la duración.

  • {{edad en años y meses|20 ene 2001|19 feb 2012}} → 10 años y 11 meses No se está usando la plantilla de forma adecuada.
  • {{edad en años y meses|20 ene 2001|19 feb 2012|duration=on}} → 11 años y 1 mes

También se puede redondear la unidad de tiempo menos significativa.

  • {{edad en años y meses|20 ene 2001|10 ene 2012}} → 10 años y 11 meses
  • {{edad en años y meses|20 ene 2001|10 ene 2012|round=on}} → 11 años No se está usando la plantilla de forma adecuada.

Los números grandes se pueden formatear con comas.

  • {{edad en años y meses|2012|120|format=commas|range=yes}} → −1,891 o 1,892 años
  • {{edad en años y meses|2012|120|format=commas|range=dash}} → −1,891–1,892 años

Deletrear números

Nota: La traducción de Módulo:ConvertNumeric no está 100% terminada, los cardinales funcionan pero en español la distinción entre cardinal y cardinal_us no tiene sentido. Se está trabajando en ello. Nota2: Los siguientes ejemplos no funcionarán hasta que se migre la plantilla de edad

Las plantillas que utilizan age_generic pueden mostrar números en forma de palabras en vez de numerales. El resultado puede ser un número cardinal como "cinco" o un número ordinal como "quinto". Dependiendo de cómo se escriba el parámetro la primera letra será o no en mayúscula. Si indicas una fecha parcial y pides un rango de vuelta también te lo puede deletrear. Ejemplos:

  • {{edad en meses|01|01|1898|01|02|2018|format=cardinal}} → mil cuatrocientos cuarenta y uno
  • {{edad en meses|01|01|1898|01|02|2018|format=Cardinal}} → Mil cuatrocientos cuarenta y uno
  • {{edad en meses|01|01|1898|01|02|2018|format=ordinal}} → milésimo cuadringentésimo cuadragésimo primero
  • {{edad en meses|01|01|1898|01|02|2018|format=Ordinal}} → Milésimo cuadringentésimo cuadragésimo primero
  • {{edad en meses|1980|1990|range=yes|format=Cardinal}} → Ciento ocho o ciento treinta y uno
  • {{edad en años y meses|abril 1980|1995|format=Cardinal|range=yes}} → Catorce o quince años

Categoría de seguimiento

Ver también

  • {{intervalo tiempo}} • Esta plantilla soporta todos los cálculos de edad/duración y provee más opciones como abreviaturas o omisión de unidades.

-- Implement various "age of" and other date-related templates.

local mtext = {
	-- Message and other text that should be localized.
	['mt-bad-param1'] =             'Parámetro no válido $1',
	['mt-bad-param2'] =             'El parámetro $1=$2 no es válido',
	['mt-bad-show'] =               'El parámetro show=$1 no está soportado',
	['mt-cannot-add'] =             'No se puede sumar "$1"',
	['mt-conflicting-show'] =       'El parámetro show=$1 está en conflicto con round=$2',
	['mt-date-wrong-order'] =       'La segunda fecha tiene que ocurrir después que la primera',
	['mt-dd-future'] =              'La fecha de muerte (la primera) no puede ser en el futuro',
	['mt-dd-wrong-order'] =         'La fecha de muerte (la primera) tiene que ocurrir después que la de nacimiento (la segunda)',
	['mt-invalid-bd-age'] =         'Fecha de nacimiento no válida para calcular una edad',
	['mt-invalid-dates-age'] =      'Fechas no válidas para calcular la edad',
	['mt-invalid-end'] =            'Fecha de fin no válida en el segundo parámetro',
	['mt-invalid-start'] =          'Fecha de comienzo no válida en el primer parámetro',
	['mt-need-jdn'] =               'Se necesita un número de fecha juliana válido',
	['mt-need-valid-bd'] =          'Se necesita una fecha de nacimiento válida: año, mes día',
	['mt-need-valid-bd2'] =         'Se necesita una fecha de nacimiento válida (segunda fecha): año, mes día',
	['mt-need-valid-date'] =        'Se necesita una fecha válida',
	['mt-need-valid-dd'] =          'Se necesita una fecha de muerte válida (segunda fecha): año, mes, día',
	['mt-need-valid-ymd'] =         'Se necesita un año, mes y día válidos',
	['mt-need-valid-ymd-current'] = 'Se necesita un año|mes|día válidos o "currentdate"',
	['mt-need-valid-ymd2'] =        'La segunda fecha debería ser año, mes, día',
	['mt-template-bad-name'] =      'El nombre de plantilla especificado no es válido',
	['mt-template-x'] =             'La plantilla que llame a este método debe especificar "|template=x" donde x es la operación necesaria',
	['txt-age'] =                   '(edad ',
	['txt-aged'] =                  ' (a los ',
	['txt-and'] =                   ' y ',
	['txt-comma-and'] =             ', y ',
	['txt-error'] =                 'Error: ',
	['txt-or'] =                    ' o ',
}

local translate, from_en, to_en, isZero
if translate then
	-- Functions to translate from en to local language and reverse go here.
	-- See example at [[:bn:Module:বয়স]].
else
	from_en = function (text)
		return text
	end
	isZero = function (text)
		return tonumber(text) == 0
	end
end

local _Date, _currentDate
-- Return objects exported from the date module or its sandbox.
local function getExports(frame)
	if not _Date then
		local sandbox = frame:getTitle():find('sandbox', 1, true) and '/sandbox' or ''
		local datemod = require('Módulo:Date' .. sandbox)
		local realDate = datemod._Date
		_currentDate = datemod._current
		if to_en then
			_Date = function (...)
				local args = {}
				for i, v in ipairs({...}) do
					args[i] = to_en(v)
				end
				return realDate(unpack(args))
			end
		else
			_Date = realDate
		end
	end
	return _Date, _currentDate
end

local Collection  -- a table to hold items
Collection = {
	add = function (self, item)
		if item ~= nil then
			self.n = self.n + 1
			self[self.n] = item
		end
	end,
	join = function (self, sep)
		return table.concat(self, sep)
	end,
	remove = function (self, pos)
		if self.n > 0 and (pos == nil or (0 < pos and pos <= self.n)) then
			self.n = self.n - 1
			return table.remove(self, pos)
		end
	end,
	sort = function (self, comp)
		table.sort(self, comp)
	end,
	new = function ()
		return setmetatable({n = 0}, Collection)
	end
}
Collection.__index = Collection

-- If text is a string, return its trimmed content, or nil if empty.
-- Otherwise return text (which may, for example, be nil).
local function stripToNil(text)
	if type(text) == 'string' then
		text = text:match('(%S.-)%s*$')
	end
	return text
end

-- Return true if parameter should be interpreted as "yes".
-- Do not want to accept mixed upper/lowercase unless done by current templates.
-- Need to accept "on" because "round=on" is wanted.
local function yes(parameter)
	return ({ y = true, yes = true, on = true })[parameter]
end

-- Return formatted message text for an error or warning.
local function message(msg, id)
	local function getText(msg)
		return mtext[msg] or error('Bug: el mensaje "' .. tostring(msg) .. '" no está definido')
	end
	local text
	if type(msg) == 'table' then
		text = getText(msg[1])
		local rep = {}
		for i, v in ipairs(msg) do
			if i > 1 then
				rep['$' .. (i - 1)] = v
			end
		end
		text = text:gsub('$%d+', rep)
	else
		text = getText(msg)
	end
	local categories = {
		error = '[[Categoría:Error de Edad]]',
		warning = '[[Categoría:Error de Edad]]',  -- same as error until determine whether 'Age warning' would be worthwhile
	}
	local a, b, category
	if id == 'warning' then
		a = '<sup>[<i>'
		b = '</i>]</sup>'
	else
		a = '<strong class="error">' .. getText('txt-error')
		b = '</strong>'
	end
	if mw.title.getCurrentTitle():inNamespaces(0) then
		-- Category only in namespaces: 0=article.
		category = categories[id or 'error']
	end
	return
		a ..
		mw.text.nowiki(text) ..
		b ..
		(category or '')
end

-- Return the given number formatted with commas as group separators,
-- given that the number is an integer.
local function formatNumber(number)
	local numstr = tostring(number)
	local length = #numstr
	local places = Collection.new()
	local pos = 0
	repeat
		places:add(pos)
		pos = pos + 3
	until pos >= length
	places:add(length)
	local groups = Collection.new()
	for i = places.n, 2, -1 do
		local p1 = length - places[i] + 1
		local p2 = length - places[i - 1]
		groups:add(numstr:sub(p1, p2))
	end
	return groups:join(',')
end

-- Return result of spelling number, or
-- return number (as a string) if cannot spell it.
-- i == 1 for the first number which can optionally start with an uppercase letter.
local function spellNumber(number, options, i)
	number = tostring(number)
	return require('Módulo:ConvertNumeric').spell_number(
		number,
		-- nil,                       -- fraction numerator  NO SOPORTADO
		-- nil,                       -- fraction denominator  NO SOPORTADO
		i == 1 and options.upper,     -- true: 'One' instead of 'one'
		options.ordinal,              -- true: 'first' instead of 'one'
		options.adj                   -- true: hyphenated
	) or number
end

-- Return extra text that will be inserted before the visible result
-- but after any sort key.
local function makeExtra(args, flagCurrent)
	
	local extra = args.prefix or ''
	if mw.ustring.len(extra) > 1 then
		-- Parameter "~" gives "~3" whereas "over" gives "over 3".
		if extra:sub(-6, -1) ~= '&nbsp;' then
			extra = extra .. ' '
		end
	end
	if flagCurrent then
		extra = '<span class="currentage"></span>' .. extra
	end
	return extra
end

-- Return a sort key if requested.
-- Assume value is a valid number which has not overflowed.
local function makeSort(value, sortable)
	if sortable == 'sortable_table' or sortable == 'sortable_on' or sortable == 'sortable_debug' then
		local sortKey
		if value == 0 then
			sortKey = '5000000000000000000'
		else
			local mag = math.floor(math.log10(math.abs(value)) + 1e-14)
			if value > 0 then
				sortKey = 7000 + mag
			else
				sortKey = 2999 - mag
				value = value + 10^(mag+1)
			end
			sortKey = string.format('%d', sortKey) .. string.format('%015.0f', math.floor(value * 10^(14-mag)))
		end
		local result
		if sortable == 'sortable_table' then
			result = 'data-sort-value="_SORTKEY_"|'
		elseif sortable == 'sortable_debug' then
			result = '<span data-sort-value="_SORTKEY_♠"><span style="border:1px solid">_SORTKEY_♠</span></span>'
		else
			result = '<span data-sort-value="_SORTKEY_♠"></span>'
		end
		return result:gsub('_SORTKEY_', sortKey)
	end
end

local translateParameters = {
	abbr = {
		off = 'abbr_off',
		on = 'abbr_on',
	},
	disp = {
		age = 'disp_age',
		raw = 'disp_raw',
	},
	format = {
		raw = 'format_raw',
		commas = 'format_commas',
	},
	round = {
		on = 'on',
		yes = 'on',
		months = 'ym',
		weeks = 'ymw',
		days = 'ymd',
		hours = 'ymdh',
	},
	sep = {
		comma = 'sep_comma',
		[','] = 'sep_comma',
		serialcomma = 'sep_serialcomma',
		space = 'sep_space',
	},
	show = {
		hide = { id = 'hide' },
		y = { 'y', id = 'y' },
		ym = { 'y', 'm', id = 'ym' },
		ymd = { 'y', 'm', 'd', id = 'ymd' },
		ymw = { 'y', 'm', 'w', id = 'ymw' },
		ymwd = { 'y', 'm', 'w', 'd', id = 'ymwd' },
		yd = { 'y', 'd', id = 'yd', keepZero = true },
		m = { 'm', id = 'm' },
		md = { 'm', 'd', id = 'md' },
		w = { 'w', id = 'w' },
		wd = { 'w', 'd', id = 'wd' },
		h = { 'H', id = 'h' },
		hm = { 'H', 'M', id = 'hm' },
		hms = { 'H', 'M', 'S', id = 'hms' },
		d = { 'd', id = 'd' },
		dh = { 'd', 'H', id = 'dh' },
		dhm = { 'd', 'H', 'M', id = 'dhm' },
		dhms = { 'd', 'H', 'M', 'S', id = 'dhms' },
		ymdh = { 'y', 'm', 'd', 'H', id = 'ymdh' },
		ymdhm = { 'y', 'm', 'd', 'H', 'M', id = 'ymdhm' },
		ymwdh = { 'y', 'm', 'w', 'd', 'H', id = 'ymwdh' },
		ymwdhm = { 'y', 'm', 'w', 'd', 'H', 'M', id = 'ymwdhm' },
	},
	sortable = {
		off = false,
		on = 'sortable_on',
		table = 'sortable_table',
		debug = 'sortable_debug',
	},
}

local spellOptions = {
	cardinal = {},
	Cardinal = { upper = true },
	ordinal = { ordinal = true },
	Ordinal = { ordinal = true, upper = true },
}

-- Return part of a date after performing an optional operation.
local function dateExtract(frame)
	local Date = getExports(frame)
	local args = frame:getParent().args
	local parms = {}
	for i, v in ipairs(args) do
		parms[i] = v
	end
	if yes(args.fix) then
		table.insert(parms, 'fix')
	end
	if yes(args.partial) then
		table.insert(parms, 'partial')
	end
	local show = stripToNil(args.show) or 'dmy'
	local date = Date(unpack(parms))
	if not date then
		if show == 'format' then
			return 'error'
		end
		return message('mt-need-valid-date')
	end
	local add = stripToNil(args.add)
	if add then
		for item in add:gmatch('%S+') do
			date = date + item
			if not date then
				return message({ 'mt-cannot-add', item })
			end
		end
	end
	local sortKey, result
	local sortable = translateParameters.sortable[args.sortable]
	if sortable then
		local value = (date.partial and date.partial.first or date).jdz
		sortKey = makeSort(value, sortable)
	end
	if show ~= 'hide' then
		result = date[show]
		if result == nil then
			result = from_en(date:text(show))
		elseif type(result) == 'boolean' then
			result = result and '1' or '0'
		else
			result = from_en(tostring(result))
		end
	end
	return (sortKey or '') .. makeExtra(args) .. (result or '')
end

-- Return text to be used between a range of ages.
local function rangeJoin(range)
	return range == 'dash' and '–' or mtext['txt-or']
end

-- Return wikitext representing an age or duration.
local function makeText(values, components, names, options, noUpper)
	local text = Collection.new()
	local count = #values
	local sep = names.sep or ''
	for i, v in ipairs(values) do
		-- v is a number (say 4 for 4 years), or a table ({4,5} for 4 or 5 years).
		local islist = type(v) == 'table'
		if (islist or v > 0) or (text.n == 0 and i == count) or (text.n > 0 and components.keepZero) then
			local fmt, vstr
			if options.spell then
				fmt = function(number)
					return spellNumber(number, options.spell, noUpper or i)
				end
			elseif i == 1 and options.format == 'format_commas' then
				-- Numbers after the first should be small and not need formatting.
				fmt = formatNumber
			else
				fmt = tostring
			end
			if islist then
				vstr = fmt(v[1]) .. rangeJoin(options.range)
				noUpper = true
				vstr = vstr .. fmt(v[2])
			else
				vstr = fmt(v)
			end
			local name = names[components[i]]
			if name then
				local plural = names.plural
				if not plural or (islist and v[2] or v) == 1 then
					plural = ''
				end
				if plural ~= '' and name == 'mes' then  -- Manejar el caso de que sea necesario poner 'meses' para evitar 'mess'
					name = 'meses'
					plural = ''
				end
				if not options.ignoreZero then
					text:add(vstr .. sep .. name .. plural)
				elseif not (vstr == '0') then
					text:add(vstr .. sep .. name .. plural)
				end
			else
				if not options.ignoreZero then
					text:add(vstr)
				elseif not (vstr == '0') then
					text:add(vstr)
				end
			end
		end
	end
	local first, last
	if options.join == 'sep_space' then
		first = ' '
		last = ' '
	elseif options.join == 'sep_comma' then
		first = ', '
		last = ', '
	elseif options.join == 'sep_serialcomma' and text.n > 2 then
		first = ', '
		last = mtext['txt-comma-and']
	elseif options.join == 'sep_and' then
		first = ', '
		last = mtext['txt-and']
	else
		first = ', '
		last = mtext['txt-and']
	end
	for i, v in ipairs(text) do
		if i < text.n then
			text[i] = v .. (i + 1 < text.n and first or last)
		end
	end
	local sign = ''
	if options.isnegative then
		-- Do not display negative zero.
		if text.n > 1 or (text.n == 1 and text[1]:sub(1, 1) ~= '0' ) then
			if options.format == 'format_raw' then
				sign = '-'  -- plain hyphen so result can be used in a calculation
			else
				sign = '−'  -- Unicode U+2212 MINUS SIGN
			end
		end
	end
	return
		(options.sortKey or '') ..
		(options.extra or '') ..
		sign ..
		text:join() ..
		(options.suffix or '')
end

-- Return a formatted date difference using the given parameters
-- which have been validated.
local function dateDifference(parms)
	local names = {
		abbr_off = {
			plural = 's',
			sep = '&nbsp;',
			y = 'año',
			m = 'mes',
			w = 'semana',
			d = 'día',
			H = 'hora',
			M = 'minuto',
			S = 'segundo',
		},
		abbr_on = {
			y = 'a',
			m = 'm',
			w = 's',
			d = 'd',
			H = 'h',
			M = 'm',
			S = 's',
		},
		abbr_infant = {      -- for {{age for infant}}
			plural = 's',
			sep = '&nbsp;',
			y = 'yr',
			m = 'mo',
			w = 'wk',
			d = 'day',
			H = 'hr',
			M = 'min',
			S = 'sec',
		},
		abbr_raw = {},
	}
	local diff = parms.diff  -- must be a valid date difference
	local show = parms.show  -- may be nil; default is set below
	local abbr = parms.abbr or 'abbr_off'
	local defaultJoin
	if abbr ~= 'abbr_off' then
		defaultJoin = 'sep_space'
	end
	if not show then
		show = 'ymd'
		if parms.disp == 'disp_age' then
			if diff.years < 3 then
				defaultJoin = 'sep_space'
				if diff.years >= 1 then
					show = 'ym'
				else
					show = 'md'
				end
			else
				show = 'y'
			end
		end
	end
	if type(show) ~= 'table' then
		show = translateParameters.show[show]
	end
	if parms.disp == 'disp_raw' then
		defaultJoin = 'sep_space'
		abbr = 'abbr_raw'
	elseif parms.wantSc then
		defaultJoin = 'sep_serialcomma'
	end
	local diffOptions = {
		round = parms.round,
		duration = parms.wantDuration,
		range = parms.range and true or nil,
	}
	local sortKey
	if parms.sortable then
		local value = diff.age_days + (parms.wantDuration and 1 or 0)  -- days and fraction of a day
		if diff.isnegative then
			value = -value
		end
		sortKey = makeSort(value, parms.sortable)
	end
	local textOptions = {
		extra = parms.extra,
		format = parms.format,
		join = parms.sep or defaultJoin,
		isnegative = diff.isnegative,
		range = parms.range,
		sortKey = sortKey,
		spell = parms.spell,
		suffix = parms.suffix,  -- not currently used
		ignoreZero = parms.ignoreZero,
	}
	if show.id == 'hide' then
		return sortKey or ''
	end
	local values = { diff:age(show.id, diffOptions) }
	if values[1] then
		return makeText(values, show, names[abbr], textOptions)
	end
	if diff.partial then
		-- Handle a more complex range such as
		-- {{age_yd|20 Dec 2001|2003|range=yes}} → 1 year, 12 days or 2 years, 11 days
		local opt = {
			format = textOptions.format,
			join = textOptions.join,
			isnegative = textOptions.isnegative,
			spell = textOptions.spell,
			ignoreZero = parms.ignoreZero,
		}
		return
			(textOptions.sortKey or '') ..
			makeText({ diff.partial.mindiff:age(show.id, diffOptions) }, show, names[abbr], opt) ..
			rangeJoin(textOptions.range) ..
			makeText({ diff.partial.maxdiff:age(show.id, diffOptions) }, show, names[abbr], opt, true) ..
			(textOptions.suffix or '')
	end
	return message({ 'mt-bad-show', show.id })
end

-- Parse template parameters and return one of:
-- * date         (a date table, if single)
-- * date1, date2 (two date tables, if not single)
-- * text         (a string error message)
-- A missing date is optionally replaced with the current date.
-- If wantMixture is true, a missing date component is replaced
-- from the current date, so can get a bizarre mixture of
-- specified/current y/m/d as has been done by some "age" templates.
-- Some results may be placed in table getopt.
local function getDates(frame, getopt)
	local Date, currentDate = getExports(frame) -- Obtener referencias a las dependencias
	getopt = getopt or {}  -- Extraer opciones establecidas
	local function flagCurrent(text)
		-- This allows the calling template to detect if the current date has been used,
		-- that is, whether both dates have been entered in a template expecting two.
		-- For example, an infobox may want the age when an event occurred, not the current age.
		-- Don't bother detecting if wantMixture is used because not needed and it is a poor option.
		if not text then
			if getopt.noMissing then
				return nil  -- this gives a nil date which gives an error
			end
			text = 'currentdate'
			if getopt.flag == 'usesCurrent' then
				getopt.usesCurrent = true
			end
		end
		return text
	end
	
	local args = frame:getParent().args
	local fields = {}
	local isNamed = args.a or args.a1 or args.a2 or
		args.m or args.m1 or args.m2 or
		args.d or args.d1 or args.d2
		
	-- Si se usan nombres asignarlos a la lista
	if isNamed then
		fields[1] = args.d1 or args.d
		fields[2] = args.m1 or args.m
		fields[3] = args.a1 or args.a
		fields[4] = args.d2
		fields[5] = args.m2
		fields[6] = args.a2	
	else  -- Y si no sacarlos en el orden establecido
		for i = 1, 6 do
			fields[i] = args[i]
		end
	end
	
	local imax = 0
	for i = 1, 6 do
		fields[i] = stripToNil(fields[i])
		if fields[i] then
			imax = i
		end
		if getopt.omitZero and i % 3 ~= 1 then  -- omit zero months and days as unknown values but keep year 0 which is 1 BCE
			if isZero(fields[i]) then
				fields[i] = nil
				getopt.partial = true
			end
		end
	end
	local fix = getopt.fix and 'fix' or ''
	local partialText = getopt.partial and 'partial' or ''
	local dates = {}
	if getopt.useWikiDefault then
		if imax == 0 then
			fields[1] = 20
			fields[2] = 5
			fields [3] = 2001
			imax = 3
		end
	end
	if isNamed or imax >= 3 then
		local nrDates = getopt.single and 1 or 2
		if getopt.wantMixture then
			-- Cannot be partial since empty fields are set from current.
			local components = { 'year', 'month', 'day' }
			for i = 1, nrDates * 3 do
				fields[i] = fields[i] or currentDate[components[i > 3 and i - 3 or i]]
			end
			for i = 1, nrDates do
				local index = i == 1 and 1 or 4
				local d, m, y = fields[index], fields[index+1], fields[index+2]
				if (m == 2 or m == '2') and (d == 29 or d == '29') then
					-- Workaround error with following which attempt to use invalid date 2001-02-29.
					-- {{age_ymwd|year1=2001|year2=2004|month2=2|day2=29}}
					-- {{age_ymwd|year1=2001|month1=2|year2=2004|month2=1|day2=29}}
					-- TODO Get rid of wantMixture because even this ugly code does not handle
					-- 'Feb' or 'February' or 'feb' or 'february'.
					if not ((y % 4 == 0 and y % 100 ~= 0) or y % 400 == 0) then
						d = 28
					end
				end
				dates[i] = Date(y, m, d)
			end
		else
			-- If partial dates are allowed, accept
			--     year only, or
			--     year and month only
			-- Do not accept year and day without a month because that makes no sense
			-- (and because, for example, Date('partial', 2001, nil, 12) sets day = nil, not 12).
			for i = 1, nrDates do
				local index = i == 1 and 1 or 4
				local d, m, y = fields[index], fields[index+1], fields[index+2]
				if (getopt.partial and y and (m or not d)) or (y and m and d) then
					dates[i] = Date(fix, partialText, y, m, d)
				elseif not y and not m and not d then
					dates[i] = Date(flagCurrent())
				end
			end
		end
	else
		getopt.textdates = true  -- have parsed each date from a single text field
		dates[1] = Date(fix, partialText, flagCurrent(fields[1]))
		if not getopt.single then
			dates[2] = Date(fix, partialText, flagCurrent(fields[2]))
		end
	end
	if not dates[1] then
		return message(getopt.missing1 or 'mt-need-valid-ymd')
	end
	if getopt.single then
		return dates[1]
	end
	if not dates[2] then
		return message(getopt.missing2 or 'mt-need-valid-ymd2')
	end
	return dates[1], dates[2]
end

-- Return the result required by the specified template.
-- Can use sortable=x where x = on/table/off/debug in any supported template.
-- Some templates default to sortable=on but can be overridden.
local function ageGeneric(frame)
	
	local name = frame.args.template -- Extraer nombre de la plantilla que llama
	if not name then 
		return message('mt-template-x') -- Error si no se indica plantilla
	end
	local args = frame:getParent().args  -- Extraer los argumentos originales de la plantilla
	
	local specs = {
		age_days = {                -- {{age in days}} --Adaptada
			show = 'd',
			disp = 'disp_raw',
		},
		age_days_nts = {            -- {{age in days nts}} --Adaptada
			show = 'd',
			disp = 'disp_raw',
			format = 'format_commas',
			sortable = 'on',
		},
		duration_days = {           -- {{duration in days}}
			show = 'd',
			disp = 'disp_raw',
			duration = true,
		},
		duration_days_nts = {       -- {{duration in days nts}}
			show = 'd',
			disp = 'disp_raw',
			format = 'format_commas',
			sortable = 'on',
			duration = true,
		},
		age_full_years = {          -- {{age}}
			show = 'y',
			abbr = 'abbr_raw',
			flag = 'usesCurrent',
			omitZero = true,
			range = 'no',
		},
		age_full_years_nts = {      -- {{age nts}}
			show = 'y',
			abbr = 'abbr_raw',
			format = 'format_commas',
			sortable = 'on',
		},
		age_in_years = {            -- {{age in years}}
			show = 'y',
			abbr = 'abbr_raw',
			negative = 'error',
			range = 'dash',
		},
		age_in_years_nts = {        -- {{age in years nts}}
			show = 'y',
			abbr = 'abbr_raw',
			negative = 'error',
			range = 'dash',
			format = 'format_commas',
			sortable = 'on',
		},
		age_infant = {              -- {{age for infant}}
			-- Do not set show because special processing is done later.
			abbr = yes(args.abbr) and 'abbr_infant' or 'abbr_off',
			disp = 'disp_age',
			sep = 'sep_space',
			sortable = 'on',
		},
		age_m = {                   -- {{age in months}}
			show = 'm',
			disp = 'disp_raw',
		},
		age_w = {                   -- {{age in weeks}}
			show = 'w',
			disp = 'disp_raw',
		},
		age_wd = {                  -- {{age in weeks and days}}
			show = 'wd',
		},
		age_yd = {                  -- {{age in years and days}} --Adaptada
			show = 'yd',
			format = 'format_commas',
			sep = args.sep == 'comma' and 'sep_comma' or 'sep_and',
			ignoreZero = stripToNil(args['ceros']) == 'yes' and false or true,
			useWikiDefault = true,
		},
		age_yd_nts = {              -- {{age in years and days nts}} --Adaptada
			show = 'yd',
			format = 'format_commas',
			sep = args.sep == 'comma' and 'sep_comma' or 'sep_and',
			sortable = 'on',
			ignoreZero = stripToNil(args['ceros']) == 'yes' and false or true,
		},
		age_ym = {                  -- {{age in years and months}}
			show = 'ym',
			sep = args.sep == 'comma' and 'sep_comma' or 'sep_and',
			ignoreZero = stripToNil(args['ceros']) == 'yes' and false or true,
		},
		age_ymd = {                 -- {{age in years, months and days}}
			show = 'ymd',
			range = true,
			ignoreZero = stripToNil(args['ceros']) == 'yes' and false or true,
			useWikiDefault = true,
		},
		age_ymwd = {                -- {{age in years, months, weeks and days}}
			show = 'ymwd',
		},
	}
	local spec = specs[name]  -- Seleccionar la especificación de la plantilla elegida
	if not spec then
		return message('mt-template-bad-name')  -- Si no se encuentra error
	end
	
	if name == 'age_days' then
		local su = stripToNil(args['show unit'])  -- Extraer el parámetro 'show unit'
		if su then
			if su == 'abbr' or su == 'full' then  -- Seleccionar el tipo de unidad a mostrar
				spec.disp = nil
				spec.abbr = su == 'abbr' and 'abbr_on' or nil   -- Si es abreviada se marca en la spec
			end
		end
	end
	
	local ceros = stripToNil(args.ceros)
	if ceros then
		if ceros == 'yes' then
			spec.ignoreZero = false
		else
			spec.ignoreZero = true
		end	
	end
	
	
	-- Para mantener compatibilidad con {{edad}} antigua y el parámetro 'años'
	
	local anyos = args['años']
	if name == 'age_full_years' then 
		anyos = anyos ~= nil and anyos or '&nbsp;años'
	else
		anyos = ''
	end
	
	-- Para mantener compatibilidad con las que utilizan por defecto la de la wikipedia
	local useWikiDefault = ''
	if spec.useWikiDefault then
		useWikiDefault = spec.useWikiDefault
	else
		useWikiDefault = false
	end
	
	-- Gestión de rangos en fechas parciales o relleno cuando no se quieren rangos
	local partial, autofill
	local range = stripToNil(args.range) or spec.range
	if range then  
		-- Si se usan fechas parciales y la edad puede ser 11 o 12 años.
		-- "|range=" (vacío) no tiene ningún efecto (se usa lo que haya en spec).
		-- "|range=yes" or spec.range == true utiliza range = true (retorna "11 or 12")
		-- "|range=dash" or spec.range == 'dash' utiliza range = 'dash' (retorna "11–12").
		-- "|range=no" or spec.range == 'no' utiliza range = nil y rellena las fechas en el  diff (retorna "12").
		--     ("on" equivale a "yes", y "off" equivale a "no").
		-- "|range=OTHER" utiliza range = nil y rechaza fechas parciales.
		range = ({ dash = 'dash', off = 'no', no = 'no', [true] = true })[range] or yes(range)
		if range then
			partial = true  -- Aceptar fechas parciales con posible rango de edades al final
			if range == 'no' then
				autofill = true  -- El día/mes que falten en la segunda fecha se rellenarán con datos de la primera o 1
				range = nil
			end
		end
	end
	
	local getopt = {
		fix = yes(args.fix),  -- Si se usa fix=on se arreglan las unidades demasiado grandes -> 26 horas son 1 día y 2 horas
		flag = stripToNil(args.flag) or spec.flag,
		omitZero = spec.omitZero,
		partial = partial,
		wantMixture = spec.wantMixture,
		useWikiDefault = useWikiDefault,
	}
	
	local date1, date2 = getDates(frame, getopt)
	if type(date1) == 'string' then
		return date1
	end
	local format = stripToNil(args.format)
	local spell = spellOptions[format]
	if format then
		format = 'format_' .. format
	elseif name == 'age_days' and getopt.textdates then
		format = 'format_commas'
	end
	local parms = {
		diff = date2:subtract(date1, { fill = autofill }),
		wantDuration = spec.duration or yes(args.duration),
		range = range,
		wantSc = yes(args.sc),
		show = args.show == 'hide' and 'hide' or spec.show,
		abbr = spec.abbr,
		disp = spec.disp,
		extra = makeExtra(args, getopt.usesCurrent and format ~= 'format_raw'),
		format = format or spec.format,
		round = yes(args.round),
		sep = spec.sep,
		sortable = translateParameters.sortable[args.sortable or spec.sortable],
		spell = spell,
		ignoreZero = spec.ignoreZero, --spec.ignoreZero and true or false 
	}
	if (spec.negative or frame.args.negative) == 'error' and parms.diff.isnegative then
		return message('mt-date-wrong-order')
	end
	return from_en(dateDifference(parms) .. anyos)
end

-- Implement [[Template:Birth date and age]].
local function bda(frame)
	local args = frame:getParent().args
	local options = {
		missing1 = 'mt-need-valid-bd',
		noMissing = true,
		single = true,
	}
	local date = getDates(frame, options)
	if type(date) == 'string' then
		return date  -- error text
	end
	local Date = getExports(frame)
	local diff = Date('currentdate') - date
	if diff.isnegative or diff.years > 150 then
		return message('mt-invalid-bd-age')
	end
	local disp, show = 'disp_raw', 'y'
	if diff.years < 2 then
		disp = 'disp_age'
		if diff.years == 0 and diff.months == 0 then
			show = 'd'
		else
			show = 'm'
		end
	end
	local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
	local result = '(<span class="bday">%-Y-%m-%d</span>) </span>' ..
					(df and '%-d %B %-Y' or '%B %-d, %-Y')
	result = from_en('<span style="display:none"> ' ..
		date:text(result) ..
		'<span class="noprint ForceAgeToShow"> ' ..
		mtext['txt-age'] ..
		dateDifference({
			diff = diff,
			show = show,
			abbr = 'abbr_off',
			disp = disp,
			sep = 'sep_space',
		}) ..
		')</span>')
	local warnings = tonumber(frame.args.warnings)
	if warnings and warnings > 0 then
		local good = {
			df = true,
			mf = true,
			day = true,
			day1 = true,
			month = true,
			month1 = true,
			year = true,
			year1 = true,
		}
		local invalid
		local imax = options.textdates and 1 or 3
		for k, _ in pairs(args) do
			if type(k) == 'number' then
				if k > imax then
					invalid = tostring(k)
					break
				end
			else
				if not good[k] then
					invalid = k
					break
				end
			end
		end
		if invalid then
			result = result .. message({ 'mt-bad-param1', invalid }, 'warning')
		end
	end
	return result
end

-- Implement [[Template:Death date and age]].
local function dda(frame)
	local args = frame:getParent().args
	local options = {
		missing1 = 'mt-need-valid-dd',
		missing2 = 'mt-need-valid-bd2',
		noMissing = true,
		partial = true,
	}
	local date1, date2 = getDates(frame, options)
	if type(date1) == 'string' then
		return date1
	end
	local diff = date1 - date2
	if diff.isnegative then
		return message('mt-dd-wrong-order')
	end
	local Date = getExports(frame)
	local today = Date('currentdate') + 1  -- one day in future allows for timezones
	if date1 > today then
		return message('mt-dd-future')
	end
	local years
	if diff.partial then
		years = diff.partial.years
		years = type(years) == 'table' and years[2] or years
	else
		years = diff.years
	end
	if years > 150 then
		return message('mt-invalid-dates-age')
	end
	local df = stripToNil(args.df)  -- day first (dmy); default is month first (mdy)
	local result
	if date1.day then  -- y, m, d known
		result = (df and
			'%-d %B %-Y' or
			'%B %-d, %-Y') ..
			'<span style="display:none">(%-Y-%m-%d)</span>'
	elseif date1.month then  -- y, m known; d unknown
		result =
			'%B %-Y' ..
			'<span style="display:none">(%-Y-%m-00)</span>'
	else  -- y known; m, d unknown
		result =
			'%-Y' ..
			'<span style="display:none">(%-Y-00-00)</span>'
	end
	result = from_en(date1:text(result) ..
		mtext['txt-aged'] ..
		dateDifference({
			diff = diff,
			show = 'y',
			abbr = 'abbr_off',
			disp = 'disp_raw',
			range = 'dash',
			sep = 'sep_space',
		}) ..
		')')
	local warnings = tonumber(frame.args.warnings)
	if warnings and warnings > 0 then
		local good = {
			df = true,
			mf = true,
		}
		local invalid
		local imax = options.textdates and 2 or 6
		for k, _ in pairs(args) do
			if type(k) == 'number' then
				if k > imax then
					invalid = tostring(k)
					break
				end
			else
				if not good[k] then
					invalid = k
					break
				end
			end
		end
		if invalid then
			result = result .. message({ 'mt-bad-param1', invalid }, 'warning')
		end
	end
	return result
end

-- Implement [[Template:Gregorian serial date]].
-- Return Gregorian serial date of the given date, or the current date.
-- The returned value is negative for dates before 1 January 1 AD
-- despite the fact that GSD is not defined for such dates.
local function dateToGsd(frame)
	local date = getDates(frame, { wantMixture=true, single=true })
	if type(date) == 'string' then
		return date
	end
	return tostring(date.gsd)
end

-- Return formatted date from a Julian date.
-- The result includes a time if the input includes a fraction.
-- The word 'Julian' is accepted for the Julian calendar.
local function jdToDate(frame)
	local Date = getExports(frame)
	local args = frame:getParent().args
	local date = Date('juliandate', args[1], args[2])
	if date then
		return from_en(date:text())
	end
	return message('mt-need-jdn')
end

-- Return Julian date (a number) from a date which may include a time,
-- or the current date ('currentdate') or current date and time ('currentdatetime').
-- The word 'Julian' is accepted for the Julian calendar.
local function dateToJd(frame)
	local Date = getExports(frame)
	local args = frame:getParent().args
	local date = Date(args[1], args[2], args[3], args[4], args[5], args[6], args[7])
	if date then
		return tostring(date.jd)
	end
	return message('mt-need-valid-ymd-current')
end

-- Implement [[Template:Time interval]].
-- There are two positional arguments: date1, date2.
-- The default for each is the current date and time.
-- Result is date2 - date1 formatted.
local function timeInterval(frame)
	local Date = getExports(frame)
	local args = frame:getParent().args
	local parms = {
		extra = makeExtra(args),
		wantDuration = yes(args.duration),
		range = yes(args.range) or (args.range == 'dash' and 'dash' or nil),
		wantSc = yes(args.sc),
	}
	local fix = yes(args.fix) and 'fix' or ''
	local date1 = Date(fix, 'partial', stripToNil(args[1]) or 'currentdatetime')
	if not date1 then
		return message('mt-invalid-start')
	end
	local date2 = Date(fix, 'partial', stripToNil(args[2]) or 'currentdatetime')
	if not date2 then
		return message('mt-invalid-end')
	end
	parms.diff = date2 - date1
	for argname, translate in pairs(translateParameters) do
		local parm = stripToNil(args[argname])
		if parm then
			parm = translate[parm]
			if parm == nil then  -- test for nil because false is a valid setting
				return message({ 'mt-bad-param2', argname, args[argname] })
			end
			parms[argname] = parm
		end
	end
	if parms.round then
		local round = parms.round
		local show = parms.show
		if round ~= 'on' then
			if show then
				if show.id ~= round then
					return message({ 'mt-conflicting-show', args.show, args.round })
				end
			else
				parms.show = translateParameters.show[round]
			end
		end
		parms.round = true
	end
	return from_en(dateDifference(parms))
end

return {
	age_generic = ageGeneric,           -- can emulate several age templates
	birth_date_and_age = bda,           -- Template:Birth_date_and_age
	death_date_and_age = dda,           -- Template:Death_date_and_age
	gsd = dateToGsd,                    -- Template:Gregorian_serial_date
	extract = dateExtract,              -- Template:Extract
	jd_to_date = jdToDate,              -- Template:?
	JULIANDAY = dateToJd,               -- Template:JULIANDAY
	time_interval = timeInterval,       -- Template:Time_interval
}