Module:Date

De USAC-CGT
Aller à la navigation Aller à la recherche

La documentation pour ce module peut être créée à Module:Date/doc

-- luacheck: globals mw, no max line length

local fun = {}

local Outils = require 'Module:Outils'
local Duree = require 'Module:Durée'
-- chargement de la base de données répertoriant certaines pages existant ou n'existant pas pour éviter les "ifexist".
local dataLiens
local success, resultat = pcall ( mw.loadData, 'Module:Date/Data' )
if success then
	dataLiens = resultat
else
	-- protection au cas où le sous-module serait mal modifié
	dataLiens = { [''] = { mois = { aucun = 1000, tous = { 1773, 2014 } }, } }
end

-- nettoie un paramètre non nommé (vire les espaces au début et à la fin)
-- retourne nil si le texte est vide ou n'est pas du texte. Attention c'est important pour les fonctions qui l'utilisent.
local trim = Outils.trim

-- Fonction destinée à mettre la première lettre du mois en majuscule :
-- utilisation de string car aucun mois ne commence par une lettre non ascii en français ou anglais.
local function ucfirst( str )
	return str:sub( 1, 1 ):upper() .. str:sub( 2 )
end

local modelePremier = '<abbr class="abbr" title="premier">1<sup>er</sup></abbr>'


-- liste des mois, écriture exacte et alias, en minuscule
local listeMois = {
	{ num = 1,  nJour = 31, abrev = 'janv.',  nom = 'janvier', alias = { 'jan.', 'janv.', 'jan', 'janv', 'january' } },
	{ num = 2,  nJour = 29, abrev = 'fév.',   nom = 'février', alias = { 'fevrier', 'fev.', 'fev', 'fév.', 'fév', 'févr', 'févr.', 'february', 'feb', 'feb.' } },
	{ num = 3,  nJour = 31, abrev = 'mars',   nom = 'mars', alias = { 'mar.', 'mar', 'march' } },
	{ num = 4,  nJour = 30, abrev = 'avr.',   nom = 'avril', alias = { 'avr.', 'avr', 'apr', 'april'} },
	{ num = 5,  nJour = 31, abrev = 'mai',    nom = 'mai', alias = { 'may' } },
	{ num = 6,  nJour = 30, abrev = 'juin',   nom = 'juin', alias = { 'jun', 'june' } },
	{ num = 7,  nJour = 31, abrev = 'juill.', nom = 'juillet', alias = { 'juil.', 'juil', 'juill.', 'juill', 'jul', 'july' } },
	{ num = 8,  nJour = 31, abrev = 'août',   nom = 'août', alias = { 'aoû', 'aug', 'august' } },
	{ num = 9,  nJour = 30, abrev = 'sept.',  nom = 'septembre', alias = { 'sept.', 'sept', 'sep.', 'sep', 'september' } },
	{ num = 10, nJour = 31, abrev = 'oct.',   nom = 'octobre', alias = { 'oct.', 'oct', 'october' } },
	{ num = 11, nJour = 30, abrev = 'nov.',   nom = 'novembre', alias = { 'nov.', 'nov', 'november' } },
	{ num = 12, nJour = 31, abrev = 'déc.',   nom = 'décembre', alias = { 'decembre', 'déc.', 'dec.', 'dec', 'déc', 'december' } },
	aout = { num = 8, nJour = 31, abrev = 'aout', nom = 'aout', alias = { 'aou' } },
}

-- ajoute les noms, abréviations et alias en tant que clés de listeMois
for i = 1, 12 do
	local mois = listeMois[i]
	listeMois[tostring( i )] = mois
	if i < 10 then
		listeMois['0' .. i] = mois
	end
	listeMois[mois.nom] = mois
	listeMois[mois.abrev] = mois
	for j = 1, #mois.alias do
		listeMois[mois.alias[j]] = mois
	end
end
for i = 1, #listeMois.aout.alias do
	listeMois[listeMois.aout.alias[i]] = listeMois.aout
end

local liste_saisons = {
	{ 'printemps', 'spring', },
	{ 'été', 'summer', },
	{ 'automne', 'autumn', },
	{ 'hiver', 'winter', },
}

-- à partir d'un nom de saison (en français ou en anglais),
-- retourne son nom canonique (exemple : "été")
-- si non reconnu, retourne nil
function fun.determinationSaison( saison )
	local s = trim( saison )
	if s then
		s = mw.ustring.lower( s )
		for i = 1, 4 do
			for j = 1, #liste_saisons[i] do
				if s == liste_saisons[i][j] then
					return liste_saisons[i][1]
				end
			end
		end
	end
end

---
-- à partir d'un nom de mois (en français ou en anglais), de son numéro ou d'une abréviation,
-- retourne son nom canonique (exemple : "juin") et son numéro (exemple : 6)
-- si non reconnu, retourne nil, nil
function fun.determinationMois( mois )
	local result

	local num = tonumber( mois )
	if num then
		result = listeMois[num]
	else
		local str = trim( mois )
		if str then
			result = listeMois[str]
			if not result then
				result = listeMois[mw.ustring.lower( str )]
			end
		end
	end

	if result then
		return result.nom, result.num
	else
		return nil, nil
	end
end


-- fonction interne à modeleDate, pour déterminer si on peut se passer de faire un ifexist
local function existDate( dataQualificatif, annee, mois )
	local data
	if mois then
		data = dataQualificatif.mois
	else
		data = dataQualificatif.annee
	end
	if type( data ) ~= 'table' then
		-- si data n'existe pas c'est que l'on considère qu'il n'y a pas de lien.
		return
	end
	-- le qualificatif est remplacé par celui de la base de données, ce qui permet des alias.
	local lien = annee
	if dataQualificatif.qualificatif ~= '' then
		lien = lien .. ' ' .. dataQualificatif.qualificatif
	end
	local seul = annee
	if mois then
		lien = mois .. ' ' .. lien
		seul = ucfirst( mois ) .. ' ' .. annee
	end
	local aucun = tonumber( data.aucun )
	if aucun and annee <= aucun then
		-- si l'année est dans la partie 'aucun' on teste s'il y a malgré tout un lien isolé
		if type( data.seul ) == 'table' then
			for i, v in ipairs( data.seul ) do
				if seul == v or seul == tonumber( v ) then
					return lien
				end
			end
		end
		-- partie aucun et pas de lien => nil
		return nil
	elseif type( data.tous ) == 'table' then
		local tous1, tous2 = tonumber( data.tous[1] ), tonumber( data.tous[2] )
		if tous1 and tous2 and annee >= tous1 and annee <= tous2 then
			-- l'année est dans la partie 'tous' donc on retourne le lien
			return lien
		end
	end
	-- l'année n'est ni dans la partie aucun, ni dans la partie tous donc il faut tester si la page existe.
	local cibleLien = mw.title.new( lien )
	if cibleLien and cibleLien.exists then
		return lien
	end
end

---
-- Supprime le jour de la semaine, et "le" avant une date
function fun.nettoyageJour( jour )
	if type( jour ) == 'string' then
		local nomJour = { '[Ll]undi', '[Mm]ardi', '[Mm]ercredi', '[Jj]eudi', '[Vv]endredi',
			'[Ss]amedi', '[Dd]imanche', '^ *[Ll]e' }
		local premier = { '<abbr class="abbr ?" title="[Pp]remier" ?>1<sup>er</sup></abbr>', '1<sup>er</sup>', '1er' }
		for i = 1, #nomJour do
			jour = jour:gsub( nomJour[i], '' )
		end
		for i = 1, #premier do
			jour = jour:gsub( premier[i], '1' )
		end
		jour = trim( jour )
	end
	return jour
end

