Aller au contenu

« Module:Date » : différence entre les versions

Une page de Wikipédia, l'encyclopédie libre.
Contenu supprimé Contenu ajouté
Zebulon84 (discuter | contributions)
retouche de la modification précédente : les tables de loadData sont en lecture seule :(
l'année était de trop dans l'infobulle, vu qu'elle se trouve avant l'abréviation, et non dedans ; refs discussion
(226 versions intermédiaires par 20 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
-- luacheck: globals mw, no max line length
local fun = {}


local fun = {}
local TableBuilder = require( 'Module:TableBuilder' )


local Outils = require 'Module:Outils'
-- génère une erreur
-- chargement de la base de données répertoriant certaines pages existant ou n'existant pas pour éviter les "ifexist".
local function erreurDate(texte)
local dataLiens
return '<span class="error">' .. (texte or "''aucune erreur indiquée''") .. "</span>"
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
end


-- nettoie un paramètre non nommé (vire les espaces au début et à la fin)
-- 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 fonction qui l'utilise.
-- retourne nil si le texte est vide ou n'est pas du texte. Attention c'est important pour les fonctions qui l'utilisent.
local function trim( texte )
local trim = Outils.trim
if type( texte ) == "string" and texte ~= "" then
return mw.text.trim( texte )
end
end


-- 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 )
local function ucfirst( str )
return mw.ustring.upper( mw.ustring.sub( str, 1, 1 ) ) .. mw.ustring.sub( str, 2 )
return str:sub( 1, 1 ):upper() .. str:sub( 2 )
end
end


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



-- liste des mois, écriture exacte et simplifiée, en minuscule
-- liste des mois, écriture exacte et alias, en minuscule
local liste_mois = {
local listeMois = {
{ "janvier", "jan.", "janv.", "jan", "janv", "january", nJour = 31 },
{ num = 1, nJour = 31, abrev = 'janv.', nom = 'janvier', alias = { 'jan.', 'janv.', 'jan', 'janv', 'january' } },
{ "février", "fevrier", "fev.", "fev", "fév.", "fév", "february", nJour = 29 },
{ 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.' } },
{ "mars", "mar.", "mar", "march", nJour = 31 },
{ num = 3, nJour = 31, abrev = 'mars', nom = 'mars', alias = { 'mar.', 'mar', 'march' } },
{ "avril", "avr.", "avr", "apr", "april", nJour = 30 },
{ num = 4, nJour = 30, abrev = 'avr.', nom = 'avril', alias = { 'avr.', 'avr', 'apr', 'april'} },
{ "mai", "may", nJour = 31 },
{ "juin", "jun", "june", nJour = 30 },
{ num = 5, nJour = 31, abrev = 'mai', nom = 'mai', alias = { 'may' } },
{ num = 6, nJour = 30, abrev = 'juin', nom = 'juin', alias = { 'jun', 'june' } },
{ "juillet", "juil.", "juil", "juill.", "juill", "jul", "july", nJour = 31 },
{ num = 7, nJour = 31, abrev = 'juill.', nom = 'juillet', alias = { 'juil.', 'juil', 'juill.', 'juill', 'jul', 'july' } },
{ "août", "aout","aou", "aug", "august", nJour = 31 },
{ num = 8, nJour = 31, abrev = 'août', nom = 'août', alias = { 'aoû', 'aug', 'august' } },
{ "septembre", "sept.", "sept", "sep.", "sep", "september", nJour = 30 },
{ num = 9, nJour = 30, abrev = 'sept.', nom = 'septembre', alias = { 'sept.', 'sept', 'sep.', 'sep', 'september' } },
{ "octobre", "oct.", "oct", "october", nJour = 31 },
{ num = 10, nJour = 31, abrev = 'oct.', nom = 'octobre', alias = { 'oct.', 'oct', 'october' } },
{ "novembre", "nov.", "nov", "november", nJour = 30 },
{ num = 11, nJour = 30, abrev = 'nov.', nom = 'novembre', alias = { 'nov.', 'nov', 'november' } },
{ "décembre", "decembre", "déc.", "dec.", "dec", "déc", "december", nJour = 31 },
{ 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
-- nom du mois à partir du numéro
for i = 1, 12 do
function fun.nomDuMois( num )
local mois = listeMois[i]
if type( num ) ~= "number" or num < 1 or num > 12 then
listeMois[tostring( i )] = mois
return nil
end
if i < 10 then
listeMois['0' .. i] = mois
return liste_mois[num][1]
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
end


local liste_saisons = {
-- valide que la chaîne passée est un mois valide.
{ 'printemps', 'spring', },
-- retourne le nom complet ou nil si non reconnu
{ 'été', 'summer', },
-- si reconnu, retourne aussi le numéro du mois [1-12]
{ 'automne', 'autumn', },
function fun.valideMois( mois )
{ 'hiver', 'winter', },
if type( mois ) ~= "string" then
}
return nil
end
local m = mw.ustring.lower( mois )


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


---
-- determinationMois trouve le numéro du mois et son nom,
-- à partir de son nom, de son numéro ou d'une expression mathématique.
-- à 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)
-- l'objet frame est facultatif, mais permet de tester si mois est une expression type '2+3'
-- si non reconnu, retourne nil, nil
function fun.determinationMois( mois, frame )
function fun.determinationMois( mois )
local num, nom
local result
if tonumber( mois ) then

num = math.floor( math.fmod( tonumber( mois ) - 1, 12 ) ) + 1
elseif type( mois ) == "string" then
local num = tonumber( mois )
if num then
nom, num = fun.valideMois( mois )
result = listeMois[num]
if nom == nil and frame and frame.callParserFunction then
else
-- essai de détermination d'un nombre avec le parser #expr de Mediawiki.
local str = trim( mois )
-- la fonction s'appelle sans l'objet frame pour ne pas tourner en boucle.
if str then
nom, num = fun.determinationMois( frame:callParserFunction( '#expr', mois ) )
result = listeMois[str]
end
if not result then
end
result = listeMois[str:gsub( 'É', 'é' ):gsub( 'Û', 'û' ):lower()]
if num and not nom then
end
nom = liste_mois[num][1]
end
end
end
return nom, num

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




-- fonction interne à modeleDate, pour déterminer si on peut se passer de faire un ifexit
-- fonction interne à modeleDate, pour déterminer si on peut se passer de faire un ifexist
local function existDate( dataQualificatif, annee, mois )
local function existDate( dataQualificatif, annee, mois )
local data
local data
if mois then
if mois then
data = dataQualificatif.mois
data = dataQualificatif.mois
else
else
data = dataQualificatif.annee
data = dataQualificatif.annee
end
end
if type( data ) ~= 'table' then
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.
-- si data n'existe pas c'est que l'on considère qu'il n'y a pas de lien.
return
return
end
end
-- le qualificatif est remplacer par celui de la base de donnée, ce qui permet des alias.
-- le qualificatif est remplacé par celui de la base de données, ce qui permet des alias.
local lien = annee .. ' ' .. ( dataQualificatif.qualificatif or '' )
local lien = annee
if dataQualificatif.qualificatif ~= '' then
lien = lien .. ' ' .. dataQualificatif.qualificatif
local seul = annee
end
if mois then
local seul = annee
lien = mois .. ' ' .. lien
if mois then
seul = ucfirst( mois ) .. ' ' .. annee
lien = mois .. ' ' .. lien
end
seul = ucfirst( mois ) .. ' ' .. annee
end
if type( data.aucun ) == 'number' and annee <= (data.aucun or 0) then
local aucun = tonumber( data.aucun )
-- si la 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
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é
for i, v in ipairs( data.seul ) do
if seul == v then
if type( data.seul ) == 'table' then
for i, v in ipairs( data.seul ) do
return lien
if seul == v or seul == tonumber( v ) then
end
return lien
end
end
end
end
-- partie aucun et pas de lien => nil
end
return nil
-- partie aucun et pas de lien => nil
elseif type( data.tous ) == 'table'
return nil
and type( data.tous[1] ) == 'number'
and type( data.tous[2] ) == 'number'
elseif type( data.tous ) == 'table' then
and annee >= data.tous[1]
local tous1, tous2 = tonumber( data.tous[1] ), tonumber( data.tous[2] )
and annee <= 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
then
return lien
-- l'année est dans la partie 'tous' donc on retourne le lien
end
return lien
end
end
-- l'annee n'est ni dans la partie aucun, ni dans la partie tous donc il faut tester si la page existe.
-- l'année n'est ni dans la partie aucun, ni dans la partie tous donc il faut tester si la page existe.
if mw.title.new( lien ).exists then
local cibleLien = mw.title.new( lien )
if cibleLien and cibleLien.exists then
return lien
return lien
end
end
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
if date:find( '[[', nil, true ) then
date = date
-- suppression catégories (doit être exécuté avant le code de suppression des liens)
:gsub( '%[%[[Cc]atégorie:.-%]%]', '' )
:gsub( '%[%[[Cc]ategory:.-%]%]', '' )
-- suppression liens
:gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
end
date = date
-- suppression balises
:gsub( '%b<>', '' )
-- 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 (doit être exécuté après les autres remplacements)
: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: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}}.
-- émule le modèle {{m|Date}}.
-- Paramètres :
-- Paramètres :
-- 1 : jour (numéro ou "1er"). optionnel, si absent pas de jour
-- 1 : jour (numéro ou "1er") ou la date complète
-- 2 : mois (en toutes lettres)
-- 2 : mois (en toutes lettres) ou spécialité de l'année
-- 3 : année (nombre)
-- 3 : année (nombre)
-- 4 : optionnel, spécialité de l'année
-- 4 : spécialité de l'année
-- julien : date dans le calendrier julien
-- Comportement spécial ("truc à deux balles au lieu d'utiliser un
-- compact : affiche le mois sous forme d'abréviation
-- paramètre nommé du genre "sans année=oui"...") : si 1 est vide
-- avJC : non pour désactiver l'affichage de « av. J.-C. » pour les dates négatives
-- mais que le reste est complet → on n'affiche pas l'année
-- â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 )
function fun.modeleDate( frame )
local args = frame.args or frame
local Yesno = require 'Module:Yesno'
if args[1] == nil and args[3] == nil and frame.getParent then
args = frame:getParent().args
end
-- chargement de la base de donnée 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 ou le sous module serait mal modifié
dataLiens = { [''] = { mois = { aucun = 1000, tous = { 1938, 2013 } }, }, parametresPage = { } }
end
local annee, mois, numMois, jour
local decalage = 0
-- si pas de jour mais que args[2] est un mois on décale tout et on
-- n'affiche pas l'année
if trim( args[1] ) == nil and fun.valideMois( trim( args[3] ) ) ~= nil then
decalage = 1
end
-- on traite l'année
local bannee = trim( args[3 + decalage] )
if bannee then
annee = tonumber( bannee )
if annee == nil then
-- test si l'année contient av. J.-C.
annee = mw.ustring.match( mw.ustring.upper( bannee ), '^(%d+)%sAV%.?%s?J%.?%-?C' )
annee = tonumber( annee )
if annee then
annee = 0 - annee
else
return erreurDate( 'Année invalide (' .. bannee .. ')' )
end
end
else
annee = nil
end


local args = Outils.extractArgs( frame )
-- on traite le mois
local resultat
local bmois = trim (args[2 + decalage] )
if bmois then
mois, numMois = fun.valideMois( bmois )
if mois == nil then
-- on teste si le mois est fourni sous forme numérique
numMois = tonumber( bmois )
mois = fun.nomDuMois( numMois )
end
if mois == nil then
return erreurDate( 'Mois invalide (' .. bmois .. ')' )
else
-- on traite le jour si présent
local bjour = trim( args[1 + decalage] )
if bjour then
jour = tonumber( bjour )
if jour == nil then
if bjour == '1er'
or bjour == '<abbr class="abbr" title="Premier" >1<sup>er</sup></abbr>' -- correspond à {{1er}}
then
jour = 1
else
return erreurDate( 'Jour invalide (' .. bjour .. ')' )
end
end
-- on valide que le jour est correct
if jour < 1 or jour > 31 then
return erreurDate( 'Jour invalide (' .. bjour .. ')' )
elseif jour > liste_mois[numMois].nJour then
return erreurDate( 'Jour invalide (' .. bjour .. ' ' .. mois .. ')' )
-- l'année bisextile n'est pas testée pour accepter les dates juliennes.
end
else
-- S'il n'y a pas de jour on regarde si la première lettre du mois est en majuscule
if mw.ustring.match( bmois, '^%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
end
-- else
-- return erreurDate("Le mois est obligatoire")
end


local dateNaissanceMort
-- on traite le champs optionnel
local qualificatif = trim( args[4 + decalage] ) or dataLiens.parametresPage.qualificatif
-- on traite l'age
local age = trim( args['âge'] or args['age'] )
age = age and fun.age( annee, numMois, jour )
-- on traite le calendrier
local gannee, gmois, gjour = annee, numMois, jour -- date suivant le calendrier grégorien pour <time>
local jannee, jmois, jjour, julien = annee, mois, jour -- servira éventuellement à a affiché la date selon le calendrier julien
local julien1, julien2, julien3 = '', '', '' -- servira éventuellement à a affiché des parenthèses
local julien = trim( string.lower( args.julien or dataLiens.parametresPage.julien or '' ) )
if annee and jour then
local amj = annee * 10000 + numMois * 100 + jour
if amj < 15821014 then
gannee, gmois, gjour = fun.jullianToGregorian( annee, numMois, jour )
elseif julien == 'oui' then
gannee, gmois, gjour = fun.jullianToGregorian( annee, numMois, jour )
annee, numMois, jour = gannee, liste_mois[gmois][1], gjour
end
end


-- analyse des paramètres non nommés (ou paramètres de la date jour, mois, annee)
local test, params
-- on génère le résultat
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
-- Déclarations des variables
-- la date est dans le premier paramètre
local wikiListe = TableBuilder.new() -- reçois le texte affiché pour chaque paramètre
test, params = fun.separationJourMoisAnnee( arg1 )
local iso = TableBuilder.new() -- reçois le format date ISO de ce paramètre
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
local dataQualificatif = dataLiens[qualificatif or '']
if args.masquerMois then cleanArgs.masquerMois = args.masquerMois end
if type( dataQualificatif ) ~= 'table' then
if args.masquerAnnee then cleanArgs.masquerAnnee = args.masquerAnnee end
-- si le qualifiquatif n'est pas dans la base de donnée, on crée une table minimum,
-- qui imposera un test sur l'annee, mais considère qu'il n'y a pas de lien sur le jour ou le mois
dataQualificatif = { qualificatif = ' ' .. qualificatif, annee = { } }
end
local dataCat = dataLiens[dataQualificatif.cat]
if type( dataCat ) ~= 'table' or dataCat == dataQualificatif then
dataCat = { qualificatif = '' }
end
-- Date julienne
if jjour ~= jour then
wikiListe.insert( '<abbr title="selon le calendrier julien" >' .. jjour )
julien1 = '('
if jannee ~= annee then
wikiListe.insert( jmois )
wikiListe.insert( jannee .. '</abbr>' )
julien3 = ')'
else
wikiListe.insert( jmois .. '</abbr>' )
julien2 = ')'
end
end
-- le jour si présent
local qualifJour = ''
if jour then
-- éphémérides du qualificatif, ou de sa catégorie, ou générale.
qualifJour = dataQualificatif.jour and dataQualificatif.qualificatif
or dataCat.jour and dataCat.qualificatif
or ''
local lien = jour .. ' ' .. mois .. ' ' .. qualifJour
if jour == 1 then
jour = '<abbr class="abbr" title="premier">1<sup>er</sup></abbr>'
lien = '1er ' .. mois .. ' ' .. qualifJour
end
-- s'il n'y a pas de lien sur le mois, il sera affiché avec le jour.
wikiListe.insert( julien1 .. '[[' .. lien .. '|' .. jour .. ']]' )
wikiListe.insert( julien1 .. '[[' .. lien .. '|' .. jour .. ' '.. mois .. ']]' .. julien2 )
iso.insert( 1, string.sub( '0' .. gjour, -2 ) )
end


test, params = fun.validationJourMoisAnnee( cleanArgs )
-- le mois
if mois then
if test and dataLiens[trim( args[4] )] then
params.qualificatif = trim( args[4] )
local lien
end
if annee then
end
lien = existDate( dataQualificatif, annee, mois ) or existDate( dataCat, annee, mois )

if lien == nil and qualificatif and qualifJour~= '' then
-- analyse des paramètres nommés
-- test nouveau test sans le qualificatif uniquement s'il n'y a pas d'éphémérides pour ce qualificatif.
if test then
lien = existDate( dataLiens[''], annee, mois )
params.agePrefix = args.agePrefix
end
if args.qualificatif and dataLiens[args.qualificatif] then
end
params.qualificatif = args.qualificatif
if lien then
end
-- s'il y a un lien on retire le lien affichant 'jour mois' pour ajouter '[[mois annee|mois']]

wikiListe.remove()
-- julien peut avoir trois valeurs : inactif, format standard (true), format court
wikiListe.insert( '[[' .. lien .. '|' .. mois .. ']]' .. julien2 )
params.julien = Yesno( args.julien, 'court', false )
else
params.avJC = Yesno( args.avJC )
-- sinon on retire le lien affichant 'jour' pour ne garder que le lien 'jour mois'

wikiListe.remove( #wikiListe - 1 )
if args['républicain'] and args['républicain'] ~= '' then
-- s'il n'y avait pas je jour, la liste est vide mais ça ne pose pas de problème
if args['républicain'] == 'liens' then
-- sauf si l'année n'est pas affichée :
params.republicain = 'liens'
if #wikiListe == 0 and ( annee == nil or decalage > 0 ) then
else
return mois
params.republicain = Yesno( args['républicain'], false )
end
end
end
else
iso.insert( 1, string.sub( '0' .. gmois, -2 ) )
params.republicain = false
end
end
if args.dateNaissanceMort and args.dateNaissanceMort ~= '' then
-- l'année
dateNaissanceMort = args.dateNaissanceMort
if annee and decalage == 0 then -- seulement si on doit l'affichée
elseif args['dateNaissanceÉvénement'] and args['dateNaissanceÉvénement'] ~= '' then
local lien = existDate( dataQualificatif, annee ) or existDate( dataCat, annee )
dateNaissanceMort = args['dateNaissanceÉvénement']
local texte = annee
end
if annee < 0 then
if dateNaissanceMort then
local annneeAvJc = 0 - annee
local testNaissanceMort, paramsNaissanceMort = fun.separationJourMoisAnnee( dateNaissanceMort )
lien = lien or ( annneeAvJc .. ' av. J.-C.' )
if testNaissanceMort then
local avJC = trim( string.lower( args.avJC or dataLiens.parametresPage.avJC or '' ) )
params.anneeNaissanceMort, params.moisNaissanceMort, params.numMoisNaissanceMort, params.jourNaissanceMort = paramsNaissanceMort.annee, paramsNaissanceMort.mois, paramsNaissanceMort.numMois, paramsNaissanceMort.jour
if args.avJC == 'non' then
end
texte = annneeAvJc
end
else

texte = annneeAvJc .. '&nbsp;<abbr class="abbr" title="'
local listeParam = {
.. annneeAvJc .. ' avant Jésus-Christ">av.&nbsp;J.-C.</abbr>'
age = 'âge',
end
['âge'] = 'âge',
end
naissance = 'naissance',
lien = lien or annee
mort = 'mort',
if mois and #wikiListe == 0 then
['événement'] = 'événement',
-- si le mois n'a pas de lien et n'est pas affiché avec le jour, il est affiché avec l'année.
evenement = 'evenement',
texte = mois .. ' ' .. texte
['décès'] = 'mort',
end
apJC = 'apJC',
wikiListe.insert( '[[' .. lien .. '|' .. texte .. ']]' .. julien3 )
nolinks = 'nolinks',
iso.insert( 1, string.sub( '000' .. annee , -4 ) )
compact = 'compact',
end
compacte = 'compact',
}
-- l'age
for n, v in pairs( listeParam ) do
local classAge
params[v] = params[v] or Yesno( args[n], true, false ) or nil
if type( age ) == 'number' and age >= 0 then
end
if age == 0 then

age = '<span class="noprint"> (moins d\'1 an)</span>'
if not params.nolinks then
elseif age == 1 then
local liens = Yesno( args.liens )
age = '<span class="noprint"> (1 an)</span>'
if liens == nil then
else
-- liens actifs par défaut si qualificatif
age = '<span class="noprint"> (' .. age .. ' ans)</span>'
liens = params.qualificatif and params.qualificatif ~= "" and true or false
end
end
classAge = 'class="bday" '
params.nolinks = not liens
else
end
age = ''

classAge = ''
-- sortie pour les tests unitaire, ou pour débugger
end
if args.debug then
return params
-- compilation du résultat
end
local wikiText = wikiListe.concat( '&nbsp;' )

-- On ajoute un peu de sémantique. La date doit être dans le calendrier grégorien proleptique
resultat = fun._modeleDate( params )
-- ce formatage ne s'applique pas avant J.C.

if wikiText ~= '' and ( gannee == nil or gannee > 0) then
else
wikiText = '<time '.. classAge .. 'datetime="' .. iso.concat( '-' ) .. '">' .. wikiText .. '</time>'
local yn_afficherErreurs = Yesno( args.afficherErreurs )
end
if yn_afficherErreurs == nil or yn_afficherErreurs == true then
resultat = params
return wikiText .. age
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
end


function fun._modeleDate( args )
-- voir émule le modèle:Inscription date
local annee, mois, numMois, jour = args.annee, args.mois, args.numMois, args.jour
-- la détection des arguments permet d'utilisé la fonction depuis un modèle, depuis invoke, ou depuis une autre fonction.
local qualificatif = args.qualificatif
-- pour facilité l'écriture de lua, annee (sans accent) est accepté lors de l'appel depuis lua.

function fun.modeleInscriptionDate(frame)
local annee, mois, jour
if ( annee or mois or jour ) == nil then
return
if frame.getParent then
end
annee = frame:getParent().args ['année'] or frame.args ['année'] or frame.args.annee or ''

mois = frame:getParent().args.mois or frame.args.mois or ''
-- on traite l'âge, naissance et mort
jour = frame:getParent().args.jour or frame:getParent().args ['quantième'] or frame.args.jour or ''
local agePrefix = args.agePrefix
else
annee = frame ['année'] or frame.annee or ''
local age = args['âge'] and fun.age( annee, numMois, jour )
local naissance = args.naissance
mois = frame.mois or ''
local mort = args.mort
jour = frame.jour or ''
local evenement = args['événement'] or args.evenement
end
if annee == '' then
if mort and args.anneeNaissanceMort then
age = fun.age( args.anneeNaissanceMort, args.numMoisNaissanceMort, args.jourNaissanceMort, annee, numMois, jour )
-- si annee n'est pas précisé, on utilise la paramètre date
agePrefix = agePrefix or 'à ' -- faut-il mettre \194\160 ?
annee = frame.getParent and (frame:getParent().args.date or frame.args.date) or frame.date or ''
elseif evenement and args.anneeNaissanceMort then
if annee == '' then
if naissance then
return ''
age = fun.age( annee, numMois, jour, args.anneeNaissanceMort, args.numMoisNaissanceMort, args.jourNaissanceMort )
else
else
return '<span class="nowrap">' .. annee .. '</span>'
age = fun.age(args.anneeNaissanceMort, args.numMoisNaissanceMort, args.jourNaissanceMort, annee, numMois, jour )
end
end
else
end
-- si l'année est renseigné, on essaye de trouver le mois
agePrefix = agePrefix or ''
mois = fun.determinationMois (mois, frame)

if mois then
-- on traite le calendrier
mois = mois .. '&nbsp;'
local gannee, gmois, gjour = annee, numMois, jour -- date suivant le calendrier grégorien pour <time>
if jour ~= '' then
local jannee, jmois, jjour = annee, mois, jour -- date suivant le calendrier julien si necessaire
-- si le mois est valide on détermine le jour
local julienDate, julienSup, julienSep -- servira éventuellement à afficher la date selon le calendrier julien
jour = tonumber(jour) or jour -- suppresion des 0 qui trainent
local gregAprMois, gregAprAn, gregFin -- message de calendrier grégorien lorsque la date est selon le calendrier julien
if jour == 1 or jour == '1er' then
local dateRepublicaine
jour = '<abbr class="abbr" title="Premier">1<sup>er</sup></abbr>'
if annee and jour then
end
jour = jour .. '&nbsp;'
local amj = annee * 10000 + numMois * 100 + jour
if amj < 15821014 then
end
if annee > 0 then
return jour .. mois .. annee
gannee, gmois, gjour = fun.julianToGregorian( annee, numMois, jour )
else
else
return annee
-- calendrier grégorien proleptique avec année 0.
end
gannee, gmois, gjour = fun.julianToGregorian( annee + 1, numMois, jour )
end
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="'
.. 'avant Jésus-Christ">av. J.-C.</abbr>'
end
elseif args.apJC then
texteAnnee = texteAnnee .. ' <abbr class="abbr" title="'
.. '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 = (require 'Module:Durée')._duree({ jour1, mois1, annee1, jour2, mois2, annee2, noerror = true })
if duree then
return '<br /><small>(' .. duree .. ')</small>'
end
end
end


---
-- la fonction dateISO renvoie un date au format aaaa-mm-jj (sans liens)
-- 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]]
-- l'année peut être sous la forme 2013 ou [[2013 en litérature|2013]]
-- le mois peut être en lettre ou en chiffres
-- le mois peut être en lettres ou en chiffres
-- le jour peut être sous la forme '05', '{{1er}}' ou 'vendredi 13'
-- le jour peut être sous la forme '05', '{{1er}}' ou 'vendredi 13'
function fun.dateISO( frame )
function fun.dateISO( frame )
local args = Outils.extractArgs( frame )
local annee, mois, jour
local annee = Outils.notEmpty( args['année'], args.annee, args.year, args.date )
if frame.getParent then
-- extraction de l'année
annee = frame:getParent().args ['année'] or frame.args ['année'] or frame.args.annee or ''
if type( annee ) == 'string' then
mois = frame:getParent().args.mois or frame.args.mois or ''
annee = ( tonumber( annee ) -- match '2013'
jour = frame:getParent().args.jour or frame:getParent().args ['quantième'] or frame.args.jour or ''
or string.match ( annee, '%D(%d%d%d%d)%D' ) -- match '[[2013 en musique|2013]]'
else
or string.match ( annee, '%D(%d%d%d%d)$' ) -- match '17 septembre 2013'
annee = frame ['année'] or frame.annee or ''
or string.match ( annee, '^(%d%d%d%d)%D' ) -- match '2013-09-17'
mois = frame.mois or ''
)
jour = frame.jour or ''
end
end
annee = tonumber ( annee ) or string.match ( annee, '%D(-? ?%d?%d?%d%d)%D' )
annee = tonumber( annee )
if annee then
annee = string.sub( '000' .. annee, -4 )
local nomMois, numMois = fun.determinationMois( mois, frame )
if numMois then
if numMois < 10 then
mois = '-0' .. numMois
else
mois = '-' .. numMois
end
local bjour = tonumber( jour ) or tonumber( string.match ( jour, '%D?(%d?%d)%D?') )
if bjour then
if bjour < 10 then
jour = '-0' .. bjour
else
jour = '-' .. bjour
end
else
jour = ''
end
else
mois = ''
jour = ''
end
return annee .. mois .. jour
end
end


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

local rank = (ranks[mt] or 0) + dy - 1
local rank = (ranks[mt] or 0) + dy - 1
if(fun.isLeapYear(yr) and (mt >= 3)) then
if(fun.isLeapYear(yr) and (mt >= 3)) then
Ligne 488 : Ligne 1 284 :
local yr1 = tonumber(arguments[1]) or 0
local yr1 = tonumber(arguments[1]) or 0
local yr2 = tonumber(arguments[2]) or 0
local yr2 = tonumber(arguments[2]) or 0

return fun.daysSinceOrigin(yr2) - fun.daysSinceOrigin(yr1)
return fun.daysSinceOrigin(yr2) - fun.daysSinceOrigin(yr1)
end
end
Ligne 498 : Ligne 1 294 :
end
end


-- Test d'année bissextile
-- Test d'année bissextile (Suit le calendrier grégorien)
function fun.isLeapYear(year)
function fun.isLeapYear(year)
local yr = tonumber(year) or 1
local yr = tonumber(year) or 1
Ligne 514 : Ligne 1 310 :
else
else
for i=1,7,2 do
for i=1,7,2 do
p = pattern[n%10 + 1]
local p = pattern[n%10 + 1]
for j=0,2 do
for j=0,2 do
p = string.gsub(p,tostring(j),letters[i+j])
p = string.gsub(p,tostring(j),letters[i+j])
Ligne 528 : Ligne 1 324 :
function fun.dateRepublicain(frame)
function fun.dateRepublicain(frame)
local pframe = frame:getParent()
local pframe = frame:getParent()
local arguments = pframe.args
local arguments = pframe.args
return fun.formatRepCal(fun.do_toRepCal(arguments))
return fun.formatRepCal(fun.do_toRepCal(arguments))
end
end


---
-- Calcul d'une date dans le calendrier républicain
-- Calcul d'une date dans le calendrier républicain
-- On suppose que les années 4n+3 sont sextiles (3, 7, 11...)
-- On suppose que les années 4n+3 sont sextiles (3, 7, 11...)
Ligne 544 : Ligne 1 341 :
end
end


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


---
-- Voir Modèle:Âge
-- Voir Modèle:Âge
-- retourne l'age en fonction de la ou les dates fournies. La valeur retounée est de type 'number'
-- retourne l'âge en fonction de la ou les dates fournies. La valeur retournée est de type 'number'
-- Parammètres :
-- Paramètres :
-- 1, 2, 3 : année, mois jour de naissance (supposé dans le calendrier grégorien)
-- 1, 2, 3 : année, mois jour de naissance (supposé dans le calendrier grégorien)
-- 4, 5, 6 : année, mois, joue du calcul (facultatif, par défaut la date UTC courante).
-- 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 )
function fun.age( an, mn, jn, ac, mc, jc )
if ac == nil then
local an = tonumber( an )
local today = os.date( '!*t' )
if an == nil then
ac = today.year
-- pas de message d'erreur qui risque de faire planter la fonction appelante
mc = today.month
-- à elle de gérer ce retour.
jc = today.day
return
else
end
ac = tonumber( ac )
-- les jours et mois sont par défaut égal à 1, pour pouvoir calculer un age même si la date est incompète.
local mn = tonumber( mn ) or 1
mc = tonumber( mc )
local jn = tonumber( jn ) or 1
jc = tonumber( jc )
end

local today = os.date( '!*t' )
local ac = tonumber( ac ) or today.year
an = tonumber( an )
local mc = tonumber( mc ) or today.month
mn = tonumber( mn )
local jc = tonumber( jc ) or today.day
jn = tonumber( jn )

if an == nil or ac == nil or mn == nil or mc == nil then
local age = ac - an
-- pas de message d'erreur qui risque de faire planter la fonction appelante
if mc < mn or ( mc == mn and jc < jn ) then
-- à elle de gérer ce retour.
age = age - 1
return
end
end
return age

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
end


function fun.modeleAge( frame )
function fun.modeleAge( frame )
args = frame.getParent().args
local args = Outils.extractArgs( frame )
local annee = args[1] or args['année']
local age = fun.age(
args[1] or args['année'],
args[2] or args['mois'],
if annee == nil then
args[3] or args['jour'],
return erreurDate( "Il faut au minimum l'année pour calculer un âge" )
args[4],
end
args[5],
local age = fun.age (
args[6]
args[1] or args['année'],
)
args[2] or args['mois'],
if age then
args[3] or args['jour'],
return age
args[4],
else
args[5],
return '<span class="error">Paramètres incorrects ou insuffisants pour calculer l\'âge précis</span>'
args[6]
end
)
if age then
return age
else
return erreurDate("les paramètres doivent être des chiffres" )
end
end
end


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


---
-- calcul du jour julien à partir d'une date du calendrier julier
-- calcul du jour julien à partir d'une date du calendrier julien
function fun.julianDayJulian( year, month, day, hour, minute, second )
function fun.julianDayJulian( year, month, day, hour, min, sec )
local julian
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 )
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 + ( minute or 0 ) / 1440 + ( second or 0 ) / 86400
+ day + ( hour or 12 ) / 24 + ( min or 0 ) / 1440 + ( sec or 0 ) / 86400
- 32205.5
- 32205.5
return julian
return julian
end
end


---
-- calcul d'une date dans le calendrier Grégorien à partir du jour julien
-- calcul d'une date dans le calendrier grégorien à partir du jour julien
function fun.jullianDayToGregorian( jd )
function fun.julianDayToGregorian( julianDay )
local base = math.floor( jd + 32044.5 ) -- 1 March -4800 (proleptic Gregorian date)
local nCentury = math.floor( ( base * 4 + 3 ) / 146097 )
local base = math.floor( julianDay + 32044.5 ) -- 1 March -4800 (proleptic Gregorian date)
local sinceCentury = base - math.floor( nCentury * 146097 / 4 )
local nCentury = math.floor( ( base * 4 + 3 ) / 146097 )
local nYear = math.floor( ( sinceCentury * 4 + 3 ) / 1461 )
local sinceCentury = base - math.floor( nCentury * 146097 / 4 )
local sinceYear = sinceCentury - math.floor( nYear * 1461 / 4 )
local nYear = math.floor( ( sinceCentury * 4 + 3 ) / 1461 )
local nMonth = math.floor( ( sinceYear * 5 + 2 ) / 153 )
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 day = sinceYear - math.floor( ( nMonth * 153 + 2 ) / 5 ) + 1
local year = math.floor( sinceYear / 306 ) + nYear + 100 * nCentury - 4800
local month = nMonth - math.floor( nMonth / 10 ) * 12 + 3
local year = math.floor( sinceYear / 306 ) + nYear + 100 * nCentury - 4800

return year, month, day
return year, month, day
end
end


---
function fun.jullianToGregorian( year, month, day )
-- calcul d'une date dans le calendrier julien à partir du jour julien
return fun.jullianDayToGregorian( fun.julianDayJulian( year, month, day ) )
-- 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
end


---
-- erreurModuleData affiche d'un message d'erreur si le Module:Langue/Data n'a pas été chargé correctement,
-- calcul d'une date dans le calendrier grégorien à partir d'une date dans le calendrier julien
-- pour la page de discussion de la base de donnée et ceux qui veulent surveiller cette page.
function fun.erreurModuleData()
function fun.julianToGregorian( year, month, day )
return fun.julianDayToGregorian( fun.julianDayJulian( year, month, day ) )
local success, resultat = pcall ( mw.loadData, 'Module:Date/Data' )
end
if success == false then
local message = [[<strong class="error">Le chargement du module Date/Data génère une erreur : </strong><br />%s<br />


---
<span class="error">Cette erreur doit être corrigée au plus vite car des milliers de page ne s'affichent pas correctement</span>
-- calcul d'une date dans le calendrier julien à partir d'une date dans le calendrier grégorien
]]
function fun.gregorianToJulian( year, month, day )
return string.format( message, resultat )
year = tonumber(year)
end
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
end



function fun.checkDataCat( frame )
--[[
local category = trim(frame.args[1]) or ''
Cette fonction retourne "CET" ou "CEST" selon que dans la pseudo-timezone en cours
local monthLinks = frame .args[2] == 'oui'
c'est l'heure d'été ou l'heure d'hiver.
local all = frame.args[3] == 'oui'
Cette fonction n'a de sens a priori que pour des modèles utilisés en Europe
local dataLink = mw.loadData( 'Module:Date/Data' )

local wikiList = TableBuilder.new()
Paramètre optionnel non nommé : "sans lien" : retourne le texte CET/CEST. sinon
local currentYear = tonumber( os.date( '%Y' ) )
retourne ce même texte avec un wikilien vers les articles correspondants
local columns = '<div style="-moz-column-width:5em;-webkit-column-width:5em;column-width:5em;-moz-column-gap:1em;-webkit-column-gap:1em;column-gap:1em;text-align:left;">'
--]]
local newSection
function fun.CEST(frame)
if monthLink then
-- option : ne pas créer de wikilien
newSection = '\n\n== %s ==\n\n=== Années ===\n' .. columns
local opt = trim(frame.args[1] or frame:getParent().args[1])
else
-- on récupère l'information dans la zone courante
newSection ='\n\n== %s ==\n' .. columns
local t = mw.getContentLanguage():formatDate("I", nil, true)
end

for field, dataField in pairs( dataLink ) do
if (t == "1") then -- heure d'été
-- boucle sur tous les qualificatif ayant pour catégorie le premier paramère
if (opt == "sans lien") then
if dataField.cat == category or ( category == 'cat' and dataField.cat == field ) then
return "CEST"
local monthInitialYear, initialYear
elseif (opt == "décalage") then
-- définition de l'année à partir de laquelle on va tester toutes les année / mois
return "2"
if dataField.qualificatif == field or ( category == 'cat' and dataField.cat == field ) then
else
if dataField.annee and dataField.annee.aucun and dataField.annee.aucun < currentYear then
return "[[Heure d'été d'Europe centrale|CEST]]"
local aucun = dataField.annee.aucun
end
initialYear = math.min( aucun - math.ceil( (currentYear - aucun) / 4 ), 1980 )
else -- heure d'hiver (ou autre zone où ça ne s'applique pas)
else
if (opt == "sans lien") then
initialYear = currentYear - 50
return "CET"
end
elseif (opt == "décalage") then
if dataField.mois and dataField.mois.aucun and dataField.mois.aucun < currentYear then
return "1"
local aucun = dataField.mois.aucun
else
monthInitialYear = math.min( aucun - math.ceil( (currentYear - aucun) / 4 ), currentYear - 8 )
return "[[Heure normale d'Europe centrale|CET]]"
else
end
monthInitialYear = currentYear - 8
end
end
elseif all then
-- si le paramètre 2 est défini on teste aussi tous les alias depuis 1980.
initialYear = currentYear - 50
monthInitialYear = currentYear - 8
end
-- création de l'ensembles des liens
if initialYear then
-- ajout de lien vers les pages annuelles de l'année en court + 5 jusqu'à initialYear
wikiList.insert( string.format( newSection, field ) )
local fieldLink = ' ' .. field
if category == 'cat' then
fieldLink = ' ' .. dataField.qualificatif
end
for year = ( currentYear + 5 ), initialYear, -1 do
wikiList.insert( '\n* [[' .. year .. fieldLink ..'|' .. year .. ']]' )
end
wikiList.insert( '\n</div>' )
if monthLinks then
-- insertstion de liens vers les mois de l'année en court + 1 jusqu'à monthInitialYear
wikiList.insert( '\n\n=== Mois ===' )
local month, sep
for year = ( currentYear + 1 ), monthInitialYear, -1 do
wikiList.insert( '\n* ' .. year .. ' : ' )
sep = ' • '
for j = 1, 12 do
month = ucfirst( liste_mois[j][1] ) .. ' '
if j == 12 then sep = ''
end
wikiList.insert( '[[' .. month .. year .. ' ' .. fieldLink .. '|' .. month .. ']]' .. sep )
end
end
-- insertion de quelques date pour tester les éphémérides
wikiList.insert( '\n\n=== Jours ===' )
wikiList.insert( '\n* [[1er janvier ' .. fieldLink .. ']]' )
wikiList.insert( '\n* [[14 mars ' .. fieldLink .. ']]' )
wikiList.insert( '\n* [[22 juin ' .. fieldLink .. ']]' )
wikiList.insert( '\n* [[3 septembre ' .. fieldLink .. ']]' )
end
end
end
end
return table.concat( wikiList )
end
end



Version du 27 juin 2024 à 12:08

 Documentation[voir] [modifier] [historique] [purger]

Utilisation

Fonctions utilisables depuis un modèle

  • modeleDate(frame) – affiche une date, optionnellement avec les liens les plus pertinents (précisions ci-dessous).
  • dateISO(frame) – similaire à modeleInscriptionDate mais la date est au format aaaa-mm-jj. Paramètres nommés année, mois, jour. Pour respecter l'ISO 8601 qui définit la date uniquement selon le calendrier grégorien, cette fonction ne retourne rien pour les dates avant 1583.
  • dateInfobox(frame) affiche une date avec les liens pertinents, gère correctement les paramètres contenant déjà un modèle date, ou avec du texte suivant la date (précisions ci-dessous). Prévu pour être utilisé dans les Infobox.
  • dureeInfobox(frame) affiche la durée entre deux dates, si elle n'est pas déjà incluse. Prévu pour être utilisé dans les Infobox.
  • dateRepublicain(frame) – affiche une date grégorienne au format républicain (sans liens). Paramètres 1=année, 2=mois, 3=jour.
  • modeleAge(frame) - retourne l'âge (nombre d'années) depuis une date ou entre deux dates. Paramètres 1=année, 2=mois, 3=jour, 4=année, 5=mois, 6=jour.
  • erreurModuleData() - retourne un message d'erreur si Module:Date/Data ne se charge pas correctement.
  • checkDataCat(frame) - retourne une liste de pages annuelle et mensuelle pour faciliter les mises à jour de Date/Data. Paramètres 1=cat, mois=liste de mois si 'oui', alias = liste tous les alias si 'oui'

