Aller au contenu

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

De Wreck
Modèle:Infobox>Zolo
Aucun résumé des modifications
m 147 versions importées
 
(136 versions intermédiaires par 13 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
-- luacheck: globals mw, no max line length
-- TODO: améliorer les synergies avec Module:Date (gestion par module:Date de dates sans lien et de "XIe siècle en astronautique"
-- 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 datemodule = require 'Module:Date'
local linguistic = require('Module:Linguistique')
local linguistic -- = require 'Module:Linguistique' -- chargé uniquement si nécessaire
local roman -- = require 'Module:Romain' -- chargé uniquement si nécessaire
local p = {}
local p = {}


Ligne 15 : Ligne 18 :
day = 11,
day = 11,
hour = 12,
hour = 12,
minute = 12,
minute = 13,
second = 14,
second = 14,
}
}
local suffixeAvJC = ' <abbr class="abbr nowrap" title="avant Jésus-Christ">av. J.-C.</abbr>'


local function vowelfirst(str)
local function vowelfirst(str)
linguistic = linguistic or require 'Module:Linguistique'
return linguistic.vowelfirst(str)
return linguistic.vowelfirst(str)
end
end


local function guessprecision(obj) -- précision des dates qui ne viennent pas de Module:Wikidata/Dates
function p.dateObject(orig, params)
local prec = 0
--[[ transforme un snak en un nouvel objet utilisable par des fonctions comme p.setprecision
for i, j in pairs(obj) do
{type = 'dateobject', timestamp = str, era = '+' ou '-', year = number, month = number, day = number, calendar = calendar}
if (numericprecision[i] or 0) > prec then
]]--
prec = numericprecision[i]
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
if params and params.withpreposition then
return p.atdate(obj, params)
else
return p.simplestring(obj, params)
end
end
end  
elseif obj.type == 'rangeobject' then
return prec
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
end


local function centuryString(centurynumber)
local function milleniumString(millenium, era, hideera)
return mw.getCurrentFrame():expandTemplate{ title = 'Nombre en romain', args = { centurynumber + 1 }} .. '<sup>e</sup> siècle'
roman = roman or require 'Module:Romain'
local str = roman.toRoman(millenium) .. '<sup>e</sup> millénaire'
if era == '-' and (not hideera) then
str = str .. suffixeAvJC
end
return str
end
end


local function milleniumString(centurynumber)
local function centuryString(century, era, hideera)
return mw.getCurrentFrame():expandTemplate{ title = 'Nombre en romain', args = { centurynumber + 1 }} .. '<sup>e</sup> millénaire'
roman = roman or require 'Module:Romain'
local str = roman.toRoman(century) .. '<sup>e</sup> siècle'
if era == '-' and (not hideera) then
str = str .. suffixeAvJC
end
return str
end
end


local function decadeString(decade)
local function decadeString(decade, era, hideera, withlink)
return 'années ' .. decade .. '0'
local target
local str = 'années ' .. decade
if decade ~= '0' then str = str .. '0' end
if era == '-' and (not hideera) then
if withlink then target = str .. ' av. J.-C.' end
str = str .. suffixeAvJC
end
if withlink then
if target then str = target .. '|' .. str end
str = '[[' .. str .. ']]'
end
return str
end
end


function p.simplestring(dateobject, displayformat)  
function p.simplestring(dateobject, displayformat)
 
-- transforme un object date ponctuel en texte
-- 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
-- les dates de type ISO devraient passer par Module:Date, mais il faut pouvoir désactiver les liens
local yearstr, monthstr, daystr= tostring(dateobject.year), tostring(dateobject.month), tostring(dateobject.day)
if type(dateobject) == 'string' or type(dateobject) == 'nil' then
return dateobject
end


-- adaptation à mw.formatDate en attendant de passer par Module:Date
-- si le date object comporte déjà le texte souhaité on le retourne
if yearstr then
if dateobject.string then
while #yearstr < 4 do
return dateobject.string
yearstr = 0 .. yearstr
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
end
end


local era = dateobject.era
local era = dateobject.era
local precision = tonumber(dateobject.precision) or numericprecision[dateobject.precision] or guessprecision(dateobject)
 