---
-- Sépare une chaine date en une table contenant les champs jour, mois et annee.
-- la date doit contenir le mois.
function fun.separationJourMoisAnnee( date )
	date = trim( date )
	if date then
		local function erreur( periode, valeur )
			return false, '<span class="error">' .. periode .. ' invalide (' .. valeur .. ')</span>'
		end

		local dateAvantCleanup = date
		local jour, mois, annee, masquerMois, masquerAnnee, separateur

		-- variable pour construire les regex
		local j = '([0-3]?%d)'                            -- jour
		local m = '([01]?%d)'                             -- mois numérique
		local mmm = '([^%s%p%d]+[.]?)'                    -- mois en toute lettre
		local mmm2 = '([^%s%p%d]+[.]?[-/][^%s%p%d]+[.]?)' -- mois-mois en toute lettre
		local aj = '(%-?%d+)'                             -- année ou jour
		local s = '[ ./-]+'                               -- séparateur simple
		local sep = '([ ./-]+)'                           -- séparateur avec capture, pour le détecter deux fois
		local moins = '(%-?)'                             -- signe moins pour signifier qu'il ne faut pas afficher cette donnée

		date = fun.nettoyageJour( date )
		if date == nil then
			return erreur( 'Date', dateAvantCleanup )
		end
		-- suppression catégorie, liens, balises
		date = mw.ustring.gsub( date, '%[%[[Cc]at[ée]gor[yi]e?:.-%]%]', '' )
		date = date	:gsub( '%b<>', '' )
					:gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
		-- suppression des espaces insécables
					-- nbsp
					:gsub( '\194\160', ' ' )
					:gsub( '&nbsp;', ' ' )
					:gsub( '&#160;', ' ' )
					-- narrow nbsp
					:gsub( '\226\128\175', ' ' )
					:gsub( '&#8239;', ' ' )
					-- thin space
					:gsub( '\226\128\137', ' ' )
					:gsub( '&thinsp;', ' ' )
					:gsub( '&#8201;', ' ' )
					-- simple space
					:gsub( '&#32;', ' ' )
					-- plusieurs espaces
					:gsub( ' +', ' ' )
		-- réduction av. J-C pour simplifier un peu les regex :
					:gsub( '(%d+) ?[Aa][Vv]%.? ?[Jj][ .-]*[Cc]%.?', '-%1' )
		-- suppression de l'heure dans les dates ISO
					:gsub( '^+?([%d-]*%d%d%-%d%d)T%d%d[%d:,.+-]*Z?$' , '%1')

		-- test année seule
		if date:match( '^'..aj..'$' ) then
			annee = date:match( '^'..aj..'$' )
		elseif date:match( '^'..aj..s..aj..moins..'$' ) then
			-- jj/mm, mm/aaaa ou aaaa/mm
			local a, separateur, b, sb = date:match( '^'..aj..sep..aj..moins..'$' )
			a, b = tonumber( a ), tonumber( b )
			if separateur:match( '^.+%-$' ) then
				-- probablement mm/-aaaa, année av.JC
				b = 0 - b
			end
			if  a > 12 and ( b < 1 or b > 31 ) or
				b > 12 and ( a < 1 or a > 31 ) then
				return erreur( 'Date', dateAvantCleanup )
			elseif b < 1 or b > 31 then
				mois, annee, masquerAnnee = a, b, sb
			elseif a < 1 or a > 31 then
				annee, mois = a, b
			elseif b > 12 then
				return erreur( 'Mois', b )
			else
				jour, mois, masquerMois = a, b, sb
			end
		elseif date:match( '^'..aj..sep..m..moins..'%2'..aj..moins..'$' ) then
			-- jj/mm/aaaa ou aaaa/mm/jj
			jour, separateur, mois, masquerMois, annee, masquerAnnee = date:match( '^'..aj..sep..m..moins..'%2'..aj..moins..'$' )
			if separateur == '-' and masquerMois == '-' and masquerAnnee == '' and tonumber( annee ) > 0 then
				-- date au format jj-mm--aaaa type 17-06--44 pour 17 juin 44 av. JC
				masquerMois = nil
				annee = 0 - annee
			end
		elseif date:match( '^'..j..sep..mmm..moins..'%2'..aj..moins..'$' ) then
			-- jj mmm aaaa
			jour, separateur, mois, masquerMois, annee, masquerAnnee = date:match( '^'..j..sep..mmm..moins..'%2'..aj..moins..'$' )
		elseif date:match( '^'..mmm..s..aj..moins..'$' ) then
			-- mmm aaaa
			mois, separateur, annee, masquerAnnee = date:match( '^'..mmm..sep..aj..moins..'$' )
			if separateur:match( '^.+%-$' ) then
				annee = '-' .. annee
			end
		elseif date:match( '^'..mmm2..s..aj..moins..'$' ) then
			-- mmm-mmm aaaa
			mois, separateur, annee, masquerAnnee = date:match( '^'..mmm2..sep..aj..moins..'$' )
			if separateur:match( '^.+%-$' ) then
				annee = '-' .. annee
			end
		elseif date:match( '^'..j..s..mmm..moins..'$' ) then
			-- jj mmm
			jour, mois, masquerMois = date:match( '^'..j..s..mmm..moins..'$' )
		elseif date:match( '^'..mmm..s..j..', ?'..aj..'$') then
			-- mmm jj, aaaa (format anglo-saxon)
			mois, jour, annee = date:match( '^'..mmm..s..j..', ?'..aj..'$')
		elseif date:match( '^'..mmm..'$' ) then
			mois = date
		else
			return erreur( 'Date', dateAvantCleanup )
		end
		local jn, an = tonumber( jour ), tonumber( annee )
		if jn and an and ( jn > 31 or jn < 0 or #jour >= 3 ) and an <= 31 then
			-- cas notamment des date ISO 2015-06-17, -0044-06-17 et -0002-06-17
			-- inversion du jour et de l'année
			local temp = annee
			annee = jour
			jour = temp
		end

		return fun.validationJourMoisAnnee{
			jour, mois, annee,
			masquerAnnee = trim( masquerAnnee ) and true or nil,
			masquerMois = ( trim( masquerAnnee ) or not annee ) and trim( masquerMois ) and true or nil,
			-- or nil sert juste à éviter de trainer une valeur false dans tous les tests unitaires.
		}
	else
		return true, {}
	end
end


---
-- validationJourMoisAnnee vérifie que les paramètres correspondent à une date valide.
-- la date peut être dans les paramètres 1 à 3, ou dans des paramètres jour, mois et annee.
-- La fonction retourne true suivi d'une table avec la date en paramètres nommés (sans accent sur année)
-- ou false suivi d'un message d'erreur.
function fun.validationJourMoisAnnee( frame )
	local args = Outils.extractArgs( frame )
	local jour, mois, numMois, annee
	local bjour = args[1] or args['jour'] or ''
	local bmois = tostring( args[2] or args['mois'] or '' )
	local bannee = args[3] or args['annee'] or args['année'] or ''

	local function erreur( periode, valeur )
		return false, '<span class="error">' .. periode .. ' invalide (' .. valeur .. ')</span>'
	end

	-- on traite l'année
	if Outils.notEmpty( bannee ) then
		annee = tonumber( bannee )
		if annee == nil and type( bannee ) == 'string' then
			-- test si l'année contient av. J.-C.
			annee = bannee:upper():match( '^(%d+) ?[Aa][Vv]%.? ?[Jj][ .-]*[Cc]%.?' )
			annee = tonumber( annee )
			if annee then
				annee = 0 - annee
			else
				return erreur( 'Année', bannee )
			end
		elseif annee == 0 then
			return erreur( 'Année', 0 )
		end
	else
		annee = nil
	end

	-- on traite le mois
	if Outils.notEmpty( bmois ) then
		mois, numMois = fun.determinationMois( bmois )
		if mois == nil then
			mois = fun.determinationSaison( bmois )
			if mois == nil then
				local mois1, sep, mois2 = bmois:match( '^([^%s%p%d]+[.]?)([-/])([^%s%p%d]+[.]?)$' )
				if mois1 then
					mois1 = fun.determinationMois( mois1 )
					mois2 = fun.determinationMois( mois2 )
					if mois1 == nil or mois2 == nil then
						return erreur( 'Mois', bmois )
					end
					mois = mois1 .. sep .. mois2
				else
					return erreur( 'Mois', bmois )
				end
			end
		end
		-- on traite le jour si présent
		if Outils.notEmpty( bjour ) then
			if not numMois then
				erreur( 'Date', 'jour avec saison ou plusieurs mois' )
			end
			jour = tonumber( bjour )
			if jour == nil then
				jour = tonumber( fun.nettoyageJour( bjour ) )
			end
			if jour == nil then
				return erreur( 'Jour', bjour )
			end
			-- on valide que le jour est correct
			if jour < 1 or jour > 31 then
				return erreur( 'Jour', bjour )
			elseif jour > listeMois[numMois].nJour then
				return erreur( 'Jour', bjour .. ' ' .. mois )
			elseif jour == 29 and numMois == 2 and annee and ( math.fmod( annee, 4 ) ~= 0 ) then
				-- l'année bisextile sur les siècles est toujours acceptée pour être compatible avec les dates juliennes.
				return erreur( 'Jour', '29 février ' .. annee )
			end
		else
			-- S'il n'y a pas de jour on regarde si la première lettre du mois est en majuscule
			if bmois:match( '^%u' ) then
				-- oui, on passe la première lettre en majuscule
				mois = ucfirst( mois )
			end
			-- s'il n'y a pas d'année non plus on retourne le mois simple
		end
	else
		-- on teste le jour si présent
		if Outils.notEmpty( bjour ) then
			if annee then
				return erreur( 'Mois', 'absent' )
			else
				bjour = fun.nettoyageJour( bjour )
				jour = tonumber( bjour )
				if jour then
					if jour > 31 or jour < 1 then
						annee = jour
						jour = nil
					else
						return erreur( 'Date', 'jour seul : ' .. bjour )
					end
				else
					return erreur( 'Jour', bjour )
				end
			end
		end
	end

	-- vérification de l'absence d'un décalage
	if annee and annee < 13 and annee > 0 and not jour and ( tonumber( bmois ) or ( not mois and tonumber( args[4] ) ) ) then
		return false, '<span class="error">année improbable (' .. annee .. ')</span>'
	end

	local resultat = {
		jour = jour,
		mois = mois,
		numMois = numMois,
		annee = annee,
		masquerAnnee = args.masquerAnnee,
		masquerMois = args.masquerMois,
	}
	return true, resultat
end


---
-- émule le modèle {{m|Date}}.
-- Paramètres :
--		1 : jour (numéro ou "1er") ou la date complète
--		2 : mois (en toutes lettres) ou spécialité de l'année
--		3 : année (nombre)
--		4 : spécialité de l'année
--		julien : date dans le calendrier julien
--		compact : affiche le mois sous forme d'abréviation
--		avJC : non pour désactiver l'affichage de « av. J.-C. » pour les dates négatives
--		âge : ajoute la durée depuis cette date
--		agePrefix : préfixe pour l'age, 'à ' par défaut pour les décès
--		liens : active les liens par défaut
--		nolinks : ne met pas de lien sur la date (a précédence sur le paramètre "liens")
--		afficherErreurs : en cas d'erreur, si défini à "non" ne retourne pas un message d'erreur, mais le 1er argument inchangé
--		categoriserErreurs : en cas d'erreur, si défini à "non" ne catégorise pas ; peut aussi être défini avec une catégorie à utiliser à la place de celle par défaut
--		naissance : ajoute la class "bday"
--		mort : ajoute la class "dday"
function fun.modeleDate( frame )
	local Yesno = require 'Module:Yesno'

	local args = Outils.extractArgs( frame )
	local resultat

	local dateNaissanceMort

	-- analyse des paramètres non nommés (ou paramètres de la date jour, mois, annee)
	local test, params
	local arg1, arg2, arg3 = fun.nettoyageJour( args[1] ), trim( args[2] ), trim( args[3] )
	if type( arg1 ) == 'string' and arg3 == nil and ( arg1:match( '[^ ./-][ ./-]+[^ ./-]' ) or arg2 == nil or dataLiens[arg2] or mw.ustring.match( arg2, '%a %a' ) ) then
		-- la date est dans le premier paramètre
		test, params = fun.separationJourMoisAnnee( arg1 )
		if test then
			dateNaissanceMort = trim( arg2 )
			if dataLiens[trim( arg2 )] then
				params.qualificatif = trim( arg2 )
			end
		end
	elseif type( arg1 ) == 'string' and type( arg2 ) == 'string' and arg3 ~= nil and ( arg1:match( '[^ ./-][ ./-]+[^ ./-]' ) or dataLiens[arg3] or mw.ustring.match( arg3, '%a %a' ) ) then
		-- la date est dans le premier paramètre
		test, params = fun.separationJourMoisAnnee( arg1 )
		if test then
			dateNaissanceMort = trim( arg2 )
			if dataLiens[trim( arg3 )] then
				params.qualificatif = trim( arg3 )
			end
		end
	else
		local function masquerParam( p )
			-- sépare le signe moins final éventuel signifiant que le paramètre ne doit pas être affiché.
			if type( p ) ~= 'string' then
				return p, nil
			end
			local value, mask = p:match( '^%s*(.-)(%-?)%s*$' )
			return value, ( mask == '-' or nil )
		end
		local cleanArgs = { arg1 or args.jour }
		cleanArgs[2], cleanArgs.masquerMois = masquerParam( args[2] or args.mois )
		cleanArgs[3], cleanArgs.masquerAnnee = masquerParam( args[3] or args.annee or args['année'] )

		-- Si les paramètres ont été envoyés directement, ils ont précédence
		if args.masquerMois then cleanArgs.masquerMois = args.masquerMois end
		if args.masquerAnnee then cleanArgs.masquerAnnee = args.masquerAnnee end

		test, params = fun.validationJourMoisAnnee( cleanArgs )
		if test and dataLiens[trim( args[4] )] then
			params.qualificatif = trim( args[4] )
		end
	end

	-- analyse des paramètres nommés
	if test then
		params.agePrefix = args.agePrefix
		if args.qualificatif and dataLiens[args.qualificatif] then
			params.qualificatif = args.qualificatif
		end

		-- julien peut avoir trois valeurs : inactif, format standard (true), format court
		params.julien = Yesno( args.julien, 'court', false )
		params.avJC = Yesno( args.avJC )

		if args['républicain'] and args['républicain'] ~= '' then
			if args['républicain'] == 'liens' then
				params.republicain = 'liens'
			else
				params.republicain = Yesno( args['républicain'], false )
			end
		else
			params.republicain = false
		end
		if args.dateNaissanceMort and args.dateNaissanceMort ~= '' then
			dateNaissanceMort = args.dateNaissanceMort
		elseif args['dateNaissanceÉvénement'] and args['dateNaissanceÉvénement'] ~= '' then
			dateNaissanceMort = args['dateNaissanceÉvénement']
		end
		if dateNaissanceMort then
			local testNaissanceMort, paramsNaissanceMort = fun.separationJourMoisAnnee( dateNaissanceMort )
			if testNaissanceMort then
				params.anneeNaissanceMort, params.moisNaissanceMort, params.numMoisNaissanceMort, params.jourNaissanceMort = paramsNaissanceMort.annee, paramsNaissanceMort.mois, paramsNaissanceMort.numMois, paramsNaissanceMort.jour
			end
		end

		local listeParam = {
			age = 'âge',
			['âge'] = 'âge',
			naissance = 'naissance',
			mort = 'mort',
			['événement'] = 'événement',
			evenement = 'evenement',
			['décès'] = 'mort',
			apJC = 'apJC',
			nolinks = 'nolinks',
			compact = 'compact',
			compacte = 'compact',
		}
		for n, v in pairs( listeParam ) do
			params[v] = params[v] or Yesno( args[n], true, false ) or nil
		end

		if not params.nolinks then
			local liens = Yesno( args.liens )
			if liens == nil then
				-- liens actifs par défaut si qualificatif
				liens = params.qualificatif and params.qualificatif ~= "" and true or false
			end
			params.nolinks = not liens
		end

		-- sortie pour les tests unitaire, ou pour débugger
		if args.debug then
			return params
		end

		resultat = fun._modeleDate( params )

	else
		local yn_afficherErreurs = Yesno( args.afficherErreurs )
		if yn_afficherErreurs == nil or yn_afficherErreurs == true then
			resultat = params
		else
			resultat = args[1]
		end

		local currentTitle = mw.title.getCurrentTitle()

		if currentTitle:inNamespaces( 0, 4, 10, 14, 100 )
		and not Outils.notEmpty( args.nocat )
		and not currentTitle.prefixedText:match( '^Modèle:.+/Test$' ) then
			local categorie
			local yn_categoriserErreurs = Yesno( args.categoriserErreurs, 'custom', true )
			if yn_categoriserErreurs == nil or yn_categoriserErreurs == true then
				categorie = '[[Catégorie:Page utilisant le modèle date avec une syntaxe erronée]]'
			elseif yn_categoriserErreurs == false then
				categorie = ''
			else
				local nomCategorie = args.categoriserErreurs
					:gsub( '^%[%[', '' )
					:gsub( '%]%]$', '' )
					:gsub( '^:?[Cc]atégorie:', '' )
					:gsub( '^:?[Cc]atégory:', '' )
				categorie = '[[Catégorie:' .. nomCategorie .. ']]'
			end
			resultat = resultat .. categorie
		end
	end

	return resultat or ''
end

function fun._modeleDate( args )
	local annee, mois, numMois, jour = args.annee, args.mois, args.numMois, args.jour
	local qualificatif = args.qualificatif

	if ( annee or mois or jour ) == nil then
		return
	end

	-- on traite l'âge, naissance et mort
	local agePrefix = args.agePrefix
	local age = args['âge'] and fun.age( annee, numMois, jour )
	local naissance = args.naissance
	local mort = args.mort
	local evenement = args['événement'] or args.evenement
	if mort and args.anneeNaissanceMort then
		age = fun.age( args.anneeNaissanceMort, args.numMoisNaissanceMort, args.jourNaissanceMort, annee, numMois, jour )
		agePrefix = agePrefix or 'à ' -- faut-il mettre \194\160 ?
	elseif evenement and args.anneeNaissanceMort then
		if naissance then
			age = fun.age( annee, numMois, jour, args.anneeNaissanceMort, args.numMoisNaissanceMort, args.jourNaissanceMort )
		else
			age = fun.age(args.anneeNaissanceMort, args.numMoisNaissanceMort, args.jourNaissanceMort,  annee, numMois, jour )
		end
	end
	agePrefix = agePrefix or ''

	-- on traite le calendrier
	local gannee, gmois, gjour = annee, numMois, jour   -- date suivant le calendrier grégorien pour <time>
	local jannee, jmois, jjour = annee, mois, jour      -- date suivant le calendrier julien si necessaire
	local julienDate, julienSup, julienSep              -- servira éventuellement à afficher la date selon le calendrier julien
	local gregAprMois, gregAprAn, gregFin               -- message de calendrier grégorien lorsque la date est selon le calendrier julien
	local dateRepublicaine
	if annee and jour then
		local amj = annee * 10000 + numMois * 100 + jour
		if amj < 15821014 then
			if annee > 0 then
				gannee, gmois, gjour = fun.julianToGregorian( annee, numMois, jour )
			else
				-- calendrier grégorien proleptique avec année 0.
				gannee, gmois, gjour = fun.julianToGregorian( annee + 1, numMois, jour )
			end
			args.julien = false

		elseif args.julien then
			gannee, gmois, gjour = fun.julianToGregorian( annee, numMois, jour )
			annee, mois, jour = gannee, listeMois[gmois].nom, gjour
			if jjour == 1 then
				jjour = modelePremier
			end
			if args.compact then
				jmois = listeMois[jmois].abrev
			end
			if args.julien == 'court' then
				julienDate = jjour .. ' ' .. jmois .. ' '
				julienSup = '<sup>[[calendrier julien|jul.]]</sup>'
				if jannee == annee then
					gregAprMois = '<sup>[[calendrier grégorien|grég.]]</sup>'
				else
					julienDate = julienDate .. jannee .. ' '
					gregAprAn = '<sup>[[calendrier grégorien|grég.]]</sup>'
				end
				julienSep = ' / '
			else
				julienDate = jjour .. ' ' .. jmois .. ' ' .. jannee
				julienSep = ' ('
				gregFin = ' [[Passage du calendrier julien au calendrier grégorien|dans le calendrier grégorien]])'
			end

		elseif args.republicain then
			local DateRep = require 'Module:Date républicaine'
			local RepSansLiens
			if args.republicain == 'liens' then
				RepSansLiens = false
			else
				RepSansLiens = true
			end
			dateRepublicaine = DateRep._date_republicaine(
				RepSansLiens,
				{ fun.formatRepCal( fun.do_toRepCal{gannee, gmois, gjour} ) }
			)
		end
	else
		if annee and annee < 0 then
			gannee = gannee + 1
		end
		args.julien = false
		args.republicain = false
	end

	-- on génère le résultat

	-- Déclarations des variables
	local wikiListe = {}                   -- reçoit le texte affiché pour chaque paramètre
	local iso = {}                         -- reçoit le format date ISO de ce paramètre
	local texteMois = mois                 -- texte du mois qui sera affiché (éventuellement l'abréviation)
	if args.compact then
		if not numMois then
			-- mois est autre chose qu'un simple mois : saison, mois-mois... auquel cas, pas d'abréviation (provoquait erreur Lua)
			-- (les abréviations pour le cas "mois[-/]mois" seraient théoriquement possibles, mais ça reste à implémenter)
		else
			if args.nolinks then
				texteMois = '<abbr class="abbr" title="' .. mois .. '">' .. listeMois[mois].abrev .. '</abbr>'
			else
				texteMois = listeMois[mois].abrev
			end
		end
	end
	mois = mois and mois:gsub( 'aout', 'août' )

	local dataQualificatif, dataCat
	if not args.nolinks then
		dataQualificatif = dataLiens[qualificatif or '']
		if type( dataQualificatif ) ~= 'table' then
			-- si le qualificatif n'est pas dans la base de données, on crée une table minimum,
			-- qui imposera un test sur l'année, mais considère qu'il n'y a pas de lien sur le jour ou le mois
			dataQualificatif = { qualificatif = qualificatif, annee = { } }
		end
		dataCat = dataLiens[dataQualificatif.cat]
		if type( dataCat ) ~= 'table' or dataCat == dataQualificatif then
			dataCat = { qualificatif = '' }
		end
	end
	local function wikiLien( lien, texte )
		if lien == texte then
			return '[[' .. texte .. ']]'
		else
			return '[[' .. lien .. '|' .. texte .. ']]'
		end
	end


	-- le jour si présent
	local qualifJour = ''
	if jour then
		if args.nolinks then
			if jour == 1 then
				jour = modelePremier
			end
			table.insert( wikiListe, jour )
		else
			qualifJour = dataQualificatif.jour and dataQualificatif.qualificatif
				or dataCat.jour and dataCat.qualificatif
				or ''
			local texteJour, lien
			if jour == 1 then
				texteJour = '1<sup>er</sup>'
				lien = '1er ' .. mois
			else
				texteJour = jour
				lien = jour .. ' ' .. mois
			end
			if qualifJour ~= '' then
				lien = lien .. ' ' .. qualifJour
			end
			-- s'il n'y a pas de lien sur le mois, il sera affiché avec le jour.
			table.insert( wikiListe, wikiLien( lien, texteJour ) )
			table.insert( wikiListe, wikiLien( lien, texteJour .. ' '.. texteMois ) )
		end
		table.insert( iso, 1, string.sub( '0' .. gjour, -2 ) )
	end

	-- le mois
	if mois then
		if #wikiListe == 0 and annee == nil then
			return texteMois
		end
		if args.nolinks then
			if not args.masquerMois then
				table.insert( wikiListe, texteMois )
			end
		else
			local lien
			if annee then
				if not numMois then
					-- mois est autre chose qu'un simple mois : saison, mois-mois... auquel cas, pas de lien
				else
					lien = existDate( dataQualificatif, annee, mois ) or existDate( dataCat, annee, mois )
					if lien == nil and qualificatif and qualifJour == '' then
						-- nouveau test sans le qualificatif uniquement s'il n'y a pas d'éphémérides pour ce qualificatif.
						lien = existDate( dataLiens[''], annee, mois )
					end
				end
			end
			if lien or args.masquerMois then
				-- s'il y a un lien on retire le lien affichant 'jour mois' pour ajouter '[[mois annee|mois]]'
				table.remove( wikiListe )
				if not args.masquerMois then
					table.insert( wikiListe, wikiLien( lien, texteMois ) )
				end
			elseif #wikiListe > 0 then
				-- sinon on retire le lien affichant 'jour' pour ne garder que le lien 'jour mois'
				table.remove( wikiListe, #wikiListe - 1 )
			elseif args.masquerAnnee then
				-- s'il n'y a pas de jour et que l'année n'est pas affichée, on insère le mois seul.
				table.insert( wikiListe, texteMois )
			end
		end
		if gmois then
			table.insert( iso, 1, string.sub( '0' .. gmois, -2 ) )
		end
		table.insert( wikiListe, gregAprMois )
	end

	-- l'année
	if annee and not (args.julien == true and args.nolinks and jannee == annee ) then
		if not args.masquerAnnee then
			local texteAnnee = annee
			if annee < 0 then
				local anneeAvJc = 0 - annee
				if args.avJC == false then
					texteAnnee = anneeAvJc
				else
					texteAnnee = anneeAvJc .. ' <abbr class="abbr" title="'
						.. anneeAvJc .. ' avant Jésus-Christ">av. J.-C.</abbr>'
				end
			elseif args.apJC then
				texteAnnee = texteAnnee .. ' <abbr class="abbr" title="'
					.. texteAnnee .. ' après Jésus-Christ">apr. J.-C.</abbr>'
			end
			if args.nolinks then -- seulement si on doit l'afficher
				table.insert( wikiListe, texteAnnee )
			else
				local lien = existDate( dataQualificatif, annee ) or existDate( dataCat, annee )
				if not lien then
					if annee < 0 then
						local anneeAvJc = 0 - annee
						lien = anneeAvJc .. ' av. J.-C.'
					else
						lien = annee
					end
				end
				if mois and #wikiListe == 0 then
					-- si le mois n'a pas de lien et n'est pas affiché avec le jour, il est affiché avec l'année.
					texteAnnee = texteMois .. ' ' .. texteAnnee
				end
				table.insert( wikiListe, wikiLien( lien, texteAnnee ) )
			end
		end
	end
	if annee then
		if gannee > 999 then
			table.insert( iso, 1, gannee )
		elseif gannee > -1 then
			table.insert( iso, 1, string.sub( '000' .. gannee , -4 ) )
		elseif gannee > -999 then
			-- calendrier grégorien proleptique avec année 0.
			table.insert( iso, 1, 'U-' .. string.sub( '000' .. ( 0 - gannee ), -4 ) )
		else
			table.insert( iso, 1, 'U' .. gannee )
		end
	end
	table.insert( wikiListe, gregAprAn )

	-- l'age
	if type( age ) == 'number' and age >= 0 and ( not naissance or age < 120 ) then
		if age == 0 then
			age = '(' .. agePrefix .. 'moins d’un\194\160an)'
		elseif age == 1 then
			age = '(' .. agePrefix .. '1\194\160an)'
		else
			age = '('.. agePrefix .. age .. '\194\160ans)'
		end
	else
		age = false
	end


	-- compilation du résultat
	local wikiTexte = table.concat( wikiListe, ' ' )
	local isoTexte = table.concat( iso, '-' )

	-- On ajoute un peu de sémantique.
	local wikiHtml = mw.html.create( '' )

	if julienDate then
		wikiHtml:tag( 'span')
				:addClass( 'nowrap' )
				:attr( 'data-sort-value', isoTexte )
				:wikitext( julienDate )
				:node( julienSup )
				:done()
			:wikitext( julienSep )
	end

	local dateHtml = wikiHtml:tag( 'time' )
			:wikitext( wikiTexte )
	if wikiTexte:match( ' ' ) then
		dateHtml:addClass( 'nowrap' )
	end
	if isoTexte ~= wikiTexte then
		dateHtml:attr( 'datetime', isoTexte )
				:attr( 'data-sort-value', isoTexte )
	end
	if not args.nolinks then
		dateHtml:addClass( 'date-lien' )
	end
	if naissance then
		dateHtml:addClass( 'bday' )
	elseif mort then
		dateHtml:addClass( 'dday' )
	end

	wikiHtml:wikitext( gregFin )

	if dateRepublicaine then
		wikiHtml:wikitext( ' (', dateRepublicaine, ')' )
	end

	if age then
		wikiHtml:wikitext( ' ' )
				:tag( 'span' )
					:addClass( 'noprint')
					:wikitext( age )
					:done()
	end

	return tostring( wikiHtml )
end

	
-- isoJourMoisAnnee transforme une date iso en un tableau équivalent à celui retourné par separationJourMoisAnnee
local function isoJourMoisAnnee( dateiso )
	if dateiso and dateiso:sub( 1, 2 ) == 'U-' then
		dateiso = dateiso:sub( 2 )
	end
	local annee, mois, jour = dateiso:match( '^(%-?%d%d%d%d+)%-(%d+)%-(%d+)$' )
	if not annee then
		annee, mois = dateiso:match( '^(%-?%d%d%d%d+)%-(%d+)$' )
	end
	if not annee then
		annee = dateiso:match( '^(%-?%d%d%d%d+)$' )
	end
	if annee and tonumber( annee ) <= 0 then
		-- la date iso utilise une année 0
		annee = tonumber( annee ) - 1
	end
	if not annee then
		mois, jour = dateiso:match( '^(%d+)%-(%d+)$' )
	end
	return fun.validationJourMoisAnnee{ jour, mois, annee }
end

-- analyseDate sépare la date du contenu qui précède et suit, supprime les liens, et retourne si possible une table avec jour mois année
local function analyseDate( d )
	if trim( d ) then
		local datesMultiples = d:match( ' ou ' ) or d:match( '^[Ee]ntre ' ) or d:match( '<time.->.-<time.->' )
		if datesMultiples then
			return d
		end
		local approx = d:match( '^[Vv]ers ' ) or d:match( '^[Aa]près ' ) or d:match( '^[Aa]vant ' )
		-- booléen qui indique que la date est approximative, empêchant l'affichage de l'âge ou de la durée
		approx = approx and true or false

		local analyse = d
		-- s'il s'agit d'une date formattée avec {{date}}, on utilisera la valeur du datetime pour reconstruire la date
		local dateiso = d:match( 'datetime="([U%d-]+)"' ) or d:match( '<time>(.-)</time>' )
		local debut, strDate, fin
		if dateiso then
			-- supprime le formatage créé par {{date}}
			debut, strDate, fin = analyse:match( '(.-)<time.->(.-)</time>(.*)' )
		end
		if not strDate then
			-- sépare une date av. J.-C. du contenu qui suit
			strDate, fin = analyse:match( '(.-%d av%. J%.%-C%.]*%-?)(.*)' )
		end
		if not strDate then
			-- sépare la date (avec ses liens) d'une référence ou contenu commençant par un espace
			strDate, fin = analyse:match( '(.-%d%d%d%]*%-?)([\127 ].+)' )
		end
		if not strDate then
			-- sépare la date du contenu commençant par <br>
			strDate, fin = analyse:match( '(.-%d%d%d%]*%-?)(<br ?/?>.+)' )
		end
		analyse = strDate or analyse
		-- supprime les liens
		analyse = analyse:gsub(
			'%[%[([^%[%]|]*)|?([^%[%]]*)%]%]',
			function ( l, t )
				return trim( t ) or l
			end
		)
		
		local t, r
		if dateiso then
			-- Si la date était formatée avec {{date}}, on la reconstruit à partir de la valeur de datetime
			t, r = isoJourMoisAnnee( dateiso )
		end
		if t then
			local tTexte, rTexte = fun.separationJourMoisAnnee( analyse )
			if r.annee and r.jour then
				local amj = r.annee * 10000 + r.numMois * 100 + r.jour
				-- Les dates avant le 14 octobre 1582 sont dans le calendrier julien mais utilisent un datetime grégorien
				local gregToJul = amj < 15821014
				if not gregToJul and amj < 15821024 then
					-- Entre le 14 et le 23 octobre, on ne peut pas différencier entre julien et grégorien sans comparer avec le texte
					gregToJul = tTexte and rTexte.jour ~= r.jour
				end
				if gregToJul then
					local jannee, jmois, jjour = fun.gregorianToJulian( r.annee, r.numMois, r.jour )
					r = {
						annee = jannee,
						numMois = jmois,
						mois = listeMois[jmois].nom,
						jour = jjour
					}
				end
			end
			if tTexte then
				-- On analyse le texte de la date pour repérer les dates partiellement masquées
				-- Si seul le jour est affiché, separationJourMoisAnnee l'interprète comme une année
				if not rTexte.jour and not rTexte.mois and rTexte.annee == r.jour then
					r.masquerMois = true
					r.masquerAnnee = true
				elseif rTexte.jour == r.jour and rTexte.mois == r.mois and not rTexte.annee then
					r.masquerAnnee = true
				end
			end
		else
			t, r = fun.separationJourMoisAnnee( analyse )
		end
		if t then
			return r, fin, debut, approx
		else
			return d, fin, debut, approx
		end
	end
end

---
-- fonction destinée aux infobox, notamment pour afficher les dates de naissance et de mort
-- les liens présent dans les dates fournies sont automatiquement supprimés pour gérer les cas où
-- le paramètre contient déjà un modèle date.
-- Paramètres :
-- 		1 : type de date à afficher (naissance / n, mort / m, ou date / d)
-- 		2 : Date ou date de naissance
-- 		3 : Date de mort si type n ou m
-- 		qualificatif = suffixe des page de date à lier (exemple : en musique)
-- 		nolinks : n'affiche pas de lien
--		préfixe : préfixe à afficher s'il y a un jour (par défaut '')
--		préfixe sans jour : préfixe à afficher s'il n'y a pas de jour (par défaut : '')
function fun.dateInfobox( frame )
	local args = frame.args
	if type( args ) ~= 'table' or not ( args[1] and args[2] ) then
		return
	end
	-- prefix ajoute un préfixe en fonction de la présence ou non du jour si le paramètre "préfixe sans jour" est défini
	local function prefix( dateString )
		if dateString then
			local datetime = dateString:match( 'datetime="([U%d%-]+)"' )
			if datetime and datetime:match('%-%d%d%-%d%d') and trim( args['préfixe'] ) then
				return args['préfixe'] .. ' ' .. dateString
			end
			if trim( args['préfixe sans jour'] ) then
				return args['préfixe sans jour'] .. ' ' .. dateString
			end
		end
		return dateString
	end

	local naissance = args[1]:match( '^n' ) == 'n'
	local mort = args[1]:match( '^m' ) or args[1]:match( 'décès' )
	local evenement = args[1]:match( '^é' )
	local affichageDate, qualificatif = args[2], args[4]
	local affichageDateTab, resultatDate, complementDate, prefixeDate, approxDate
	local dateNaissance, dateMort, approxNaissance, approxMort
	if mort or evenement then
		affichageDate = args[3]
	end
	if not trim( affichageDate ) then
		return
	end

	if affichageDate:match( ']]</time>' ) or affichageDate:match( '</time>]]' ) then
		-- S'il y a des liens il y a probablement déjà un modèle date, évitons de l'exécuter une 2e fois
		if ( naissance or mort or evenement ) and ( affichageDate:match( 'wikidata%-linkback' )) then
			local _
			dateNaissance, _, _, approxNaissance = analyseDate( args[2] )
			dateMort, _, _, approxMort = analyseDate( args[3] )
			resultatDate = affichageDate
		else
			return prefix( affichageDate )
		end
	else
		affichageDateTab, complementDate, prefixeDate, approxDate = analyseDate( affichageDate )
		if type( affichageDateTab ) ~= 'table' then
			return affichageDateTab
		else
			if naissance then
				local _
				dateNaissance, approxNaissance = affichageDateTab, approxDate
				dateMort, _, _, approxMort = analyseDate( args[3] )
			elseif mort then
				local _
				dateNaissance, _, _, approxNaissance = analyseDate( args[2] )
				dateMort, approxMort = affichageDateTab, approxDate
			else
				qualificatif = args[3]
			end
			affichageDateTab.naissance = not approxNaissance and naissance
			affichageDateTab.mort = not approxMort and mort
			affichageDateTab.evenement = evenement
			affichageDateTab.qualificatif = args.qualificatif or qualificatif
			affichageDateTab.liens = true -- Dans les infobox, liens activés par défaut
			affichageDateTab.nolinks = args.nolinks
			affichageDateTab.nocat = args.nocat
			affichageDateTab.julien = args.julien
		end
	end
	resultatDate = resultatDate or fun.modeleDate( affichageDateTab )

	local age, prefixAge, suffixAge, calculAge = '', ' <span class="noprint">(', ')</span>', nil
	if naissance and
		dateNaissance and
		not approxNaissance and
		not dateMort and
		type( dateNaissance ) == 'table'
	then
		calculAge = fun.age( dateNaissance.annee, dateNaissance.numMois, dateNaissance.jour )
		if calculAge and calculAge > 120 then
			calculAge = nil
		end
	elseif ( mort or evenement ) and
		dateNaissance and
		dateMort and
		not approxNaissance and
		not approxMort and
		type( dateNaissance ) == 'table'
		and type( dateMort ) == 'table'
	then
		calculAge = fun.age(
			dateNaissance.annee,
			dateNaissance.numMois,
			dateNaissance.jour,
			dateMort.annee,
			dateMort.numMois,
			dateMort.jour
		)
		prefixAge = ' (à '
		suffixAge = ')'
	end
	if tonumber( calculAge ) then
		if calculAge > 1 then
			age = prefixAge .. calculAge .. '\194\160ans' .. suffixAge
		elseif calculAge == 1 then
			age = prefixAge .. 'un\194\160an' .. suffixAge
		elseif calculAge == 0 then
			age = prefixAge .. 'moins d’un\194\160an' .. suffixAge
		end
		if complementDate and complementDate:match( 'ans?%)' ) then
			complementDate = ''
		end
	end

	return ( prefixeDate or '' ) .. prefix( resultatDate ) .. ( complementDate or '' ) .. age
end

function fun.dureeInfobox( frame )
	local args = frame.args
	if type( args ) ~= 'table' or not args[1] then
		return
	end
	
	-- vérifie si une chaîne semble contenir une durée
	local function contientDuree( chaine )
		return chaine and (
			mw.ustring.match( chaine, '%f[%w]ans?%f[^%w]' ) or
			mw.ustring.match( chaine, '%f[%w]mois%f[^%w]' ) or
			mw.ustring.match( chaine, '%f[%w]jours?%f[^%w]' )
		)
	end
	
	local jour1, mois1, annee1, jour2, mois2, annee2 = '', '', '', '', '', ''
	local t1, fin1, _, approx1 = analyseDate( args[1] )
	if approx1 or type( t1 ) ~= 'table' then
		return
	end
	jour1 = t1.jour or ''
	mois1 = t1.numMois or ''
	annee1 = t1.annee or ''
	if args[2] and args[2] ~= "" then
		local t2, fin2, _, approx2 = analyseDate( args[2] )
		if approx2 or type( t2 ) ~= 'table' then
			return
		end
		if contientDuree( fin2 ) then
			-- La durée semble déjà renseignée manuellement
			return
		end
		jour2 = t2.jour or ''
		mois2 = t2.numMois or ''
		annee2 = t2.annee or ''
		if annee1 == '' or annee2 == '' then
			-- Mieux vaut ne pas extrapoler l'année
			return
		end
		if ( jour1 ~= '' and jour2 == '' ) or
			( mois1 ~= '' and mois2 == '' ) then
				-- Si la deuxième date est moins précise que la première, mieux vaut ne rien afficher
				return
		end
	elseif annee1 == '' or contientDuree( fin1 ) then
		-- L'année n'est pas spécifiée ou la durée semble déjà renseignée
		return
	end
	local duree = Duree._duree({ jour1, mois1, annee1, jour2, mois2, annee2, noerror = true })
	if duree then
		return '<br /><small>(' .. duree .. ')</small>'
	end
end

---
-- la fonction dateISO renvoie un date au format aaaa-mm-jj (sans liens)
-- l'année peut être sous la forme 2013 ou [[2013 en litérature|2013]]
-- le mois peut être en lettres ou en chiffres
-- le jour peut être sous la forme '05', '{{1er}}' ou 'vendredi 13'
function fun.dateISO( frame )
	local args = Outils.extractArgs( frame )
	local annee = Outils.notEmpty( args['année'], args.annee, args.year, args.date )
	-- extraction de l'année
	if type( annee ) == 'string' then
		annee = ( tonumber( annee )	-- match '2013'
				or string.match ( annee, '%D(%d%d%d%d)%D' ) -- match '[[2013 en musique|2013]]'
				or string.match ( annee, '%D(%d%d%d%d)$' )  -- match '17 septembre 2013'
				or string.match ( annee, '^(%d%d%d%d)%D' )  -- match '2013-09-17'
		)
	end
	annee = tonumber( annee )

	-- le format de date iso est défini suivant le calendrier grégorien.
	-- Avant l'année 1583 la date est calendrier est probablement du calendrier julien,
	-- donc autant s'abstenir.
	if annee and annee > 1582 then
		local mois = Outils.notEmpty( args.mois, args.month )
		-- num mois trouve le numéro du mois, qu'il soit numérique ou texte, complet ou abrégé.
		local nomMois, numMois = fun.determinationMois( mois )
		if numMois then
			mois = '-' .. string.sub( '0' .. numMois, -2 )

			local jour = Outils.notEmpty( args.jour, args.day, args['quantième'] )
			if type( jour ) == 'string' then
				jour = tonumber( jour ) or tonumber( string.match ( jour, '%d+') )
			end
			jour = tonumber( jour )
			if jour and jour <= listeMois[numMois].nJour then
				jour = '-' .. string.sub( '0' .. jour, -2 )
				return annee .. mois .. jour
			else
				return annee .. mois
			end
		else
			return tostring( annee )
		end
	end
end

---
-- Rang du jour dans l'année
-- Usage : do_dayRank{année,mois,jour}
function fun.do_dayRank(arguments)
	local yr = tonumber(arguments.year or arguments[1]) or 1
	local mt = tonumber(arguments.month or arguments[2]) or 1
	local dy = tonumber(arguments.day or arguments[3]) or 1
	-- Rangs des premiers des mois
	local ranks = {0,31,59,90,120,151,181,212,243,273,304,334}

	local rank = (ranks[mt] or 0) + dy - 1
	if(fun.isLeapYear(yr) and (mt >= 3)) then
		rank = rank+1
	end
	return rank
end

-- Nombre de jours entre deux années (du 1er janvier au 1er janvier)
-- Suit le calendrier grégorien
function fun.do_daysBetween(arguments)
	local yr1 = tonumber(arguments[1]) or 0
	local yr2 = tonumber(arguments[2]) or 0

	return fun.daysSinceOrigin(yr2) - fun.daysSinceOrigin(yr1)
end

-- Nombre de jours depuis l'année 1 (du 1er janvier au 1er janvier)
function fun.daysSinceOrigin(year)
	local yr = year-1
	return 365*yr + math.floor(yr/4) - math.floor(yr/100) + math.floor(yr/400)
end

-- Test d'année bissextile (Suit le calendrier grégorien)
function fun.isLeapYear(year)
	local yr = tonumber(year) or 1
	return (yr%4 == 0) and ((yr%100 ~= 0) or (yr%400 == 0))
end

-- Conversion d'un nombre en chiffres romains
function fun.toRoman(number)
	local n = math.floor(number)
	local letters = {"I","V","X","L","C","D","M","",""}
	local pattern = {"","0","00","000","01","1","10","100","1000","02"}
	local result = ""
	if(n<=0 or n>=4000) then
		result = "---"
	else
		for i=1,7,2 do
			local p = pattern[n%10 + 1]
			for j=0,2 do
				p = string.gsub(p,tostring(j),letters[i+j])
			end
			result = p .. result
			n = math.floor(n/10)
		end
	end
	return result
end

-- Conversion et affichage d'une date dans le calendrier républicain
function fun.dateRepublicain(frame)
	local pframe = frame:getParent()
	local arguments = pframe.args
	return fun.formatRepCal(fun.do_toRepCal(arguments))
end

---
-- Calcul d'une date dans le calendrier républicain
-- On suppose que les années 4n+3 sont sextiles (3, 7, 11...)
function fun.do_toRepCal(arguments)
	local yr = tonumber(arguments.year or arguments[1]) or 2000
	-- rang absolu du jour demandé, le jour 0 étant le 22 septembre 1792 (1er jour de l'an I)
	local repDays = fun.do_dayRank(arguments) + fun.do_daysBetween{1792,yr} - fun.do_dayRank{1792,9,22}
	local repYear = math.floor((repDays+731)/365.25) - 1
	local repDayRank = repDays - 365*(repYear-1) - math.floor(repYear/4)
	local repMonth, repDay = math.floor(repDayRank/30)+1, (repDayRank%30)+1
	return {repYear, repMonth, repDay}
end

---
-- Formatage d'une date selon le calendrier républicain
-- Usage : fun.formatRepCal{année,mois,jour}
function fun.formatRepCal(arguments)
	local months = {"Vendémiaire","Brumaire","Frimaire","Nivôse","Pluviôse","Ventôse","Germinal","Floréal","Prairial","Messidor","Thermidor","Fructidor"}
	local extras = {"de la vertu","du génie","du travail","des récompenses","de l'opinion","de la Révolution"}
	local result = ""
	if(arguments[2] < 13) then
		result = result .. tostring(arguments[3]) .. "\194\160" .. months[arguments[2]]
	else
		result = result .. "jour " .. extras[arguments[3]]
	end
	result = result .. " de l'an " .. fun.toRoman(arguments[1])
	return result
end

---
-- Voir Modèle:Âge
-- retourne l'âge en fonction de la ou les dates fournies. La valeur retournée est de type 'number'
-- Paramètres :
-- 1, 2, 3 : année, mois jour de naissance (supposé dans le calendrier grégorien)
-- 4, 5, 6 : année, mois, jour du calcul (facultatif, par défaut la date UTC courante).
function fun.age( an, mn, jn, ac, mc, jc )
	if ac == nil then
		local today = os.date( '!*t' )
		ac = today.year
		mc = today.month
		jc = today.day
	else
		ac = tonumber( ac )
		mc = tonumber( mc )
		jc = tonumber( jc )
	end

	an = tonumber( an )
	mn = tonumber( mn )
	jn = tonumber( jn )

	if an == nil or ac == nil or mn == nil or mc == nil then
		-- pas de message d'erreur qui risque de faire planter la fonction appelante
		-- à elle de gérer ce retour.
		return
	end

	local age = ac - an

	-- si l'intervalle traverse l'année zéro, il faut soustraire une année
	-- parce que cette année n'existe pas dans les calendriers chrétiens
	if an < 0 and ac > 0 then
		age = age - 1
	end

	if mc == mn then
		if jc == nil or jn == nil then
			return
		end
		return age - ( jc < jn and 1 or 0 )
	else
		return age - ( mc < mn and 1 or 0 )
	end
end

function fun.modeleAge( frame )
	local args = Outils.extractArgs( frame )
	local age = fun.age(
		args[1] or args['année'],
		args[2] or args['mois'],
		args[3] or args['jour'],
		args[4],
		args[5],
		args[6]
	)
	if age then
		return age
	else
		return '<span class="error">Paramètres incorrects ou insuffisants pour calculer l\'âge précis</span>'
	end
end

---
-- calcul du jour julien à partir d'une date du calendrier grégorien
function fun.julianDay( year, month, day, hour, min, sec )
	local julian
	julian = math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) * 1461 / 4 )
			- math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) / 100 )
			+ math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) / 400 )
			+ math.floor( ( math.fmod( month + 57609, 12 ) + 4 ) * 153 / 5 )
			+ day + ( hour or 12 ) / 24 + ( min or 0 ) / 1440 + ( sec or 0 ) / 86400
			- 32167.5
	return julian
