Différences entre les pages « Module:TableBuilder » et « Module:Date »

De Les Mots de l'agronomie
(Différence entre les pages)
imported>Jacques Ducloy
m (1 révision importée)
 
imported>Jacques Ducloy
m (1 révision importée)
 
Ligne 1 : Ligne 1 :
--Module appliquant aux fonctions de la librairie Table une inteface fluide.
+
local fun = {}
  
local meta = {
+
local TableBuilder = require( 'Module:TableBuilder' )
 +
local Outils = require( 'Module:Outils' )
 +
-- 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 = { 1773, 2014 } }, } }
 +
end
  
    insert = function ( t, ... )
+
-- nettoie un paramètre non nommé (vire les espaces au début et à la fin)
        table.insert( t, ... )
+
-- retourne  nil si le texte est vide ou n'est pas du texte. Attention c'est important pour les fonction qui l'utilise.
        return t
+
local trim = Outils.trim
    end,
 
   
 
    remove = function ( t, ... )
 
        table.remove( t, ... )
 
        return t
 
    end,
 
   
 
    sort = function ( t, ... )
 
        table.sort( t, ... )
 
        return t
 
    end,
 
   
 
    maxn = function ( t )
 
        return table.maxn( t )
 
    end,
 
   
 
    concat = function ( t, ... )
 
        return table.concat( t, ... )
 
    end,
 
   
 
    minsert = function( t, ... )
 
        local sel
 
        for i = 1, select( '#', ... ) do
 
            sel = select( i, ... )
 
            if sel then
 
                table.insert( t, sel )
 
            end
 
        end
 
        return t
 
    end,
 
   
 
    tinsert = function( t, tab, first, last )
 
        if type( tab ) == 'table' then
 
            for i = ( tonumber( first ) or 1 ), ( tonumber( last ) or #tab ) do
 
                table.insert( t, tab[i] )
 
            end
 
        end
 
        return t
 
    end,
 
 
  
meta.__index = function ( t, key )
+
-- Fonction destiné à mettre la première lettre du mois en majuscule du mois :
    local metafunc = meta[key]
+
-- utilisation de string car aucun mois ne commance par une lettre non ascii en français ou anglais.  
    if type( metafunc ) == 'function' then
+
local function ucfirst( str )
        return function ( ... ) return metafunc( t, ... ) end
+
return str:sub( 1, 1 ):upper() .. str:sub( 2 )
    end
 
 
end
 
end
       
 
-- fin des fonctions de la meta-table
 
  
 +
local modelePremier = '<abbr class="abbr" title="premier">1<sup>er</sup></abbr>'
  
local TableBuilder = {
 
    new = function( ... )
 
        local t = { ... }
 
        setmetatable( t, meta )
 
        return t
 
    end,
 
   
 
    set = function( t )
 
        if type( t ) == 'table' then
 
            setmetatable( t, meta )
 
        end
 
        return t
 
    end,
 
  
    clone = function ( t )
+
-- liste des mois, écriture exacte et simplifiée, en minuscule
        local tableRefs = { }
+
local liste_mois = {
        local function recursiveClone( val )
+
{ "janvier", "jan.", "janv.", "jan", "janv", "january", nJour = 31 },
            if type( val ) == 'table' then
+
{ "février", "fevrier", "fev.", "fev", "fév.", "fév", "févr", "févr.", "february", nJour = 29 },
                -- Encode circular references correctly
+
{ "mars", "mar.", "mar", "march", nJour = 31 },
                if tableRefs[val] ~= nil then
+
{ "avril", "avr.", "avr", "apr", "april", nJour = 30 },
                    return tableRefs[val]
+
{ "mai", "may", nJour = 31 },
                end
+
{ "juin", "jun", "june", nJour = 30 },
   
+
{ "juillet", "juil.", "juil", "juill.", "juill", "jul", "july", nJour = 31 },
                local retVal = { }
+
{ "août", "aout", "aou", "aug", "august", nJour = 31 },
                tableRefs[val] = setmetatable( retVal, meta )
+
{ "septembre", "sept.", "sept", "sep.", "sep", "september", nJour = 30 },
   
+
{ "octobre", "oct.", "oct", "october", nJour = 31 },
                for key, elt in pairs( val ) do
+
{ "novembre", "nov.", "nov", "november", nJour = 30 },
                    retVal[key] = recursiveClone( elt )
+
{ "décembre", "decembre", "déc.", "dec.", "dec", "déc", "december", nJour = 31 },
                end
 
                return retVal
 
            else
 
                return val
 
            end
 
        end
 
        return recursiveClone( t )
 
    end,
 
 
}
 
}
 +
fun.liste_mois = liste_mois
 +
 +
local liste_saison = {
 +
{ 'printemps', 'spring', },
 +
{ 'été', 'summer', },
 +
{ 'automne', 'autumn', },
 +
{ 'hiver', 'winter', },
 +
}
 +
 +
-- nom du mois à partir du numéro
 +
function fun.nomDuMois( num )
 +
if type( num ) ~= "number" or num < 1 or num > 12 then
 +
return nil
 +
end
 +
return liste_mois[num][1]
 +
end
 +
 +
---
 +
-- valide que la chaîne passée est un mois valide.
 +
-- retourne le nom complet ou nil si non reconnu
 +
-- si reconnu, retourne aussi le numéro du mois [1-12]
 +
function fun.valideMois( mois )
 +
local m = trim( mois )
 +
if m then
 +
m = mw.ustring.lower( m )
 +
for i = 1, 12 do
 +
local j = 1
 +
while liste_mois[i][j] do
 +
if liste_mois[i][j] == m then
 +
return liste_mois[i][1], i
 +
end
 +
j = j + 1
 +
end
 +
end
 +
end
 +
-- pas trouvé = return nil
 +
end
 +
 +
---
 +
-- valide que la chaîne passée est un mois valide.
 +
-- retourne le nom complet ou nil si non reconnu
 +
-- si reconnu, retourne aussi le numéro du mois [1-12]
 +
function fun.valideSaison( saison )
 +
if type( saison ) ~= "string" then
 +
return nil
 +
end
 +
 +
local m = mw.ustring.lower( trim( saison ) )
 +
 +
for i = 1, 4 do
 +
local j = 1
 +
while liste_saison[i][j] ~= nil do
 +
if liste_saison[i][j] == m then
 +
return liste_saison[i][1]
 +
end
 +
j = j + 1
 +
end
 +
end
 +
-- pas trouvé = return nil
 +
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.
 +
-- Si le deuxième paramètre est vrai, les nombres supérieur à 12 ou non entiers sont acceptés. 
 +
function fun.determinationMois( mois, mod, boucle )
 +
local num, nom
 +
if tonumber( mois ) then
 +
num = math.floor( tonumber( mois ) )
 +
if mod then
 +
-- si le nombre du mois est calculé par une exression, le résultat peut être supérieur à 12, ou inférieur à 1
 +
num = math.fmod( num + 239, 12 ) + 1  -- +239 car fmod(-1) = -1 et non 11
 +
elseif num < 1 or num > 12 then
 +
num = nil
 +
end
 +
elseif trim( mois ) then
 +
nom, num = fun.valideMois( mois )
 +
if nom == nil and boucle == nil then
 +
-- essai de détermination d'un nombre avec le parser #expr de Mediawiki.
 +
-- le paramètre boucle évite de tourner en boucle.
 +
nom, num = fun.determinationMois( mw.getCurrentFrame():callParserFunction( '#expr', mois ), true, true )
 +
end
 +
end
 +
if num and not nom then
 +
nom = liste_mois[num][1]
 +
end
 +
return nom, num
 +
end
 +
 +
 +
--  fonction interne à modeleDate, pour déterminer si on peut se passer de faire un ifexit
 +
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 remplacer par celui de la base de donnée, ce qui permet des alias.
 +
local lien = annee .. ' ' .. ( dataQualificatif.qualificatif or '' )
 +
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 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
 +
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'annee 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="Premier" >1<sup>er</sup></abbr>', '1<sup>er</sup>', '1er' }
 +