if precision == 6 then
if not displayformat then
local century = tostring(math.floor(dateobject.year/100))
displayformat = {}
str = milleniumString(century)
end
elseif precision == 7 then
local linktopic = displayformat.linktopic or displayformat.link
local century = tostring(math.floor(dateobject.year/100))
local nolinks
str = centuryString(century)
if linktopic == '-' then
elseif precision == 8 then
nolinks = true
local decade = tostring(math.floor(dateobject.year/10))  
end
str = decadeString(decade)
 
elseif precision == 9 then
local str
str = yearstr
local precision = setprecision(dateobject, displayformat.precision)
elseif precision == 10 then
local year = tonumber(dateobject.year)
str =mw.language.new('fr'):formatDate('F Y', yearstr .. '-' .. monthstr )
 
if dateobject.year < 1000 then -- enlève les zéros en trop
-- formats gérés par ce module
str = string.gsub(str, '0', '')
if year then
if year > 999999 then -- grosses dates pour l'astronomie, la paléontologie
return bigDate(year, precision)
end
end
elseif precision == 11  then
 
if (type(displayformat) == 'table' and displayformat.linktopic) then
local hideera = displayformat.hideera
return datemodule.modeleDate({daystr, monthstr, yearstr, linktopic})
 
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
if year == 0 then century = 1 end
str = centuryString(century, era, hideera)
elseif precision == 8 then
local decade = tostring(math.floor(year/10))
str = decadeString(decade, era, hideera, not nolinks)
end
end
str = mw.language.new('fr'):formatDate('j F Y', yearstr .. '-' .. monthstr .. '-' .. daystr)
if str then
if dateobject.day == 1 then -- ajustement "1 janvier" -> 1er janvier
return str
str = string.gsub(str, '1', "1<sup>er</sup>", 1) -- remplace "1 janvier" par "1er janvier"
end
end
if dateobject.year < 1000 then  
end
str = string.gsub(str, '0', '')
 
-- formats gérés par Module:Date
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
end
end
if era == '-' then
 