end

---
-- calcul du jour julien à partir d'une date du calendrier julien
function fun.julianDayJulian( year, month, day, hour, min, sec )
	local julian
	julian = math.floor( math.floor( ( year * 12 + month + 57609 ) / 12 - 1 ) * 1461 / 4 )
			+ math.floor( ( math.fmod( month + 57609, 12 ) + 4 ) * 153 / 5 )
			+ day + ( hour or 12 ) / 24 + ( min or 0 ) / 1440 + ( sec or 0 ) / 86400
			- 32205.5
	return julian
end

---
-- calcul d'une date dans le calendrier grégorien à partir du jour julien
function fun.julianDayToGregorian( julianDay )
	local base = math.floor( julianDay + 32044.5 )  -- 1 March -4800 (proleptic Gregorian date)
	local nCentury = math.floor( ( base * 4 + 3 ) / 146097 )
	local sinceCentury = base - math.floor( nCentury * 146097 / 4 )
	local nYear = math.floor( ( sinceCentury * 4 + 3 ) / 1461 )
	local sinceYear = sinceCentury - math.floor( nYear * 1461 / 4 )
	local nMonth = math.floor( ( sinceYear * 5 + 2 ) / 153 )

	local day = sinceYear - math.floor( ( nMonth * 153 + 2 ) / 5 ) + 1
	local month = nMonth - math.floor( nMonth / 10 ) * 12 + 3
	local year = math.floor( sinceYear / 306 ) + nYear + 100 * nCentury - 4800

	return year, month, day