Fonctions utilisables depuis un autre module

  • determinationMois( mois ) - à partir d'un nom de mois, de son numéro ou d'une abréviation, retourne, si le mois a bien été trouvé, son nom canonique et son numéro
  • determinationSaison( saison ) - à partir d'un nom de saison, retourne, si la saison a bien été trouvée, son nom canonique
  • do_dayRank(arguments) - Rang du jour dans l'année. Paramètre arguments = { année, mois, jour } ou { year = année, month = mois, day = jour }
  • isLeapYear(year) - retourne true si year est une année bissextile dans le calendrier grégorien.
  • toRoman(number) - transforme number en une chaine le représentant en « chiffres romains ».
  • age( an, mn, jn, ac, mc, jc ) - similaire à modeleAge, mais les paramètres ne sont pas dans une table
  • julianDay( year, month, day, hour, minute, second ) - retourne le jour julien de la date transmise, suivant le calendrier grégorien astronomique (avec année 0)
  • julianDayJulian( year, month, day, hour, minute, second ) - retourne le jour julien d'une date du calendrier julien astronomique (avec année 0)
  • julianDayToGregorian( jd ) - retourne trois variables année, mois, jour représentant la date du calendrier grégorien astronomique correspondant à ce jour julien.
  • julianDayToJulian( jd ) - retourne trois variables année, mois, jour représentant la date du calendrier julien correspondant à ce jour julien.
  • julianToGregorian( year, month, day ) - transforme une date du calendrier julien en date du calendrier grégorien.
  • gregorianToJulian( year, month, day ) - transforme une date du calendrier grégorien en date du calendrier julien.