str = str .. ' av. J.-C.'
local argAvJC -- équivalent de hideera pour modeleDate
if displayformat.hideera then
argAvJC = 'non'
end
str = datemodule.modeleDate{jour = day, mois = month, annee = year, qualificatif = linktopic, nolinks = nolinks, avJC = argAvJC, 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
end
return str or 'date invalide'
return "depuis " .. datestr
end
end


local function fromdate(d, displayformat) -- retourne "à partir de date" en langage naturel
local function fromdate(d, displayformat) -- retourne "à partir de date" en langage naturel
local precision = d.precision or guessprecision(d)
displayformat = displayformat or {}
local precision = setprecision(d, displayformat.precision)
local datestr = p.simplestring(d, displayformat)
local datestr = p.simplestring(d, displayformat)
 
if displayformat and displayformat.textformat == 'minimum' then
if (precision >= 11) or (precision == 7) or (precision == 6) then -- ont dit "à partir du pour les dates avec jour, les siècles, les millénaires
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 .. '&nbsp;&ndash;&nbsp;' -- 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
return 'à partir du ' .. datestr
else
if vowelfirst(str) then
return "à partir d'" .. datestr
else
return 'à partir de ' .. datestr
end
end
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
end


local function upto(d, displayformat) -- retourne "jusqu'à date' en langage naturel
local function upto(d, displayformat) -- retourne "jusqu'à date' en langage naturel
displayformat = displayformat or {}
local datestring = p.simplestring(d, displayformat)
local datestring = p.simplestring(d, displayformat)
local precision = d.precision or guessprecision(d)
local precision = setprecision(d, displayformat.precision)
if (precision >= 11) or (precision == 7) or (precision == 6) then --on dit "jusqu'au" pour les dates avec jour, et pour les siècles
if displayformat and displayformat.textformat == 'short' then
return 'jusqu\'au ' .. datestring
return'&nbsp;&ndash;&nbsp;' .. datestring -- pour certaines infobox (footballeur par exemple), afficher date de début et un tiret
elseif (precision >= 9) then
end
if (precision >= 11) then --dates avec jour
return "jusqu'au " .. datestring
elseif (precision == 10) then --mois
return "jusqu'à " .. datestring
return "jusqu'à " .. datestring
else
elseif (precision == 9) then --années
return "jusqu\'en " .. datestring
return "jusqu'en " .. datestring
elseif (precision == 8) then --décennies
return "jusqu'aux " .. datestring
elseif (precision >= 6) then --siècles et millénaires
return "jusqu'au " .. datestring
else --million d'années ?
return "jusqu'au " .. datestring
end
end
 
function p.atdate(d, displayformat) -- retourne "à la date' en langage naturel
displayformat = displayformat or {}
local datestring = p.simplestring(d, displayformat)
local precision = setprecision(d, displayformat.precision)
if (precision >= 11) then --dates avec jour
return "le " .. datestring
elseif (precision >= 9) then --années et mois
return "en " .. datestring
elseif (precision == 8) then --décennies
return "dans les " .. datestring
elseif (precision >= 6) then --siècles et millénaires
return "au " .. datestring
else --million d'années ?
return "dans le " .. datestring
end
end
end
end


local function fromuntillong(startstr, endstr) -- inutile ?
local function fromuntillong(startstr, endstr, era, startprecision, endprecision)
-- on dit "du 3 au 14 janvier" mais "de septembe à octobre
-- on dit "du 3 au 14 janvier" mais "de septembre à octobre"
if precision >= 11 then -- >= day
local longstartstr
return "du " .. startstr .. " au " .. endstr
if startprecision >= 11 then -- >= day
longstartstr = "du " .. startstr
elseif startprecision == 8 then -- == décennie ("années")
longstartstr = "des " .. startstr
else
else
if vowelfirst(startstr) then
if vowelfirst(startstr) then
return "d'" .. startstr .. " à ".. endstr
longstartstr = "d'" .. startstr
else
else
return "de " .. startstr .. " à " .. endstr
longstartstr = "de " .. startstr
end
end
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
end


local function fromuntil(startpoint, endpoint, displayformat)
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"
local precision = endpoint.precision or guessprecision(endpoint) -- may need 2 precisions for start and end dates
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
 
startpoint = p.simplestring(startpoint, displayformat)
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 era, sameera
startpoint, endpoint, era, displayformat, sameera = removeclutter(startpoint, endpoint, precision, displayformat)
 
local startstr, endstr = p.simplestring(startpoint, displayformat), p.simplestring(endpoint, displayformat)
displayformat.hideera = true


local startstr = p.simplestring(startpoint)
if (startstr == '') or (startstr == endstr) then
local endstr = p.simplestring(endpoint)
if (not sameera) then
-- à améliorer pour éviter les tournures répétitives comme "du 13 septembre 2006 au 18 september 2006"
displayformat.hideera = false -- sinon c'est incompréhensible
if not params then
return p.simplestring(endpoint, displayformat)
params = {}
end
return endstr
end
-- pour éviter les tournures répétitives comme "du 13 septembre 2006 au 18 septembre 2006"
if era == "-" then
era = suffixeAvJC
else
era = ""
end
end
if params.displayformat == 'long' then
 
return fromuntillong(startstr, endstr)
if precision <= 10 then
return "entre " .. startstr .. " et " .. endstr .. era
else
else
return startstr .. '-' .. endstr
return "entre le " .. startstr .. " et le " .. endstr .. era
end
end
end
end


function p.fuzzydate(dateobjet, displayformat)
local function fromuntil(startpoint, endpoint, displayformat)
local str = p.simplestring(dateobject, displayformat)
displayformat = displayformat or {}
return "vers " .. str
local startprecision = setprecision(startpoint, displayformat.precision)
local endprecision = setprecision(endpoint, displayformat.precision)
 
-- analyse les paramètres pour éviter les redondances
 
local era, sameera
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 = suffixeAvJC
else
era = ''
if not (sameera == nil) and not sameera then
startstr = startstr .. suffixeAvJC
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
end


function p.daterange(startpoint, endpoint, displayformat)  
function p.daterange(startpoint, endpoint, displayformat)
local result
if startpoint and endpoint then
if startpoint and endpoint then
return fromuntil(startpoint, endpoint, displayformat)
result = fromuntil(startpoint, endpoint, displayformat)
elseif startpoint then
elseif startpoint then
return fromdate(startpoint, displayformat)
result = fromdate(startpoint, displayformat)
elseif endpoint then
elseif endpoint then
return upto(endpoint, displayformat)
result = upto(endpoint, displayformat)
else
else
return nil
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
end
return result
end
end


Ligne 172 : Ligne 462 :
return datemodule.age(start.year, start.month, start.day, ending.year, ending.month, ending.day)
return datemodule.age(start.year, start.month, start.day, ending.year, ending.month, ending.day)
end
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
return p

Dernière version du 22 février 2026 à 00:16

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

-- luacheck: globals mw, no max line length

-- 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 suffixeAvJC = ' <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
		if params and params.withpreposition then
			return p.atdate(obj, params)
		else
			return p.simplestring(obj, params)
		end
	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 .. suffixeAvJC
	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 .. suffixeAvJC
	end
	return str
end

local function decadeString(decade, era, hideera, withlink)
	local target
	local str = 'années ' .. decade
	if decade ~= '0' then str = str .. '0' end
	if era == '-' and (not hideera) then
		if withlink then target = str .. ' av. J.-C.' end
		str = str .. suffixeAvJC
	end
	if withlink then
		if target then str = target .. '|' .. str end
		str = '[[' .. str .. ']]'
	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 or displayformat.link
	local nolinks
	if linktopic == '-' then
		nolinks = true
	end

	local str
	local precision = setprecision(dateobject, displayformat.precision)
	local year = tonumber(dateobject.year)

	-- formats gérés par ce module
	if year then
		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
			if year == 0 then century = 1 end
			str = centuryString(century, era, hideera)
		elseif precision == 8 then
			local decade = tostring(math.floor(year/10))
			str = decadeString(decade, era, hideera, not nolinks)
		end
		if str then
			return str
		end
	end

	-- formats gérés par Module:Date
	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 argAvJC -- équivalent de hideera pour modeleDate
	if displayformat.hideera then
		argAvJC = 'non'
	end
	str = datemodule.modeleDate{jour = day, mois = month, annee = year, qualificatif = linktopic, nolinks = nolinks, avJC = argAvJC, 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 .. '&nbsp;&ndash;&nbsp;' -- 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 == 'short' then
		return'&nbsp;&ndash;&nbsp;' .. datestring -- pour certaines infobox (footballeur par exemple), afficher date de début et un tiret
	end
	if (precision >= 11) then --dates avec jour
		return "jusqu'au " .. datestring
	elseif (precision == 10) then --mois
		return "jusqu'à " .. datestring
	elseif (precision == 9) then --années
		return "jusqu'en " .. datestring
	elseif (precision == 8) then --décennies
		return "jusqu'aux " .. datestring
	elseif (precision >= 6) then --siècles et millénaires
		return "jusqu'au " .. datestring
	else --million d'années ?
		return "jusqu'au " .. datestring
	end
end

function p.atdate(d, displayformat) -- retourne "à la date' en langage naturel
	displayformat = displayformat or {}
	local datestring = p.simplestring(d, displayformat)
	local precision = setprecision(d, displayformat.precision)
	if (precision >= 11) then --dates avec jour
		return "le " .. datestring
	elseif (precision >= 9) then --années et mois
		return "en " .. datestring
	elseif (precision == 8) then --décennies
		return "dans les " .. datestring
	elseif (precision >= 6) then --siècles et millénaires
		return "au " .. datestring
	else --million d'années ?
		return "dans le " .. 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

	startpoint = p.simplestring(startpoint, displayformat)
	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 era, sameera
	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 = suffixeAvJC
	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 era, sameera
	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 = suffixeAvJC
	else
		era = ''
		if not (sameera == nil) and not sameera then
			startstr = startstr .. suffixeAvJC
			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