end

---
-- calcul d'une date dans le calendrier julien à partir du jour julien
-- calcul basé sur l'algorithme de la page https://en.wikipedia.org/wiki/Julian_day#Julian_or_Gregorian_calendar_from_Julian_day_number
function fun.julianDayToJulian( julianDay )
	local y = 4716
	local v = 3
	local j = 1401
	local u = 5
	local m = 2
	local s = 153
	local n = 12
	local w = 2
	local r = 4
	local B = 274277
	local p = 1461
	local C = -38
	local f = julianDay + j
	local e = r * f + v
    local g = math.modf( math.fmod( e, p ) / r )
    local h = u * g + w
    local D = math.modf( math.fmod( h, s ) / u ) + 1
    local M = math.fmod( math.modf( h / s ) + m, n ) + 1
    local Y = math.modf( e / p ) - y + math.modf( ( n + m - M ) / n )
    return Y, M, D
end

---
-- calcul d'une date dans le calendrier grégorien à partir d'une date dans le calendrier julien
function fun.julianToGregorian( year, month, day )
	return fun.julianDayToGregorian( fun.julianDayJulian( year, month, day ) )
end

---
-- calcul d'une date dans le calendrier julien à partir d'une date dans le calendrier grégorien
function fun.gregorianToJulian( year, month, day )
	year = tonumber(year)
	if month then month = tonumber(month) else month = 6 end --prend une valeur centrale pour donner un best "guess"
	if day then day = tonumber(day) else day = 15 end
	return fun.julianDayToJulian( fun.julianDay( year, month, day ) )
end


--[[
  Cette fonction retourne "CET" ou "CEST" selon que dans la pseudo-timezone en cours
    c'est l'heure d'été ou l'heure d'hiver.
  Cette fonction n'a de sens a priori que pour des modèles utilisés en Europe

  Paramètre optionnel non nommé : "sans lien" : retourne le texte CET/CEST. sinon
    retourne ce même texte avec un wikilien vers les articles correspondants
--]]
function fun.CEST(frame)
	-- option : ne pas créer de wikilien
	local opt = trim(frame.args[1] or frame:getParent().args[1])
	-- on récupère l'information dans la zone courante
	local t = mw.getContentLanguage():formatDate("I", nil, true)

	if (t == "1") then  -- heure d'été
		if (opt == "sans lien") then
			return "CEST"
		elseif (opt == "décalage") then
			return "2"
		else
			return "[[Heure d'été d'Europe centrale|CEST]]"
		end
	else  -- heure d'hiver (ou autre zone où ça ne s'applique pas)
		if (opt == "sans lien") then
			return "CET"
		elseif (opt == "décalage") then
			return "1"
		else
			return "[[Heure normale d'Europe centrale|CET]]"
		end
	end
end

return fun