for i, v in ipairs( nomJour ) do
 +
jour = jour:gsub( v, '' )
 +
end
 +
for i, v in ipairs( premier ) do
 +
jour = jour:gsub( v, '1' )
 +
end
 +
jour = trim( jour )
 +
end
 +
return jour
 +
end
 +
 +
---
 +
-- Sépare une chaine date en un 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, Outils.erreur( periode .. ' invalide (' .. valeur .. ')' )
 +
end
 +
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 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
 +
local regexb = {
 +
jmmm = '^'..j..s..mmm..moins..'$',
 +
mmmjva = '^'..mmm..s..j..', ?'..aj..'$',
 +
}
 +
 +
date = fun.nettoyageJour( date )
 +
-- suppression catégorie, liens, balises
 +
date = mw.ustring.gsub( date, '%[%[[Cc]at[ée]gor[yi]e?:.-%]%]', '' )
 +
date = date :gsub( '%b<>', '' )
 +
:gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
 +
-- suppression des espaces insécables
 +
:gsub( '\194\160', ' ' )
 +
:gsub( '&nbsp;', ' ' )
 +
:gsub( '\226\128\175', ' ' )
 +
:gsub( '&nnbsp;', ' ' )
 +
:gsub( '\226\128\137', ' ' )
 +
:gsub( '&thinsp;', ' ' )
 +
:gsub( '&#32;', ' ' )
 +
:gsub( ' +', ' ' )
 +
-- réduction av. J-C pour simplifier un peu les regex :
 +
:gsub( '(%d+) ?[Aa][Vv]%.? ?[Jj][ .-]*[Cc]%.?', '-%1' )
 +
-- supression de l'heure dans les date 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', date )
 +
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( '^'..j..s..mmm..moins..'$' ) then
 +
-- jj mmmm
 +
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', date )
 +
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
 +
 +
 +
---
 +
-- separationJourMoisAnnee prend jusqu'a cinq paramètre et essaie de les séparer en jour, mois, annee et qualificatif
 +
-- la date peut être dans le premier paramètre ou séparée dans les paramètre 1 à 3 ou 2 à 4.
 +
-- Le qualificatif est cherché dans le paramètre suivant.
 +
-- La fonction retourne true suivit d'une table avec la date en paramètres nommé (sans accent sur année)
 +
-- ou false suivit d'un message d'erreur.
 +
function fun.validationJourMoisAnnee( frame, ... )
 +
local args = Outils.extractArgs( frame, ... )
 +
local jour, mois, numMois, annee, erreur
 +
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, Outils.erreur( periode .. ' invalide (' .. valeur .. ')' )
 +
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 = string.match( string.upper( bannee ), '^(%d+) ?[Aa][Vv]%.? ?[Jj][ .-]*[Cc]%.?' )
 +
annee = tonumber( annee )
 +
if annee then
 +
annee = 0 - annee
 +
else
 +
return erreur( 'Année', bannee )
 +
end
 +
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.valideSaison( bmois )
 +
if mois == nil then
 +
return erreur( 'Mois', bmois )
 +
end
 +
else
 +
-- on traite le jour si présent
 +
