Module:Date complexe
La documentation pour ce module peut être créée à Module:Date complexe/doc
-- TODO: améliorer les synergies avec Module:Date (gestion par module:Date de dates sans lien et de "XIe siècle en astronautique"
local datemodule = require 'Module:Date'
local linguistic -- = require 'Module:Linguistique' -- chargé uniquement si nécessaire
local roman -- = require 'Module:Romain' -- chargé uniquement si nécessaire
local p = {}
local numericprecision = { -- convertir les précisions en valeurs numériques = à celles utilisées par Wikidata
gigayear = 0,
megayear = 3,
millenium = 6,
century = 7,
decade = 8,
year = 9,
month = 10,
day = 11,
hour = 12,
minute = 13,
second = 14,
}
local avJC = '<abbr class="abbr nowrap" title="avant Jésus-Christ"> av. J.-C.</abbr>'
local function vowelfirst(str)
linguistic = linguistic or require 'Module:Linguistique'
return linguistic.vowelfirst(str)
end
function p.dateObject(orig, params)
--[[ transforme un snak en un nouvel objet utilisable par des fonctions comme p.setprecision
{type = 'dateobject', timestamp = str, era = '+' ou '-', year = number, month = number, day = number, calendar = calendar}
]]--
if not params then
params = {}
end
local newobj = p.splitDate(orig.time, orig.calendarmodel)
newobj.precision = params.precision or orig.precision
newobj.type = 'dateobject'
return newobj
end
function p.rangeObject(begin, ending, params)
--[[
objet comportant un timestamp pour le classement chronologique et deux dateobject (begin et ending)
]]--
local timestamp
if begin then
timestamp = begin.timestamp
else
timestamp = ending.timestamp
end
return {begin = begin, ending = ending, timestamp = timestamp, type = 'rangeobject'}
end
function p.objectToText(obj, params)
if obj.type == 'dateobject' then
return p.simplestring(obj, params)
elseif obj.type == 'rangeobject' then
return p.daterange(obj.begin, obj.ending, params)
end
end
local function setprecision(obj, maxprecision)
local precision
if type(obj) == "string" then
precision = tonumber(obj)
elseif type(obj) == "number" then
precision = obj
elseif type(obj) == "table" then
precision = tonumber(obj.precision) or numericprecision[obj.precision]
end
if not precision then
precision = 0
end
-- maxprecision, surtout pour données Wikidata quand on veut afficher avec moins de précision que l'input (par exemple afficher seulement l'année)
if maxprecision then
maxprecision = tonumber(maxprecision) or numericprecision[maxprecision]
end
if maxprecision then
return math.min(precision, maxprecision)
end
return precision
end
local function bigDate(year, precision) -- TODO : gestion de la précision
local val, unit = 0, ""
if year > 999999999 then
unit = " [[giga|G]][[Année julienne|a]]"
val = year / 1000000000
elseif year > 999999 then
unit = " [[méga|M]][[Année julienne|a]]"
val = year / 1000000
end
val = mw.getContentLanguage():formatNum(val)
return val .. unit
end
local function milleniumString(millenium, era, hideera)
roman = roman or require 'Module:Romain'
local str = roman.toRoman(millenium) .. '<sup>e</sup> millénaire'
if era == '-' and (not hideera) then
str = str .. avJC
end
return str
end
local function centuryString(century, era, hideera)
roman = roman or require 'Module:Romain'
local str = roman.toRoman(century) .. '<sup>e</sup> siècle'
if era == '-' and (not hideera) then
str = str .. avJC
end
return str
end
local function decadeString(decade, era, hideera)
local str = 'années ' .. decade .. '0'
if era == '-' and (not hideera) then
str = str .. ' av. J.-C.|' .. str .. avJC
end
return '[[' .. str .. ']]'
end
function p.simplestring(dateobject, displayformat)
-- transforme un object date ponctuel en texte
-- les dates de type ISO devraient passer par Module:Date, mais il faut pouvoir désactiver les liens
if type(dateobject) == 'string' or type(dateobject) == 'nil' then
return dateobject
end
-- si le date object comporte déjà le texte souhaité on le retourne
if dateobject.string then
return dateobject.string
end
if (not dateobject.year) and (not dateobject.month) and dateobject.day then -- si seul le jour est passé, par exemple à cause de removeclutter, le format n'est pas pris en charge par module:Date
if displayformat.precision and numericprecision[displayformat.precision] < 11 then
return ''
else
return tostring(dateobject.day)
end
end
local era = dateobject.era
if not displayformat then
displayformat = {}
end
local linktopic = displayformat.linktopic
local nolinks
if linktopic == '-' then
nolinks = true
end
local str
local precision = setprecision(dateobject, displayformat.precision)
-- formats gérés par ce module
local year = tonumber(dateobject.year) or 0
if year > 999999 then -- grosses dates pour l'astronomie, la paléontologie
return bigDate(year, precision)
end
local hideera = displayformat.hideera
if precision == 6 then
local millenium = math.floor((year - 1)/1000) + 1
str = milleniumString(millenium, era, hideera)
elseif precision == 7 then
local century = math.floor((year - 1)/100) + 1
str = centuryString(century, era, hideera)
elseif precision == 8 then
local decade = tostring(math.floor(year/10))
str = decadeString(decade, era, hideera)
end
if str then
return str
end
-- formats gérés par Module:Date
local year = dateobject.year
if year and (era == '-') then
year = 0 - year
end
local month, day
if precision > 9 then
month = dateobject.month
if precision > 10 then
day = dateobject.day
end
end
local avJC -- équivalent de hideera pour modeleDate
if displayformat.hideera then
avJC = 'non'
end
str = datemodule.modeleDate{jour = day, mois = month, annee = year, qualificatif = linktopic, nolinks = nolinks, avJC = avJC, liens = true}
return str or ''
end
local function fromToNow(datestr, precision) -- retourne "depuis" plutôt que "à partir de" quand ce n'est pas terminé
if (precision >= 11) or (precision == 7) or (precision == 6) then -- on dit "depuis le" pour les dates avec jour, les siècles, les millénaires
if vowelfirst(datestr) then -- suppose l'absence de lien interne
return "depuis l'" .. datestr
else
return "depuis le " .. datestr
end
end
if (precision == 8) then -- on dit "depuis les" pour les décennies ("années ...")
return "depuis les " .. datestr
end
return "depuis " .. datestr
end
local function fromdate(d, displayformat) -- retourne "à partir de date" en langage naturel
displayformat = displayformat or {}
local precision = setprecision(d, displayformat.precision)
local datestr = p.simplestring(d, displayformat)
if displayformat and displayformat.textformat == 'minimum' then
return datestr -- par exemple pour les classements MH, juste afficher la date de début
end
if displayformat and displayformat.textformat == 'short' then
return datestr .. ' – ' -- pour certaines infobox (footballeur par exemple), afficher date de début et un tiret
end
if p.before(os.date("!%Y-%m-%dT%TZ"), d) and (displayformat.stilltrue ~= "?") and (displayformat.stilltrue ~= false) then
return fromToNow(datestr, precision)
end
if (precision >= 11) or (precision == 7) or (precision == 6) then -- on dit "à partir du" pour les dates avec jour, les siècles, les millénaires
return 'à partir du ' .. datestr
end
if (precision == 10) and (vowelfirst(datemodule.determinationMois(d.month))) then
return "à partir d'" .. datestr
end
if (precision == 8) then -- on dit "à partir des" pour les décennies
return 'à partir des ' .. datestr
end
return 'à partir de ' .. datestr
end
local function upto(d, displayformat) -- retourne "jusqu'à date' en langage naturel
displayformat = displayformat or {}
local datestring = p.simplestring(d, displayformat)
local precision = setprecision(d, displayformat.precision)
if displayformat and displayformat.textformat == 'infobox' then
return ' – '.. datestring -- pour certaines infobox (footballeur par exemple), afficher date de début et un tiret
end
if displayformat and displayformat.textformat == 'short' then
return' – ' .. datestring -- pour certaines infobox (footballeur par exemple), afficher date de début et un tiret
end
if (precision >= 11) or (precision == 7) or (precision == 6) then -- on dit "jusqu'au" pour les dates avec jour, et pour les siècles
return "jusqu'au " .. datestring
elseif (precision > 9) then
return "jusqu'à " .. datestring
elseif (precision == 8) then
return "jusqu'aux " .. datestring
else
return "jusqu'en " .. datestring
end
end
local function fromuntillong(startstr, endstr, era, startprecision, endprecision)
-- on dit "du 3 au 14 janvier" mais "de septembre à octobre"
local longstartstr
if startprecision >= 11 then -- >= day
longstartstr = "du " .. startstr
elseif startprecision == 8 then -- == décennie ("années")
longstartstr = "des " .. startstr
else
if vowelfirst(startstr) then
longstartstr = "d'" .. startstr
else
longstartstr = "de " .. startstr
end
end
local longendstr
if endprecision >= 11 then -- >= day
longendstr = " au " .. endstr .. era
elseif endprecision == 8 then -- == décennie ("années")
longendstr = " aux " .. endstr .. era
else
longendstr = " à " .. endstr .. era
end
return longstartstr .. longendstr
end
local function removeclutter(startpoint, endpoint, precision, displayformat) -- prépare à rendre la date plus jolie : "juin 445 av-JC-juillet 445 av-JC -> juin-juillet 445-av-JC"
if (type(startpoint) ~= 'table') or (type(endpoint) ~= 'table') then
return startpoint, endpoint, precision, displayformat
end
local era = endpoint.era
local sameera = false
if startpoint.era == endpoint.era then
sameera = true
end
if sameera and (endpoint.year == startpoint.year) then
startpoint.year = nil
if (startpoint.month == endpoint.month) then
startpoint.month = nil
if (startpoint.day == endpoint.day) then
startpoint.day = nil
end
end
end
return startpoint, endpoint, era, displayformat, sameera
end
function p.between(startpoint, endpoint, displayformat)
displayformat = displayformat or {}
local precision = setprecision(endpoint, displayformat.precision) or 9
local startpoint = p.simplestring(startpoint, displayformat)
local endpoint = p.simplestring(endpoint, displayformat)
if not (startpoint or endpoint) then
return nil
end
if not endpoint then
if precision <= 10 then
return "après " .. startpoint
else
return "après le " .. startpoint
end
end
if not startpoint then
if precision <= 10 then
return "avant " .. endpoint
else
return "avant le " .. endpoint
end
end
-- analyse les paramètres pour éviter les redondances
local startpoint, endpoint, era, displayformat, sameera = removeclutter(startpoint, endpoint, precision, displayformat)
local startstr, endstr = p.simplestring(startpoint, displayformat), p.simplestring(endpoint, displayformat)
displayformat.hideera = true
if (startstr == '') or (startstr == endstr) then
if (not sameera) then
displayformat.hideera = false -- sinon c'est incompréhensible
return p.simplestring(endpoint, displayformat)
end
return endstr
end
-- pour éviter les tournures répétitives comme "du 13 septembre 2006 au 18 septembre 2006"
if era == "-" then
era = avJC
else
era = ""
end
if precision <= 10 then
return "entre " .. startstr .. " et " .. endstr .. era
else
return "entre le " .. startstr .. " et le " .. endstr .. era
end
end
local function fromuntil(startpoint, endpoint, displayformat)
displayformat = displayformat or {}
local startprecision = setprecision(startpoint, displayformat.precision)
local endprecision = setprecision(endpoint, displayformat.precision)
-- analyse les paramètres pour éviter les redondances
local startpoint, endpoint, era, displayformat, sameera = removeclutter(startpoint, endpoint, endprecision, displayformat)
local hideera= displayformat.hideera
displayformat.hideera = true -- pour les chaînes intermédiaires
local startstr, endstr = p.simplestring(startpoint, displayformat), p.simplestring(endpoint, displayformat)
if (startstr == '') or (startstr == endstr) then
displayformat.hideera = hideera -- on va faire une chaîne simple, on reprend donc le format initialement demandé
if (not sameera) then
displayformat.hideera = false -- sinon c'est incompréhensible
end
return p.simplestring(endpoint, displayformat)
end
-- pour éviter les tournures répétitives comme "du 13 septembre 2006 au 18 septembre 2006"
local hasStartera = false
if era == '-' then
era = avJC
else
era = ''
if not (sameera == nil) and not sameera then
startstr = startstr .. avJC
hasStartera = true
end
end
if displayformat.textformat == 'long' then
return fromuntillong(startstr, endstr, era, startprecision, endprecision)
elseif (type(startprecision) == "number") and (startprecision > 9) or (type(endprecision) == "number") and (endprecision > 9) or hasStartera then -- si les date contiennent des mois ou jours, ou si il y a un era avant, il vaut mieux un espace
return startstr .. ' -<wbr> ' .. endstr .. era
else
return startstr .. '-<wbr>' .. endstr .. era
end
end
function p.daterange(startpoint, endpoint, displayformat)
local result
if startpoint and endpoint then
result = fromuntil(startpoint, endpoint, displayformat)
elseif startpoint then
result = fromdate(startpoint, displayformat)
elseif endpoint then
result = upto(endpoint, displayformat)
else
result = nil
end
if result and displayformat and displayformat.ucfirst and displayformat.ucfirst ~= '-' then
linguistic = linguistic or require 'Module:Linguistique'
result = linguistic.ucfirst(result)
end
return result
end
function p.duration(start, ending)
if (not start) or (not ending) then
return nil -- ?
end
return datemodule.age(start.year, start.month, start.day, ending.year, ending.month, ending.day)
end
local function splitWDdate(str) -- depuis datavalue.value.time de Wikidata, fonctionnerait aussi en utilisant simplement splitISO
local pattern = "(%W)(%d+)%-(%d+)%-(%d+)"
local era, year, month, day = str:match(pattern)
return era, year, month, day
end
local function splitISO(str)
str = mw.text.trim(str)
local era, year, month, day
era = string.sub(str, 1, 1)
if tonumber(era) then
era = '+'
end
local f = string.gmatch(str, '%d+')
year, month, day = f(), f(), f()
return era, year, month, day
end
function p.splitDate(orig, calendar)
if not orig then
return nil
end
if type(orig) == 'table' then
return orig
end
if type(orig) ~= 'string' then
return error("bad datatype for date, string expected, got " .. type(orig))
end
local era, y, m, d = splitWDdate(orig)
if not era then
era, y, m, d = splitISO(orig)
end
y, m, d = tonumber(y or 1), tonumber(m or 1), tonumber(d or 1)
return {day = d, month = m, year = y, era = era, type = 'dateobject', calendar = calendar}
end
function p.before(a, b) -- return true if b is before a or if at least one of a or b is missing
a = p.splitDate(a)
b = p.splitDate(b)
if (not a) or (not b) then
return true
end
local order = {'year', 'month', 'day'}
if a['era'] == '+' then
if b['era'] == '+' then
for i, j in ipairs(order) do
if b[j] < a[j] then
return true
elseif b[j] > a[j] then
return false
end
end
else -- b -
return true
end
else -- a -
if b['era'] == '+' then
return false
else -- b -
for i, j in ipairs(order) do
if b[j] > a[j] then
return true
elseif b[j] < a[j] then
return false
end
end
end
end
return true
end
function p.equal(a, b, precision)
a = p.splitDate(a)
b = p.splitDate(b)
if type(precision) == "string" then
precision = tonumber(precision) or numericprecision[mw.text.trim(precision)]
end
if not precision then
precision = 11 -- day by default ?
end
if (not a) or (not b) then
return true
end
if a.era and b.era and (b.era ~= a.era) then
return false
end
if (precision >= 11) then
if a.day and b.day and (b.day ~= a.day) then
return false
end
end
if (precision >= 10) then
if a.month and b.month and (b.month ~= a.month) then
return false
end
end
if (precision >= 9) then
if a.year and b.year and (b.year ~= a.year) then
return false
end
end
return true
end
return p