Modules externes dont ce module a besoin pour fonctionner

  • Date/Data - Base de donnée permettant de ne pas tester les pages que l'on sait existantes, ou n'existant pas.
  • TableBuilder – Utilise .insert et .concat pour simplifier la syntaxe.

modeleDate( frame )

Paramètres

  • 1 - jour ou vide - numérique, exception possible pour 1er ou 1er.
  • 2 - mois ou jour - numérique ou nom français ou anglais, éventuellement une abréviation courante.
  • 3 - année ou mois - Un nombre sera considéré comme année. Les années sont considérées comme suivant le calendrier grégorien après le 14 octobre 1582 (sauf si julien = 'oui') et le calendrier julien avant, sans année 0.
  • 4 - qualificatif ou année - texte correspondant à une page type « en photographie » pour « 2008 en photographie »
  • 5 - qualificatif
  • age ou âge - non vide pour afficher l'âge (aucun âge n'est affiché pour les dates dans le futur)
  • julien - 'oui' pour que la date soit considérée comme suivant le calendrier julien après le 14 octobre 1582. La date grégorienne avec liens est affichée suivie de la date julienne entre parenthèses.
  • compact - 'oui' pour abréger le nom du mois.
  • avJC - 'non' pour ne pas afficher 'av. J.-C.' après l'année si elle représente une année avant Jésus-Christ. Utile pour éviter les répétitions.
  • liens - 'oui' pour forcer l'ajout de liens quand la date ne contient pas de qualificatif.
  • nolinks - 'oui' pour empêcher l'ajout de liens (a précédence sur liens).