if Outils.notEmpty( bjour ) then
 +
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 > liste_mois[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
 +
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, Outils.erreur( 'année improbable (' .. annee .. ')' )
 +
end
 +
 +
local resultat = {
 +
jour = jour,
 +
mois = mois,
 +
numMois = numMois,
 +
annee = annee,
 +
masquerAnnee = args.masquerAnnee,
 +
masquerMois = args.masquerMois,
 +
}
 +
return true, resultat
 +
end
 +
 +
function fun.modeleDateAnalyseJMA( args )
 +
local function masquerParam( p ) -- sépare le signe moins final éventuel signifiant que le paramètre ne soit pas être affiché.
 +
local s
 +
if trim( p ) then
 +
p, s = p:match( '^(.-)(%-?)%s*$' )
 +
end
 +
return p, ( s == '-' or nil )
 +
end
 +
 +
local test, resultat
 +
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, resultat = fun.separationJourMoisAnnee( arg1 )
 +
if test then
 +
resultat.qualificatif = arg2
 +
end
 +
else
 +
local param, masquerM, masquerA
 +
param = { args[1] or args.jour }
 +
param[2], masquerM = masquerParam( args[2] or args.mois )
 +
param[3], masquerA = masquerParam( args[3] or args.annee or args['annee'] )
 +
param[4] = masquerParam( args[4] )
 +
test, resultat = fun.validationJourMoisAnnee( param )
 +
if test then
 +
resultat.masquerAnnee = masquerA
 +
resultat.masquerMois = masquerM
 +
resultat.qualificatif = trim( args[4] )
 +
end
 +
end
 +
 +
return test, resultat
 +
end
 +
 +
---
 +
-- émule le modèle {{m|Date}}.
 +
-- Paramètres :
 +
-- 1 : jour (numéro ou "1er"). optionnel, si absent pas de jour
 +
-- 2 : mois (en toutes lettres)
 +
-- 3 : année (nombre)
 +
-- 4 : optionnel, spécialité de l'année
 +
-- Comportement spécial ("truc à deux balles au lieu d'utiliser un
 +
-- paramètre nommé du genre "sans année=oui"...") : si 1 est vide
 +
-- mais que le reste est complet → on n'affiche pas l'année
 +
function fun.modeleDate( frame )
 +
local args = Outils.extractArgs( frame )
 +
local test, params = fun.modeleDateAnalyseJMA( args )
 +
local cat, resultat = ''
 +
if test then
 +
local listeParam = {
 +
qualificatif = 'qualificatif',
 +
age = 'âge',
 +
['âge'] = 'âge',
 +
naissance = 'naissance',
 +
mort = 'mort',
 +
['décès'] = 'mort',
 +
julien = 'julien',
 +
avJC = 'avJC',
 +
nolinks = 'nolinks',
 +
}
 +
for n, v in pairs( listeParam ) do
 +
params[v] = params[v] or args[n]
 +
end
 +
resultat = fun._modeleDate( params )
 +
 +
else
 +
local namespaceCategorisation = { [0] = true, [4] = true, [10] = true, [14] = true, [100] = true }
 +
if namespaceCategorisation[ mw.title.getCurrentTitle().namespace ] and not Outils.notEmpty( args.nocat ) then
 +
cat = '[[Catégorie:Page utilisant le modèle date avec une syntaxe erronée]]'
 +
end
 +
resultat = params .. cat
 +
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'age, naissance et mort
 +
local age = trim( args['âge'] or args['age'] )
 +
age = age and  fun.age( annee, numMois, jour )
 +
local naissance = trim( args.naissance )
 +
local mort = trim( args.mort )
 +
 +
-- 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  -- servira éventuellement à a affiché la date selon le calendrier julien
 +
local julien2, julien3 = nil, nil                        -- servira éventuellement à a affiché des parenthèses
 +
local julien = trim( string.lower( args.julien or '' ) )
 +
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
 +
elseif julien == 'oui' then
 +
gannee, gmois, gjour = fun.julianToGregorian( annee, numMois, jour )
 +
annee, mois, jour = gannee, liste_mois[gmois][1], gjour
 +
end
 +
else
 +
if annee and annee < 0 then
 +
gannee = gannee + 1
 +
end
 +
end
 +
 +
 +
-- on génère le résultat
 +
 +
-- Déclarations des variables
 +
local wikiListe = TableBuilder.new()  -- reçois le texte affiché pour chaque paramètre
 +
local iso = TableBuilder.new()        -- reçois le format date ISO de ce paramètre
 +
 +
local dataQualificatif, dataCat
 +
if not args.nolinks then
 +
dataQualificatif = dataLiens[qualificatif or '']
 +
if type( dataQualificatif ) ~= 'table' then
 +
-- 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
 +
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
 +
 +
-- Date julienne
 +
if jjour ~= jour then
 +
if jjour == 1 then
 +
jjour = '<abbr class="abbr" title="premier">1<sup>er</sup></abbr>'
 +
end
 +
if jannee ~= annee then
 +
julien3 = '(<abbr class=abbr title="selon le calendrier julien">' .. jjour .. ' ' .. jmois .. ' ' .. jannee .. '</abbr>)'
 +
else
 +
julien2 = '(<abbr class=abbr title="selon le calendrier julien">' .. jjour .. ' ' .. jmois .. '</abbr>)'
 +
end
 +
end
 +
 +
 +
-- le jour si présent
 +
local qualifJour = ''
 +
if jour then
 +
local texteJour = jour
 +
if args.nolinks then
 +
if jour == 1 then
 +
jour = modelePremier
 +
end
 +
wikiListe.insert( jour )
 +
else
 +
qualifJour = dataQualificatif.jour and dataQualificatif.qualificatif
 +
or dataCat.jour and dataCat.qualificatif
 +
or ''
 +
local lien = jour .. ' ' .. mois .. ' ' .. qualifJour
 +
if jour == 1 then
 +
jour = '1<sup>er</sup>'
 +
lien = '1er ' .. mois .. ' ' .. qualifJour
 +
end
 +
-- s'il n'y a pas de lien sur le mois, il sera affiché avec le jour.
 +
wikiListe.insert( wikiLien( lien, jour ) )
 +
wikiListe.insert( wikiLien( lien, jour .. ' '.. mois ) )
 +
end
 +
iso.insert( 1, string.sub( '0' .. gjour, -2 ) )
 +
end
 +
 +
-- le mois
 +
if mois then
 +
if #wikiListe == 0 and annee == nil then
 +
return mois
 +
end
 +
if args.nolinks then
 +
if not args.masquerMois then
 +
wikiListe.insert( mois )
 +
end
 +
else
 +
local lien
 +
if annee then
 +
lien = existDate( dataQualificatif, annee, mois ) or existDate( dataCat, annee, mois )
 +
if lien == nil and qualificatif and qualifJour == '' then
 +
-- test 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
 +
if lien or args.masquerMois then
 +
-- s'il y a un lien on retire le lien affichant 'jour mois' pour ajouter '[[mois annee|mois']]
 +
wikiListe.remove()
 +
if not args.masquerMois then
 +
wikiListe.insert( wikiLien( lien, mois ) )
 +
end
 +
elseif #wikiListe > 0 then
 +
-- sinon on retire le lien affichant 'jour' pour ne garder que le lien 'jour mois'
 +
wikiListe.remove( #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.
 +
wikiListe.insert( mois )
 +
end
 +
end
 +
if gmois then
 +
iso.insert( 1, string.sub( '0' .. gmois, -2 ) )
 +
end
 +
end
 +
if( julien2 ) then
 +
wikiListe.insert( julien2 )
 +
end
 +
 +
-- l'année
 +
if annee then
 +
if not args.masquerAnnee then
 +
local texteAnnee = annee
 +
local lien
 +
if annee < 0 then
 +
local annneeAvJc = 0 - annee
 +
lien = lien or ( annneeAvJc .. ' av. J.-C.' )
 +
local avJC = trim( string.lower( args.avJC or '' ) )
 +
if args.avJC == 'non' then
 +
texteAnnee = annneeAvJc
 +
else
 +
texteAnnee = annneeAvJc .. ' <abbr class="abbr" title="'
 +
.. annneeAvJc .. ' avant Jésus-Christ">av. J.-C.</abbr>'
 +
end
 +
end
 +
if args.nolinks then -- seulement si on doit l'affichée
 +
wikiListe.insert( texteAnnee )
 +
else
 +
lien = existDate( dataQualificatif, annee ) or existDate( dataCat, annee ) or lien or annee
 +
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 = mois .. ' ' .. texteAnnee
 +
end
 +
wikiListe.insert( wikiLien( lien, texteAnnee ) )
 +
end
 +
end
 +
if gannee > 999 then
 +
iso.insert( 1, gannee )
 +
elseif gannee > -1 then
 +
iso.insert( 1, string.sub( '000' .. gannee , -4 ) )
 +
elseif gannee > -999 then
 +
-- calendrier grégorien proleptique avec année 0.
 +
iso.insert( 1, 'U-' .. string.sub( '000' .. ( 0 - gannee ), -4 ) )
 +
else
 +
iso.insert( 1, 'U' .. gannee )
 +
end
 +
end
 +
if( julien3 ) then
 +
wikiListe.insert( julien3 )
 +
end
 +
 +
 +
-- l'age
 +
if type( age ) == 'number' and age >= 0 and ( not naissance or age < 120 ) then
 +
if age == 0 then
 +
age = '(moins d’un&nbsp;an)'
 +
elseif age == 1 then
 +
age = '(1&nbsp;an)'
 +
else
 +
age = '(' .. age .. '&nbsp;ans)'
 +
end
 +
else
 +
age = false
 +
end
 +
 +
 +
-- compilation du résultat
 +
local wikiTexte = wikiListe.concat( ' ' )
 +
local isoTexte = iso.concat( '-' )
 +
 +
-- On ajoute un peu de sémantique.
 +
local wikiHtml = mw.html.create( '' )
 +
 +
local dateHtml = wikiHtml:tag( 'time' )
 +
:wikitext( wikiTexte )
 +
if wikiTexte:match( ' ' ) then
 +
dateHtml:addClass( 'nowrap' )
 +
end
 +
if isoTexte ~= wikiTexte then
 +
dateHtml:attr( 'datetime', 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
 +
if age then
 +
wikiHtml:wikitext( ' ' )
 +
:tag( 'span' )
 +
:addClass( 'noprint')
 +
:wikitext( age )
 +
:done()
 +
end
 +
 +
return tostring( wikiHtml )
 +
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ées pour gérer les cas ou
 +
-- le paramètre contient déjà un modèle date.
 +
-- Paramètres :
 +
-- 1 : type de date à afficher (naissance / n, mort / m, ou date / d)
 +
-- 1 : Date ou date de naissance
 +
-- 2 : Date de mort si type n ou m
 +
-- qualificatif = suffixe des page de date à lier (exemple : en musique)
 +
-- nolinks : n'affiche pas de lien
 +
function fun.dateInfobox( frame )
 +
local args = frame.args
 +
if type( args ) ~= 'table' or not ( args[1] and args[2] ) then
 +
return
 +
end
 +
 +
local function analyseDate( d )
 +
if trim( d ) then
 +
-- supprime les liens
 +
local analyse = d:match( ' ou ') or d:match( 'entre ' ) or d:match( 'vers ' ) or d:match( 'après ' ) or d:match( 'avant ' )
 +
if analyse then
 +
return d
 +
end
 +
analyse = d:match( 'datetime="([%d-]+)"' ) or d
 +
local debut, fin = analyse:match( '(.-%d%d%d%]*%-?)([\127 ].+)' )
 +
if not debut then
 +
debut, fin = analyse:match( '(.-%d%d%d%]*%-?)(<br ?/?>.+)' )
 +
end
 +
analyse = debut or analyse
 +
analyse = analyse:gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
 +
local t, r = fun.separationJourMoisAnnee( analyse )
 +
if t then
 +
return r, fin
 +
else
 +
return d, fin
 +
end
 +
end
 +
end
 +
 +
local naissance = args[1]:match( '^n' )
 +
local mort = args[1]:match( '^m' ) or args[1]:match( 'décès' )
 +
local affichageDate, qualificatif = args[2], args[4]
 +
local affichageDateTab, resultatDate, complementDate
 +
local dateNaissance, dateMort
 +
if mort then
 +
affichageDate = args[3]
 +
end
 +
if not trim( affichageDate ) then
 +
return
 +
end
 +
if 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 ) and ( affichageDate:match( 'wikidata%-linkback' ))  then
 +
dateNaissance = analyseDate( args[2] )
 +
dateMort = analyseDate( args[3] )
 +
resultatDate = affichageDate
 +
else
 +
return affichageDate
 +
end
 +
else
 +
affichageDateTab, complementDate = analyseDate( affichageDate )
 +
if type( affichageDateTab ) ~= 'table' then
 +
return affichageDateTab
 +
else
 +
if naissance then
 +
dateNaissance = affichageDateTab
 +
dateMort = analyseDate( args[3] )
 +
elseif mort then
 +
dateNaissance = analyseDate( args[2] )
 +
dateMort = affichageDateTab
 +
else
 +
qualificatif = args[3]
 +
end
 +
affichageDateTab.naissance = naissance
 +
affichageDateTab.mort = mort
 +
affichageDateTab.qualificatif = args.qualificatif or qualificatif
 +
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 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 and dateNaissance and dateMort 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 .. '&nbsp;ans' .. suffixAge
 +
elseif calculAge == 1 then
 +
age = prefixAge .. 'un&nbsp;an' .. suffixAge
 +
elseif calculAge == 0 then
 +
age = prefixAge .. 'moins d’un&nbsp;an' .. suffixAge
 +
end
 +
if complementDate and complementDate:match( 'ans?%)' ) then
 +
complementDate = ''
 +
end
 +
end
 +
 +
return resultatDate .. ( complementDate or '' ) .. age
 +
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 lettre 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 <= liste_mois[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]) .. "&nbsp;" .. 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'age en fonction de la ou les dates fournies. La valeur retounée est de type 'number'
 +
-- Parammètres :
 +
-- 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).
 +
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
 +
 +
local an = tonumber( an )
 +
local mn = tonumber( mn )
 +
local 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
 +
if mc == mn then
 +
if jc == nil or jn == nil then
 +
return
 +
end
 +
return age-tonumber( jc < jn and 1 or 0 )
 +
else
 +
return age-tonumber( mc < mn and 1 or 0 )
 +
end
 +
end
 +
 +
function fun.modeleAge( frame )
 +
local args = frame.getParent().args
 +
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 Outils.erreur("Paramètres incorrects ou insuffisants ou pour calculer l'âge précis" )
 +
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'algorythme de la page fr.wikipedia.org/wiki/Jour_julien (1/10/2013)
 +
function fun.julianDayToJulian( julianDay )
 +
local year = math.modf( ( julianDay * 4 - 6884469 ) / 1461 )
 +
local r2 = julianDay - math.modf( ( 1461 * year + 6884472 ) / 4 )
 +
local month = math.modf( ( 5 * r2 + 461 ) / 153 )
 +
local day = r2 - math.modf( ( 153 * month - 457 ) / 5 ) + 1
 +
if month > 12 then
 +
year = year + 1
 +
month = month - 12
 +
end
 +
return year, month, day
 +
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
 +
 +
 +
 +
---
 +
-- erreurModuleData affiche  d'un message d'erreur si le Module:Langue/Data n'a pas été chargé correctement,
 +
-- pour la page de discussion de la base de donnée et ceux qui veulent surveiller cette page.
 +
function fun.erreurModuleData()
 +
local success, resultat = pcall ( mw.loadData, 'Module:Date/Data' )
 +
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>
 +
]]
 +
return string.format( message, resultat )
 +
end
 +
end
 +
 +
---
 +
-- checkDataCat génère des liens vers les pages annuelles, mensuelles et d'éphémérides liè aux
 +
-- catégories du Module:Date/Data. La date la plus ancienne dépend de 'aucun' et 'seul[1]'
 +
-- Paramètres :
 +
-- 1 : la catégorie. Il y aura une section par qualificatif de cette catégorie.
 +
-- mois : oui pour avoir les liens vers les pages mensuelles et éphémérides (4 jours dans l'année)
 +
-- alias : pour avoir des lien pour les alias en plus des qualificatif
 +
function fun.checkDataCat( frame )
 +
local category = trim(frame.args[1])
 +
local monthLinks = frame .args.mois == 'oui'
 +
local alias = frame.args.alias == 'oui'
 +
local dataLink = mw.loadData( 'Module:Date/Data' )
 +
local wikiList =  TableBuilder.new()
 +
local currentYear = tonumber( os.date( '%Y' ) )
 +
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
 +
if monthLinks then
 +
newSection = '\n\n== %s ==\n\n=== Années ===\n' .. columns
 +
else
 +
newSection ='\n\n== %s ==\n' .. columns
 +
end
 +
for field, dataField in pairs( dataLink ) do
 +
-- boucle sur tous les qualificatif ayant pour catégorie le premier paramère
 +
if dataField.cat == category or ( category == 'cat' and dataField.cat == field ) then
 +
local monthInitialYear, initialYear
 +
-- définition de l'année à partir de laquelle on va tester toutes les année / mois
 +
if dataField.qualificatif == field or ( category == 'cat' and dataField.cat == field ) then
 +
if dataField.annee and dataField.annee.aucun and dataField.annee.aucun < currentYear then
 +
local aucun = ( dataField.annee.seul and dataField.annee.seul[1] ) or dataField.annee.aucun
 +
initialYear = math.min( aucun - math.ceil( (currentYear - aucun) / 4 ), currentYear - 50 )
 +
else
 +
initialYear = currentYear - 50
 +
end
 +
if dataField.mois and tonumber( dataField.mois.aucun ) and ( tonumber( dataField.mois.aucun ) < currentYear ) then
 +
local aucun = dataField.mois.aucun
 +
monthInitialYear = math.min( aucun - math.ceil( (currentYear - aucun) / 4 ), currentYear - 8 )
 +
else
 +
monthInitialYear = currentYear - 8
 +
end
 +
elseif alias then
 +
-- si le paramètre alias est défini on teste aussi tous les alias, sinon ils sont ignorés
 +
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
 +
 +
--[[
 +
  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 correspondant
 +
--]]
 +
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 TableBuilder
+
return fun

Version actuelle datée du 7 décembre 2020 à 17:43

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

local fun = {}

local TableBuilder = require( 'Module:TableBuilder' )
local Outils = require( 'Module:Outils' )
-- 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 = { 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 fonction qui l'utilise.
local trim = Outils.trim

-- Fonction destiné à mettre la première lettre du mois en majuscule du mois : 
-- utilisation de string car aucun mois ne commance 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 simplifiée, en minuscule
local liste_mois = {
	{ "janvier", "jan.", "janv.", "jan", "janv", "january", nJour = 31 },
	{ "février", "fevrier", "fev.", "fev", "fév.", "fév", "févr", "févr.", "february", nJour = 29 },
	{ "mars", "mar.", "mar", "march", nJour = 31 },
	{ "avril", "avr.", "avr", "apr", "april", nJour = 30 },
	{ "mai", "may", nJour = 31 },
	{ "juin", "jun", "june", nJour = 30 },
	{ "juillet", "juil.", "juil", "juill.", "juill", "jul", "july", nJour = 31 },
	{ "août", "aout", "aou", "aug", "august", nJour = 31 },
	{ "septembre", "sept.", "sept", "sep.", "sep", "september", nJour = 30 },
	{ "octobre", "oct.", "oct", "october", nJour = 31 },
	{ "novembre", "nov.", "nov", "november", nJour = 30 },
	{ "décembre", "decembre", "déc.", "dec.", "dec", "déc", "december", nJour = 31 },
}
fun.liste_mois = liste_mois

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

-- nom du mois à partir du numéro
function fun.nomDuMois( num )
	if type( num ) ~= "number" or num < 1 or num > 12 then
		return nil
	end
	return liste_mois[num][1]
end

---
-- valide que la chaîne passée est un mois valide.
-- retourne le nom complet ou nil si non reconnu
-- si reconnu, retourne aussi le numéro du mois [1-12]
function fun.valideMois( mois )
	local m = trim( mois )
	if m then
		m = mw.ustring.lower( m )
		for i = 1, 12 do
			local j = 1
			while liste_mois[i][j] do
				if liste_mois[i][j] == m then
					return liste_mois[i][1], i
				end
				j = j + 1
			end
		end
	end
	-- pas trouvé = return nil
end

---
-- valide que la chaîne passée est un mois valide.
-- retourne le nom complet ou nil si non reconnu
-- si reconnu, retourne aussi le numéro du mois [1-12]
function fun.valideSaison( saison )
	if type( saison ) ~= "string" then
		return nil
	end
	
	local m = mw.ustring.lower( trim( saison ) )
	
	for i = 1, 4 do
		local j = 1
		while liste_saison[i][j] ~= nil do
			if liste_saison[i][j] == m then
				return liste_saison[i][1]
			end
			j = j + 1
		end
	end
	-- pas trouvé = return nil
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.
-- Si le deuxième paramètre est vrai, les nombres supérieur à 12 ou non entiers sont acceptés.  
function fun.determinationMois( mois, mod, boucle )
	local num, nom
	if tonumber( mois ) then
		num = math.floor( tonumber( mois ) )
		if mod then			
			-- si le nombre du mois est calculé par une exression, le résultat peut être supérieur à 12, ou inférieur à 1
			num = math.fmod( num + 239, 12 ) + 1  -- +239 car fmod(-1) = -1 et non 11 
		elseif num < 1 or num > 12 then
			num = nil
		end
	elseif trim( mois ) then
		nom, num = fun.valideMois( mois )
		if nom == nil and boucle == nil then
			-- essai de détermination d'un nombre avec le parser #expr de Mediawiki.
			-- le paramètre boucle évite de tourner en boucle.
			nom, num = fun.determinationMois( mw.getCurrentFrame():callParserFunction( '#expr', mois ), true, true )
		end
	end
	if num and not nom then
		nom = liste_mois[num][1]
	end
	return nom, num
end


--  fonction interne à modeleDate, pour déterminer si on peut se passer de faire un ifexit
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 remplacer par celui de la base de donnée, ce qui permet des alias.
	local lien = annee .. ' ' .. ( dataQualificatif.qualificatif or '' )
	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 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
			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'annee 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="Premier" >1<sup>er</sup></abbr>', '1<sup>er</sup>', '1er' }
		for i, v in ipairs( nomJour ) do
			jour = jour:gsub( v, '' )
		end
		for i, v in ipairs( premier ) do
			jour = jour:gsub( v, '1' )
		end
		jour = trim( jour )
	end
	return jour
end

---
-- Sépare une chaine date en un 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, Outils.erreur( periode .. ' invalide (' .. valeur .. ')' )
		end
		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 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
		local regexb = {
			jmmm = '^'..j..s..mmm..moins..'$',
			mmmjva = '^'..mmm..s..j..', ?'..aj..'$',
		}
		
		date = fun.nettoyageJour( date )
		-- suppression catégorie, liens, balises
		date = mw.ustring.gsub( date, '%[%[[Cc]at[ée]gor[yi]e?:.-%]%]', '' ) 
		date = date	:gsub( '%b<>', '' )
					:gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
		-- suppression des espaces insécables
					:gsub( '\194\160', ' ' )
					:gsub( '&nbsp;', ' ' )
					:gsub( '\226\128\175', ' ' )
					:gsub( '&nnbsp;', ' ' )
					:gsub( '\226\128\137', ' ' )
					:gsub( '&thinsp;', ' ' )
					:gsub( '&#32;', ' ' )
					:gsub( ' +', ' ' )
		-- réduction av. J-C pour simplifier un peu les regex :
					:gsub( '(%d+) ?[Aa][Vv]%.? ?[Jj][ .-]*[Cc]%.?', '-%1' )
		-- supression de l'heure dans les date 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', date )
			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( '^'..j..s..mmm..moins..'$' ) then
			-- jj mmmm
			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', date )
		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


---
-- separationJourMoisAnnee prend jusqu'a cinq paramètre et essaie de les séparer en jour, mois, annee et qualificatif
-- la date peut être dans le premier paramètre ou séparée dans les paramètre 1 à 3 ou 2 à 4.
-- Le qualificatif est cherché dans le paramètre suivant.
-- La fonction retourne true suivit d'une table avec la date en paramètres nommé (sans accent sur année)
-- ou false suivit d'un message d'erreur.
function fun.validationJourMoisAnnee( frame, ... )
	local args = Outils.extractArgs( frame, ... )
	local jour, mois, numMois, annee, erreur
	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, Outils.erreur( periode .. ' invalide (' .. valeur .. ')' )
	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 = string.match( string.upper( bannee ), '^(%d+) ?[Aa][Vv]%.? ?[Jj][ .-]*[Cc]%.?' )
			annee = tonumber( annee )
			if annee then
				annee = 0 - annee
			else
				return erreur( 'Année', bannee )
			end
		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.valideSaison( bmois )
			if mois == nil then
				return erreur( 'Mois', bmois )
			end
		else		
			-- on traite le jour si présent
			if Outils.notEmpty( bjour ) then
				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 > liste_mois[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
		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, Outils.erreur( 'année improbable (' .. annee .. ')' )
	end
		
	local resultat = {
		jour = jour,
		mois = mois,
		numMois = numMois,
		annee = annee,
		masquerAnnee = args.masquerAnnee, 
		masquerMois = args.masquerMois,
	}
	return true, resultat
end

function fun.modeleDateAnalyseJMA( args )
	local function masquerParam( p )	-- sépare le signe moins final éventuel signifiant que le paramètre ne soit pas être affiché.
		local s
		if trim( p ) then
			p, s = p:match( '^(.-)(%-?)%s*$' )
		end
		return p, ( s == '-' or nil )
	end
	
	local test, resultat
	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, resultat = fun.separationJourMoisAnnee( arg1 )
		if test then
			resultat.qualificatif = arg2
		end
	else
		local param, masquerM, masquerA
		param = { args[1] or args.jour }
		param[2], masquerM = masquerParam( args[2] or args.mois )
		param[3], masquerA = masquerParam( args[3] or args.annee or args['annee'] )
		param[4] = masquerParam( args[4] )
		test, resultat = fun.validationJourMoisAnnee( param )
		if test then
			resultat.masquerAnnee = masquerA
			resultat.masquerMois = masquerM
			resultat.qualificatif = trim( args[4] )
		end
	end
	
	return test, resultat
end

---
-- émule le modèle {{m|Date}}.
-- Paramètres :
--		1 : jour (numéro ou "1er"). optionnel, si absent pas de jour
--		2 : mois (en toutes lettres)
--		3 : année (nombre)
--		4 : optionnel, spécialité de l'année
--		Comportement spécial ("truc à deux balles au lieu d'utiliser un
--		paramètre nommé du genre "sans année=oui"...") : si 1 est vide
--		mais que le reste est complet → on n'affiche pas l'année
function fun.modeleDate( frame )
	local args = Outils.extractArgs( frame )
	local test, params = fun.modeleDateAnalyseJMA( args )
	local cat, resultat = ''
	if test then
		local listeParam = {
			qualificatif = 'qualificatif',
			age = 'âge',
			['âge'] = 'âge',
			naissance = 'naissance',
			mort = 'mort',
			['décès'] = 'mort',
			julien = 'julien',
			avJC = 'avJC',
			nolinks = 'nolinks',
		}
		for n, v in pairs( listeParam ) do
			params[v] = params[v] or args[n]
		end
		resultat = fun._modeleDate( params )

	else
		local namespaceCategorisation = { [0] = true, [4] = true, [10] = true, [14] = true, [100] = true }
		if namespaceCategorisation[ mw.title.getCurrentTitle().namespace ] and not Outils.notEmpty( args.nocat ) then
			cat = '[[Catégorie:Page utilisant le modèle date avec une syntaxe erronée]]'
		end
		resultat = params .. cat
	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'age, naissance et mort
	local age = trim( args['âge'] or args['age'] )
	age = age and  fun.age( annee, numMois, jour )
	local naissance = trim( args.naissance )
	local mort = trim( args.mort )
	
	-- 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   -- servira éventuellement à a affiché la date selon le calendrier julien
	local julien2, julien3 = nil, nil                        -- servira éventuellement à a affiché des parenthèses
	local julien = trim( string.lower( args.julien or '' ) )
	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
		elseif julien == 'oui' then
			gannee, gmois, gjour = fun.julianToGregorian( annee, numMois, jour )
			annee, mois, jour = gannee, liste_mois[gmois][1], gjour
		end
	else
		if annee and annee < 0 then
			gannee = gannee + 1
		end
	end
	
	
	-- on génère le résultat
	
	-- Déclarations des variables
	local wikiListe = TableBuilder.new()   -- reçois le texte affiché pour chaque paramètre
	local iso = TableBuilder.new()         -- reçois le format date ISO de ce paramètre
	
	local dataQualificatif, dataCat
	if not args.nolinks then
		dataQualificatif = dataLiens[qualificatif or '']
		if type( dataQualificatif ) ~= 'table' then
			-- 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
		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
	
	-- Date julienne
	if jjour ~= jour then
		if jjour == 1 then
			jjour = '<abbr class="abbr" title="premier">1<sup>er</sup></abbr>'
		end
		if jannee ~= annee then
			julien3 = '(<abbr class=abbr title="selon le calendrier julien">' .. jjour .. ' ' .. jmois .. ' ' .. jannee .. '</abbr>)'
		else
			julien2 = '(<abbr class=abbr title="selon le calendrier julien">' .. jjour .. ' ' .. jmois .. '</abbr>)'
		end
	end
	

	-- le jour si présent
	local qualifJour = ''
	if jour then
		local texteJour = jour
		if args.nolinks then
			if jour == 1 then
				jour = modelePremier
			end
			wikiListe.insert( jour )
		else
			qualifJour = dataQualificatif.jour and dataQualificatif.qualificatif
				or dataCat.jour and dataCat.qualificatif
				or ''
			local lien = jour .. ' ' .. mois .. ' ' .. qualifJour
			if jour == 1 then
				jour = '1<sup>er</sup>'
				lien = '1er ' .. mois .. ' ' .. qualifJour
			end
			-- s'il n'y a pas de lien sur le mois, il sera affiché avec le jour.
			wikiListe.insert( wikiLien( lien, jour ) )
			wikiListe.insert( wikiLien( lien, jour .. ' '.. mois ) )
		end
		iso.insert( 1, string.sub( '0' .. gjour, -2 ) )
	end
	
	-- le mois
	if mois then
		if #wikiListe == 0 and annee == nil then
			return mois
		end
		if args.nolinks then
			if not args.masquerMois then 
				wikiListe.insert( mois )
			end
		else
			local lien
			if annee then
				lien = existDate( dataQualificatif, annee, mois ) or existDate( dataCat, annee, mois )
				if lien == nil and qualificatif and qualifJour == '' then
					-- test 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
			if lien or args.masquerMois then
				-- s'il y a un lien on retire le lien affichant 'jour mois' pour ajouter '[[mois annee|mois']]
				wikiListe.remove()
				if not args.masquerMois then
					wikiListe.insert( wikiLien( lien, mois ) )
				end
			elseif #wikiListe > 0 then
				-- sinon on retire le lien affichant 'jour' pour ne garder que le lien 'jour mois'
				wikiListe.remove( #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.
				wikiListe.insert( mois )
			end
		end
		if gmois then
			iso.insert( 1, string.sub( '0' .. gmois, -2 ) )
		end
	end
	if( julien2 ) then
		wikiListe.insert( julien2 )
	end
	
	-- l'année
	if annee then
		if not args.masquerAnnee then
			local texteAnnee = annee
			local lien
			if annee < 0 then
				local annneeAvJc = 0 - annee
				lien = lien or ( annneeAvJc .. ' av. J.-C.' )
				local avJC = trim( string.lower( args.avJC or '' ) )
				if args.avJC == 'non' then
					texteAnnee = annneeAvJc
				else
					texteAnnee = annneeAvJc .. ' <abbr class="abbr" title="'
						.. annneeAvJc .. ' avant Jésus-Christ">av. J.-C.</abbr>'
				end
			end
			if args.nolinks then -- seulement si on doit l'affichée
				wikiListe.insert( texteAnnee )
			else
				lien = existDate( dataQualificatif, annee ) or existDate( dataCat, annee ) or lien or annee			
				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 = mois .. ' ' .. texteAnnee
				end
				wikiListe.insert( wikiLien( lien, texteAnnee ) )
			end
		end
		if gannee > 999 then
			iso.insert( 1, gannee )
		elseif gannee > -1 then
			iso.insert( 1, string.sub( '000' .. gannee , -4 ) )
		elseif gannee > -999 then
			-- calendrier grégorien proleptique avec année 0.
			iso.insert( 1, 'U-' .. string.sub( '000' .. ( 0 - gannee ), -4 ) )
		else
			iso.insert( 1, 'U' .. gannee )				
		end
	end
	if( julien3 ) then
		wikiListe.insert( julien3 )
	end

		
	-- l'age
	if type( age ) == 'number' and age >= 0 and ( not naissance or age < 120 ) then
		if age == 0 then
			age = '(moins d’un&nbsp;an)'
		elseif age == 1 then
			age = '(1&nbsp;an)'
		else
			age = '(' .. age .. '&nbsp;ans)'
		end
	else
		age = false
	end
	
	
	-- compilation du résultat
	local wikiTexte = wikiListe.concat( ' ' )
	local isoTexte = iso.concat( '-' )
	
	-- On ajoute un peu de sémantique.
	local wikiHtml = mw.html.create( '' )
	
	local dateHtml = wikiHtml:tag( 'time' )
			:wikitext( wikiTexte )
	if wikiTexte:match( ' ' ) then
		dateHtml:addClass( 'nowrap' )
	end
	if isoTexte ~= wikiTexte then
		dateHtml:attr( 'datetime', 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
	if age then
		wikiHtml:wikitext( ' ' )
				:tag( 'span' )
					:addClass( 'noprint')
					:wikitext( age )
					:done()
	end
	
	return tostring( wikiHtml )
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ées pour gérer les cas ou 
-- le paramètre contient déjà un modèle date.
-- Paramètres :
-- 		1 : type de date à afficher (naissance / n, mort / m, ou date / d)
-- 		1 : Date ou date de naissance
-- 		2 : Date de mort si type n ou m
-- 		qualificatif = suffixe des page de date à lier (exemple : en musique)
-- 		nolinks : n'affiche pas de lien
function fun.dateInfobox( frame )
	local args = frame.args
	if type( args ) ~= 'table' or not ( args[1] and args[2] ) then
		return
	end
	
	local function analyseDate( d )
		if trim( d ) then
			-- supprime les liens
			local analyse = d:match( ' ou ') or d:match( 'entre ' ) or d:match( 'vers ' ) or d:match( 'après ' ) or d:match( 'avant ' )
			if analyse then 
				return d
			end
			analyse = d:match( 'datetime="([%d-]+)"' ) or d
			local debut, fin = analyse:match( '(.-%d%d%d%]*%-?)([\127 ].+)' )
			if not debut then
				debut, fin = analyse:match( '(.-%d%d%d%]*%-?)(<br ?/?>.+)' )
			end
			analyse = debut or analyse
			analyse = analyse:gsub( '%[%[([^%[%]|]*)|?([^%[%]]*)%]%]', function ( l, t ) return trim( t ) or l end )
			local t, r = fun.separationJourMoisAnnee( analyse )
			if t then
				return r, fin
			else
				return d, fin
			end
		end
	end
	
	local naissance = args[1]:match( '^n' )
	local mort = args[1]:match( '^m' ) or args[1]:match( 'décès' )
	local affichageDate, qualificatif = args[2], args[4]
	local affichageDateTab, resultatDate, complementDate
	local dateNaissance, dateMort
	if mort then
		affichageDate = args[3]
	end
	if not trim( affichageDate ) then
		return
	end
	if 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 ) and ( affichageDate:match( 'wikidata%-linkback' ))  then
			dateNaissance = analyseDate( args[2] )
			dateMort = analyseDate( args[3] )
			resultatDate = affichageDate
		else 
			return affichageDate
		end
	else
		affichageDateTab, complementDate = analyseDate( affichageDate )
		if type( affichageDateTab ) ~= 'table' then 
			return affichageDateTab
		else
			if naissance then
				dateNaissance = affichageDateTab
				dateMort = analyseDate( args[3] )
			elseif mort then
				dateNaissance = analyseDate( args[2] )
				dateMort = affichageDateTab
			else
				qualificatif = args[3]
			end
			affichageDateTab.naissance = naissance
			affichageDateTab.mort = mort
			affichageDateTab.qualificatif = args.qualificatif or qualificatif
			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 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 and dateNaissance and dateMort 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 .. '&nbsp;ans' .. suffixAge
		elseif calculAge == 1 then
			age = prefixAge .. 'un&nbsp;an' .. suffixAge
		elseif calculAge == 0 then
			age = prefixAge .. 'moins d’un&nbsp;an' .. suffixAge
		end
		if complementDate and complementDate:match( 'ans?%)' ) then
			complementDate = ''
		end
	end
	
	return resultatDate .. ( complementDate or '' ) .. age
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 lettre 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 <= liste_mois[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]) .. "&nbsp;" .. 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'age en fonction de la ou les dates fournies. La valeur retounée est de type 'number'
-- Parammètres :
-- 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).
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

	local an = tonumber( an )
	local mn = tonumber( mn )
	local 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
	if mc == mn then
		if jc == nil or jn == nil then
			return
		end
		return age-tonumber( jc < jn and 1 or 0 )
	else
		return age-tonumber( mc < mn and 1 or 0 )
	end
end

function fun.modeleAge( frame )
	local args = frame.getParent().args
	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 Outils.erreur("Paramètres incorrects ou insuffisants ou pour calculer l'âge précis" )
	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'algorythme de la page fr.wikipedia.org/wiki/Jour_julien (1/10/2013)
function fun.julianDayToJulian( julianDay )
	local year = math.modf( ( julianDay * 4 - 6884469 ) / 1461 )
	local r2 = julianDay - math.modf( ( 1461 * year + 6884472 ) / 4 )
	local month = math.modf( ( 5 * r2 + 461 ) / 153 )
	local day = r2 - math.modf( ( 153 * month - 457 ) / 5 ) + 1
	if month > 12 then
		year = year + 1
		month = month - 12
	end
	return year, month, day
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



---
-- erreurModuleData affiche  d'un message d'erreur si le Module:Langue/Data n'a pas été chargé correctement,
-- pour la page de discussion de la base de donnée et ceux qui veulent surveiller cette page.
function fun.erreurModuleData()
	local success, resultat = pcall ( mw.loadData, 'Module:Date/Data' )
	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>
]]
		return string.format( message, resultat )
	end
end

---
-- checkDataCat génère des liens vers les pages annuelles, mensuelles et d'éphémérides liè aux
-- catégories du Module:Date/Data. La date la plus ancienne dépend de 'aucun' et 'seul[1]'
-- Paramètres :
-- 	1 : la catégorie. Il y aura une section par qualificatif de cette catégorie.
-- 	mois : oui pour avoir les liens vers les pages mensuelles et éphémérides (4 jours dans l'année)
-- 	alias : pour avoir des lien pour les alias en plus des qualificatif
function fun.checkDataCat( frame )
	local category = trim(frame.args[1])
	local monthLinks = frame .args.mois == 'oui'
	local alias = frame.args.alias == 'oui'
	local dataLink = mw.loadData( 'Module:Date/Data' )
	local wikiList =  TableBuilder.new()
	local currentYear = tonumber( os.date( '%Y' ) )
	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
	if monthLinks then
		newSection = '\n\n== %s ==\n\n=== Années ===\n' .. columns
	else
		newSection ='\n\n== %s ==\n' .. columns
	end
	for field, dataField in pairs( dataLink ) do
		-- boucle sur tous les qualificatif ayant pour catégorie le premier paramère
		if dataField.cat == category or ( category == 'cat' and dataField.cat == field ) then
			local monthInitialYear, initialYear
			-- définition de l'année à partir de laquelle on va tester toutes les année / mois
			if dataField.qualificatif == field or ( category == 'cat' and dataField.cat == field ) then
				if dataField.annee and dataField.annee.aucun and dataField.annee.aucun < currentYear then
					local aucun = ( dataField.annee.seul and dataField.annee.seul[1] ) or dataField.annee.aucun
					initialYear = math.min( aucun - math.ceil( (currentYear - aucun) / 4 ), currentYear - 50 )
				else
					initialYear = currentYear - 50
				end
				if dataField.mois and tonumber( dataField.mois.aucun ) and ( tonumber( dataField.mois.aucun ) < currentYear ) then
					local aucun = dataField.mois.aucun
					monthInitialYear = math.min( aucun - math.ceil( (currentYear - aucun) / 4 ), currentYear - 8 )
				else
					monthInitialYear = currentYear - 8
				end
			elseif alias then
				-- si le paramètre alias est défini on teste aussi tous les alias, sinon ils sont ignorés
				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

--[[
  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 correspondant
--]]
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