Módulo:Data complexa

A documentação para este módulo pode ser criada na página Módulo:Data complexa/doc

-- TODO: melhorar sinergias com o Módulo:Data (gerenciamento de módulos: Data de datas não relacionadas e "astronáutica do século XI"

local datemodule = require 'Módulo:Data'
local linguistic -- = require 'Módulo:Linguística' -- carregado apenas se necessário
local romano -- = require 'Módulo:Romano' -- carregado apenas se necessário
local p = {}

local numericprecision = { -- converter precisões em valores numéricos = para aqueles usados pelo Wikidata
	gigayear = 0,
	megayear = 3,
	millenium = 6,
	century = 7,
	decade = 8,
	year = 9,
	month = 10,
	day = 11,
	hour = 12,
	minute = 13,
	second = 14,
}

local function vowelfirst(str)
	linguistic = require 'Módulo:Linguística'
	return linguistic.vowelfirst(str)
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, especialmente para dados Wikidata quando queremos mostrar com menos precisão do que a entrada (por exemplo, exibir apenas o ano)
	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 : gestão da precisão
	local format = require "Módulo:Formato"
	local val, unidade = 0, ""
	if year > 999999999 then
		unidade = " [[giga|G]][[Ano juliano|a]]"
		val = year / 1000000000
	elseif year > 999999 then
		unidade = " [[mega|M]][[Ano juliano|a]]"
		val = year / 1000000
	end
	val = format.do_formatnum({val})
	return val .. unidade
end


local function milleniumString(millenium, era, hideera)
	romano =  romano or require 'Módulo:Romano'
	local str = romano.toRoman(millenium) .. ' milénio'
	if era == '-' and (not hideera) then
		str = str .. ' a.C.'
	end
	return str
end

local function centuryString(century, era, hideera)
	romano =  romano or require 'Módulo:Romano'
	local str = romano.toRoman(century) .. ' século'
	if era == '-' and (not hideera) then
		str = str .. ' a.C.'
	end
	return str
end

local function decadeString(decade, era, hideera)
	local str = 'década de ' .. decade .. '0'
	if era == '-' and (not hideera) then
		str = str .. ' a.C.'
	end
	return '[[' .. str .. ']]'
end

function p.simplestring(dateobject, displayformat)

	-- transforma um objeto de data de ponto em texto
	-- as datas do tipo ISO devem passar pelo Módulo:Data, mas você deve poder desativar os links
	if type(dateobject) == 'string' or type(dateobject) == 'nil' then
		return dateobject
	end
	if (not dateobject.year) and (not dateobject.month) and dateobject.day then -- se apenas o dia passou, por exemplo, devido a removeclutter, o formato não é suportado pelo módulo: Data
		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)
	
	-- formatos gerados por este módulo
	local year = tonumber( dateobject.year) or 0
	
	if year > 999999 then -- grandes datas para astronomia, paleontologia
		return bigDate(year, precision)
	end
	
	if precision == 6 then
		local millenium = math.floor(year/1000) + 1
		str = milleniumString(millenium, era, hideera)
	elseif precision == 7 then
		local century = math.floor(year/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
	
	-- formatos gerados por Módulo:Data
	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 ac -- equivalente de hideera para modeloData
	if displayformat.hideera then
		ac = 'não'
	end
	str = datemodule.modeloData{dia = day, mes = month, ano = year, qualificativo = linktopic, nolinks = nolinks, ac = ac}
	return str or ''
end

local function fromToNow(d, datestr, precision) -- devolver "de" em vez de "de" quando não tiver terminado
	if (precision >= 11) or (precision == 7) or (precision == 6)  then -- ter dito "desde as datas com dia, séculos, milénios
		if vowelfirst(datestr) then -- pressupõe a ausência de link interno
			return "depois de" .. datestr
		else
			return "depois do " .. datestr
		end
	end
	return "depois " .. datestr
end

local function fromdate(d, displayformat)  -- devolve "da data" em linguagem natural
	displayformat = displayformat or {}
	local precision = setprecision(d, displayformat.precision)
	local datestr = p.simplestring(d, displayformat)
	if displayformat and displayformat.textformat == 'minimum' then
		return datestr -- por exemplo, para as classificações do MH, mostre apenas a data de início
	end
	if displayformat and displayformat.textformat == 'short' then
		return datestr .. '&nbsp;&ndash;&nbsp;' -- para alguma infobox (jogador de futebol, por exemplo), mostra a data de início e um traço
	end
	if displayformat.stilltrue and p.before ( os.date("!%Y-%m-%dT%TZ"), d) then return
		fromToNow(d, datestr, precision)
	end
	if (precision >= 11) or (precision == 7) or (precision == 6)  then -- ter dito "desde as datas com dia, séculos, milénios
		return 'a partir de ' .. datestr
	end
	if (precision == 10) and (vowelfirst(datemodule.determinationMes(d.month))) then
		return "a partir de" .. datestr
	end
	return 'a partir de ' .. datestr
end

local function upto(d, displayformat)  -- devolve "até à data" em linguagem natural
	displayformat = displayformat or {}
	local datestring = p.simplestring(d, displayformat)
	local precision = setprecision(d, displayformat.precision)
	if displayformat and displayformat.textformat == 'infobox' then
		return '&nbsp;&ndash;&nbsp;'.. datestring -- para alguma infobox (jogador de futebol, por exemplo), mostra a data de início e um traço
	end
	if displayformat and displayformat.textformat == 'short' then
		return'&nbsp;&ndash;&nbsp;' .. datestring -- para alguma infobox (jogador de futebol, por exemplo), mostra a data de início e um traço
	end
	if (precision >= 11) or (precision == 7) or (precision == 6) then --dizemos "até" para datas com dia e por séculos
		return "até a " .. datestring
	elseif (precision > 9) then
		return "até a " .. datestring
	else
		return "até " .. datestring
	end
end

local function fromuntillong(startstr, endstr, era, precision)
	-- diz "de 3 a 14 de janeiro" mas "de setembro a outubro
	if precision >= 11 then -- >= day
		return "de " .. startstr .. " a " ..  endstr .. era
	else
		if vowelfirst(startstr) then
			return "de" .. startstr .. " a ".. endstr .. era
		else
			return "de " .. startstr .. " a " .. endstr .. era
		end
	end
end

local function removeclutter(startpoint, endpoint, precision, displayformat) -- prepare-se para tornar a data mais bonita: "De junho de 445 a.C. - julho de 445 a.C. -> De junho a julho de 445 a.C."
	if (type(startpoint) ~= 'table') or (type(endpoint) ~= 'table') then
		return startpoint, endpoint, precision, displayformat
	end
	local era = endpoint.era
	local sameera
	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 "depois " ..  startpoint
		else
			return "depois de " ..  startpoint
		end
	end
	if not startpoint then
		if precision <= 10 then
			return "antes " ..  endpoint
		else
			return "antes de " ..  endpoint
		end
	end

 	-- analisar configurações para evitar redundâncias
 	
	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 --caso contrário, é incompreensível
			return p.simplestring(endpoint, displayformat)
		end
		return endstr
	end
	-- evitar períodos repetitivos como "de 13 de setembro de 2006 a 18 de setembro de 2006
	if era == "-" then
		era = " a.C."
	else
		era = ""
	end
	
	if precision <= 10 then
		return "entre " .. startstr .. " e " .. endstr .. " " .. era
	else
		return "entre a " .. startstr .. " e a " .. endstr .. " " .. era
	end
end

local function fromuntil(startpoint, endpoint, displayformat)
	displayformat = displayformat or {}
	local precision = setprecision(endpoint, displayformat.precision)

 	-- analisar configurações para evitar redundância
 	
	local startpoint, endpoint, era, displayformat, sameera = removeclutter(startpoint, endpoint, precision, displayformat)

	local hideera= displayformat.hideera	
	displayformat.hideera = true -- para as cadeias intermediárias
	
	local startstr, endstr =  p.simplestring(startpoint, displayformat), p.simplestring(endpoint, displayformat)
	
	if (startstr == '') or (startstr == endstr) then
		displayformat.hideera = hideera -- vamos fazer uma cadeia simples, então usamos o formato original
		if (not sameera) then
			displayformat.hideera = false --caso contrário, é incompreensível
		end
		return p.simplestring(endpoint, displayformat)
	end
	-- evitar períodos repetitivos como "de 13 de setembro de 2006 a 18 de setembro de 2006"
	if era == '-' then
		era = ' a.C.'
	else
		era = ''
	end
	if displayformat.textformat == 'long' then
		return fromuntillong(startstr, endstr, era, precision)
	elseif (type(precision) == "number") and (precision > 9) then -- Se as datas contiverem meses ou dias, é melhor ter um espaço
		return startstr .. ' -<wbr> ' .. endstr .. era
	else
		return startstr .. '-<wbr>' .. endstr .. era
	end
end


function p.fuzzydate(dateobject, displayformat)
	local str = p.simplestring(dateobject, displayformat)
	if not str then
		return nil
	end
	return "para " .. str
end

function p.daterange(startpoint, endpoint, displayformat)
	if startpoint and endpoint then
		return fromuntil(startpoint, endpoint, displayformat)
	elseif startpoint then
		return fromdate(startpoint, displayformat)
	elseif endpoint then
		return upto(endpoint, displayformat)
	else
		return nil
	end
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) -- desde datavalue.value.time da Wikidata, também funcionaria usando apenas 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)
	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 = {'era', 'year', 'month', 'day'}
	for i, j in pairs(order) do
		if b[j] < a[j] then
			return true
		elseif b[j] > a[j] then
			return false
		end
	end
	return true
end

return p