Fonctionnement

  • par défaut, le modèle n'ajoute des liens que si un qualificatif a été renseigné.
  • le modèle cherche à afficher la date avec des liens vers les pages liées au qualificatif. S'il n'y a pas de page liée au qualificatif un lien sera fait vers la page générale.
  • le premier paramètre est vide et le troisième correspond à un mois (texte uniquement), tous les paramètres sont considérés comme décalés et l'année ne sera pas affichée.
  • s'il n'y a pas de page spécifique pour ce mois-année, le mois sera affiché lié avec le jour à l'éphéméride. Priorité est donnée à l'éphéméride du qualificatif sur le lien mois-année sans qualificatif.
  • le modèle s'aide de la base de donnée Date/Data pour éviter d'utiliser la fonction mw.title (équivalent du parser #ifexist:).
  • cette base permet de remplacer le qualificatif par une catégorie plus générique. Si le qualificatif est « en tennis », l'éphéméride et la page mensuelle sera liée au qualificatif « en sport ».

Fonction modeleDate

Motif testé Chaîne testée Module Fonctions coûteuses
modèle d'avant / module
date récente 14|octobre|2001 1 / 0
date ancienne (1700 - 1943), jour =1 1|octobre|1842 1 / 1
date très ancienne (<1700), jour = 1er 1|janvier|537 1 / 0
qualificatif qui n'est pas dans la base 14|octobre|2010|en animation asiatique 4 / 1
date ancienne, qualificatif qui n'est pas dans la base 14|octobre|1842|en animation asiatique 4 / 2
avec qualificatif 14|Octobre|2001|en astronautique 3 / 0
avec qualificatif avec éphémérides 14|octobre|2005|dans les chemins de fer 4 / 0
pas de jour |octobre|2001 1 / 0
pas de jour avec qualificatif |Octobre|2001|en astronautique 3 / 0
qualificatif avec page annuelle qui pourrait exister 14|octobre|2006|en Égypte 4 / 1
qualificatif avec page mensuelle existante 14|octobre|2008|en France 3 / 0
qualificatif avec page mensuelle qui pourrait exister 14|octobre|2012|en France 4 / 1
qualificatif avec page annuelle et mensuelle qui pourrait exister 14|octobre|2012|en économie 4 / 2
date ancienne avec qualificatif 14|octobre|1845|en aéronautique 4 / 1
date négative 13|octobre|-63 1 / 0
date av. J.-C. (orthographe de la page) 1|octobre|63 av. J.-C. 1 / 0
date avJC (orthographe abrégée) 13|octobre|63 avJC 1 / 0
date négative, paramètre pour cacher av. J.-C. 13|octobre|-63|avJC=non 1 / 0
année invalide 14|octobre|2001 en sport 1 / 0
jour + mois avec majuscule 14|Octobre|2001 1 / 0
mois en abrégé 14|oct.|2001 1 / 0
mois en chiffre 14|10|2001 1 / 0
mois invalide 14|otcobre|2001 Mois invalide (otcobre) 1 / 0
jour invalide jeudi 14|octobre|2001 1 / 0
jour invalide (trop grand pour le mois) 31|septembre|2001 Jour invalide (31 septembre) 1 / 0
uniquement l’année ||2001 1 / 0
uniquement l’année avec qualificatif ||2001|en littérature 1 / 0
sans année 14|octobre 0 / 0
jour uniquement 14 0 / 0
mois uniquement |Octobre Octobre 0 / 0
sans argument 0 / 0
date du calendrier julien 1|octobre|2001|julien=oui 1er octobre 2001 ( dans le calendrier grégorien)
date du calendrier julien (changement de mois) 25|octobre|2001|julien=oui 25 octobre 2001 ( dans le calendrier grégorien)
date du calendrier julien (changement d'année) 25|décembre|2001|julien=oui 25 décembre 2001 ( dans le calendrier grégorien)
date de naissance 14|octobre|2001|age=oui (22 ans)

Comparaison avec {{date de naissance}}

  • les fonctions coûteuses sont les mêmes que celles du modèle Date
  • sans l'âge, voir comparaison avec {{Date}}
Motif testé Chaîne testée Modèle Date de naissance Module
simple 1|8|2006|âge=oui (18 ans) (18 ans)
avec qualificatif 1|août|2006|en Suisse|age=oui (18 ans) (18 ans)
date ancienne 2|1|598|age=oui (1426 ans)
l'an dernier 2|1|2012|age=oui (12 ans) (12 ans)
cette année 2|1|2013|age=oui (11 ans) (11 ans)
l'an prochain 2|1|2014|age=oui (10 ans) (10 ans)
sans jour |8|2006|âge=oui
annee seule ||2006|âge=oui


dateInfobox( frame )

Fonction destinée aux infobox, notamment pour afficher les dates de naissance et de mort. Les dates sont affichées avec des liens. Gère les cas où le paramètre contient déjà un modèle date. Le contenu du paramètre situé après la date (par exemple un lieu, une référence) est conservé.

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 pages 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 vide)
  • préfixe sans jour : préfixe à afficher s'il n'y a pas de jour (par défaut vide)

Ces paramètres doivent être directement dans le #invoke appelant la fonction.

Exemples

  • {{#invoke:Date|dateInfobox|date|13 juillet 1927}}
  • {{#invoke:Date|dateInfobox|naissance|13 juillet 1927|}} (97 ans)
  • {{#invoke:Date|dateInfobox|naissance|13 juillet 1927|14 mai 2017}}
  • {{#invoke:Date|dateInfobox|naissance|30 juin 2017-}} (7 ans)
  • {{#invoke:Date|dateInfobox|mort|13 juillet 1927|30 juin 2017}} (à 89 ans)
  • {{#invoke:Date|dateInfobox|mort||30 juin 2017}}
  • {{#invoke:Date|dateInfobox|mort|13 juillet 1927|}}
  • {{#invoke:Date|dateInfobox|date|13 juillet 1927| qualificatif = en France}}
  • {{#invoke:Date|dateInfobox|date|13 juillet 1927| préfixe = le}} → le
  • {{#invoke:Date|dateInfobox|date|13 juillet 1927| préfixe = le | préfixe sans jour = en}} → le
  • {{#invoke:Date|dateInfobox|date|juillet 1927| préfixe = le}}
  • {{#invoke:Date|dateInfobox|date|juillet 1927| préfixe = le | préfixe sans jour = en}} → en
  • {{#invoke:Date|dateInfobox|date|13 juillet [[1927]]}}
  • {{#invoke:Date|dateInfobox|date|13 juillet [[1927 en France|1927]]}} → 13 juillet 1927
  • {{#invoke:Date|dateInfobox|date|{{date|13 juillet 1927|en France}}}}

dureeInfobox( frame )

Fonction destinée aux infobox, pour afficher la durée entre deux dates, notamment pour un poste ou une fonction. Gère les cas où la durée est déjà spécifiée dans l'une des valeurs. La durée est préfixée d'un retour à la ligne (<br />) et écrite en petit (<small>).

Paramètres

  • 1 : Date de début
  • 2 : Date de fin (optionnelle, date du jour par défaut)

Ces paramètres doivent être directement dans le #invoke appelant la fonction.

Exemples

  • {{#invoke:Date|dureeInfobox|8 septembre 2022}}
    (1 an, 11 mois et 17 jours)
  • {{#invoke:Date|dureeInfobox|{{date|27 juin 1940-}}|{{date|6 septembre 1940}}}}
    (2 mois et 10 jours)

Voir aussi : les tests unitaires et ceux du bac à sable.

-- luacheck: globals mw, no max line length

local fun = {}

local Outils = require 'Module:Outils'
-- 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 = s:gsub( 'É', 'é' ):lower()
		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[str:gsub( 'É', 'é' ):gsub( 'Û', 'û' ):lower()]
			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
		if date:find( '[[', nil, true ) then
			date = date
				-- suppression catégories (doit être exécuté avant le code de suppression des liens)
					:gsub( '%[%[[Cc]atégorie:.-%]%]', '' )
					:gsub( '%[%[[Cc]ategory:.-%]%]', '' )
				-- suppression liens
					:gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
		end
		date = date
			-- suppression balises
				:gsub( '%b<>', '' )
			-- 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 (doit être exécuté après les autres remplacements)
				: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: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="'
						.. 'avant Jésus-Christ">av. J.-C.</abbr>'
				end
			elseif args.apJC then
				texteAnnee = texteAnnee .. ' <abbr class="abbr" title="'
					.. '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 = (require 'Module:Durée')._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