La documentazione per questo modulo può essere creata in Modulo:Date/man

--modulo contenente funzioni per la gestione delle date
local p = {}
local c = require('Modulo:Common')

--dal value, crea un oggetto date che rappresenta la data, da usare per le successive manipolazioni
function p.getDate(value)
	if value and value.time then
		date = {century, decade, year, month, day, ac = false, precision, calendar}
		--esempio di data: +1862-08-29T00:00:00Z
		date.year, date.month, date.day = value.time:match( '.+(%d?%d%d%d%d)%-(%d%d)%-(%d%d).+' )
		if date.year and date.month and date.day then
			date.year = tonumber(date.year)
			date.century = math.ceil(date.year / 100)
			date.decade = (date.year % 100) - ((date.year % 100) % 10)
			date.ac = (value.time:sub(1, 1) == '-')
			date.precision = value.precision
			date.calendar = p.getCalendar(value)
			return date
		else
			mw.log('Formato data non riconosciuto!')
		end
	end
end

--fornisce una lista di oggetti date presi dalla property fornita (di default, con ordinamento ASC)
function p.getDatesFromProperty(property, order)	
	local list = {}
	claims = c.sortClaimsByDate(c.getClaimsByProperty(property) or {}, order)
	for index, claim in pairs(claims) do
		date = p.getDate(c.getClaimValue(claim))
		if date then table.insert(list, date) end
	end
	return list
end

--ritorna la lista di tutte le date per la proprietà indicata, concatenate da "-" o da "tra il e il"
function p.getAllDates(property)
	local list = {}		--lista di tutte le date
 
	claims = c.getClaimsByProperty(property) or {}
	for index, claim in pairs(claims) do
 
		local val = p.getDate(c.getClaimValue(claim))
		local preStringa = ''
 
		local circa = c.getLabelFromValue(c.getQualifierValueFromClaim(claim, 'P1480'));
		circa = (circa and circa..' ') or ''
		local ante = p.getDate(c.getQualifierValueFromClaim(claim, 'P1326'))
		local post = p.getDate(c.getQualifierValueFromClaim(claim, 'P1319'))
 
		if val then
			preStringa = circa .. p.formatDate(val) .. ', '
		end
 
		if ante and post then
			--caso "tra il": ritorniamo direttamente questa coppia di date con la stringa "tra il"
			return preStringa .. 'tra il ' .. p.formatDate(post) .. ' e il ' .. p.formatDate(ante)
		elseif ante ~=nil and post == nil then
			--caso ante
			table.insert(list, preStringa .. 'prima del ' .. p.formatDate(ante))
		elseif ante ==nil and post ~= nil then
			--caso post
			table.insert(list, preStringa .. 'dopo il ' .. p.formatDate(post))
		elseif val then
			--caso normale
			table.insert(list, circa .. p.formatDate(val))
		end
	end
 
	--a questo punto va costruita la stringa finale
	return c.concat(list, '-')
end

--trova la "data comune" più accurata possibile tra la lista di date fornita
function p.getCommonDate(dates)
	commonDate = {}
	for k, v in pairs(dates[1] or {}) do commonDate[k] = v end  --deep copy
		
	for i, date in pairs(dates or {}) do
		for k, v in pairs(date) do
			if v ~= commonDate[k] then commonDate[k] = nil end
		end
		if date[precision] and commonDate.precision and date[precision] < commonDate.precision then 
			commonDate.precision = date[precision]
		end
	end
	return commonDate
end

function p.getAnteOrPostFromProperty(property)
	claims = c.getClaimsByProperty(property) or {}
	for index, claim in pairs(claims) do
		ante = p.getDate(c.getQualifierValueFromClaim(claim, 'P1326'))
		post = p.getDate(c.getQualifierValueFromClaim(claim, 'P1319'))
		if ante then 
			return ante
		elseif post then 
			return post
		end
	end
end

function p.addYears(date, years)
	local ac = date.ac
	date.year = date.year * (date.ac and -1 or 1) + years
	date.ac = not(date.year > 0)
	date.year = math.abs(date.year)
	if date.year == 0 then date.ac = not(ac) end
	if ac ~= date.ac then
		date.year = date.year + 1 --year 0 does not exist!
	end
	date.century = math.ceil(date.year / 100)
	return date
end

--ritorna la data formattata a seconda della precisione
function p.formatDate(date)
	if date and date.precision then
		
		--precision: 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day
		if date.precision == 7 then
			return p.formatCentury(date)
		elseif date.precision == 8 then
			return p.formatDecadeCentury(date)
		elseif date.precision == 9 then
			return p.formatYear(date)
		elseif date.precision == 10 then
			return p.formatMonth(date) .. ' ' .. p.formatYear(date)
		elseif date.precision == 11 then
			return p.formatDayMonth(date) .. ' ' .. p.formatYear(date)
		end
	end
end

--il nome secolo nel formato "XII secolo"
function p.formatCentury(date)
	if date and date.century then return c.toRoman(date.century) ..' secolo' .. p.avantiCristo(date) end
end

--il nome del decennio nel formato "anni 50"
function p.formatDecade(date)
	if date and date.decade then return 'anni ' .. date.decade end
end

--il nome del decennio nel formato "anni 50 del II secolo a.C."
function p.formatDecadeCentury(date)
	if date and date.decade and date.century then return p.formatDecade(date) .. ' del ' .. p.formatCentury(date) end
end

--ritorna l'anno formattato con a.C. se necessario
function p.formatYear(date)
	if date and date.year then return date.year .. p.avantiCristo(date) end
end

--ritorna il mese in formato esteso (gennaio, febbraio...)
function p.formatMonth(date)
	day = 1 --per dare un giorno "vero" al formatDate (con 0 potrebbe ritornare risultati imprevisti)
	if date and date.year and date.month then 
		return mw.getLanguage('it'):formatDate('F', date.year..'-'..date.month..'-'..day) 
	end
end

--ritorna giorno+mese in formato esteso (1° gennaio, 12 febbraio...)
function p.formatDayMonth(date)
	if date and date.year and date.month and date.day then 
		return mw.getLanguage('it'):formatDate('j F',  date.year..'-'..date.month..'-'..date.day):gsub('^1%s', '1° ')
	end
end

function p.avantiCristo(date)
	return (date and date.ac and ' a.C.') or ''
end

--ritorna il nome del calendario in testo puro
function p.getCalendar(value)
	calendarmodel = ''
	--TODO capire perché diavolo a volte arriva come string e come sistemare
	if type(value) == 'string' then
		calendarmodel = value
	elseif value and value.calendarmodel then
		calendarmodel = value.calendarmodel
	end
	
	if calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then 
		return 'giuliano'
	elseif calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then 
		return 'gregoriano'
	else
		mw.log('Calendario non riconosciuto: ' .. calendarmodel)
		return ''
	end
end

--ritorna una stringa con il tipo di calendario e il link
function p.linkCalendar(calendar)
	if p.getCalendar(calendar) == 'giuliano' then
		return '\'\'[[:w:Calendario giuliano|giul.]]\'\''
	elseif p.getCalendar(calendar) == 'gregoriano' then
		return '\'\'[[:w:Calendario gregoriano|greg.]]\'\''
	else
		return ''
	end
end

function p.monthToNumber(month)
	if     month == 'gennaio'   then return '01'
	elseif month == 'febbraio'  then return '02'
	elseif month == 'marzo'     then return '03'
	elseif month == 'aprile'    then return '04'
	elseif month == 'maggio'    then return '05'
	elseif month == 'giugno'    then return '06'
	elseif month == 'luglio'    then return '07'
	elseif month == 'agosto'    then return '08'
	elseif month == 'settembre' then return '09'
	elseif month == 'ottobre'   then return '10'
	elseif month == 'novembre'  then return '11'
	elseif month == 'dicembre'  then return '12'
	end
end

function p.numberToMonth(mon)
	if     mon == '01' then return 'gennaio'
	elseif mon == '02' then return 'febbraio'
	elseif mon == '03' then return 'marzo'
	elseif mon == '04' then return 'aprile'
	elseif mon == '05' then return 'maggio'
	elseif mon == '06' then return 'giugno'
	elseif mon == '07' then return 'luglio'
	elseif mon == '08' then return 'agosto'
	elseif mon == '09' then return 'settembre'
	elseif mon == '10' then return 'ottobre'
	elseif mon == '11' then return 'novembre'
	elseif mon == '12' then return 'dicembre'
	end
end

--prende una data in formato "1° febbraio" e ritorna "02-01"
function p.getNumericDate(frame)
	stringDate = frame.args[1]:gsub("°", "")
	day, month = stringDate:match("(%w+) (%w+)")
	day = string.format("%02d", day)
	return p.monthToNumber(month)..'-'..day
end

--prende una data nel formato 2018.01 (archivio del bar) e ritorna "2018"
function p.getBarYear(frame)
	stringDate = frame.args[1]
	year, mon = stringDate:match("(%w+).(%w+)")
	return year 
end

--prende una data nel formato 2018.01 (archivio del bar) e ritorna "gennaio"
function p.getBarMonth(frame)
	stringDate = frame.args[1]
	year, mon = stringDate:match("(%w+).(%w+)")
	return p.numberToMonth(mon)
end

-- prende una data e ritorna il secolo
function p.secolo(frame)
	stringDate = frame.args[1]
	if stringDate:lower():match("secolo") then
		stringDate = stringDate:gsub("Secolo", "secolo"):gsub("a.c.", "a.C.")
		return stringDate
	else
		stringDate = stringDate:lower():gsub("[-/].*", "")
		str = " secolo"
		if stringDate:match("a.c.") then
			stringDate = stringDate:gsub(" a.c.", "")
			str = str .. " a.C."
		end
		return c.toRoman(math.ceil(stringDate / 100)) .. str
	end
end

function p.absoluteDate(stringDate)
	if not stringDate then
		return nil
	end
	if stringDate:match("a.C.") then
		stringDate = stringDate:gsub(" a.C.", "")
		numDate = tonumber(stringDate)
		if numDate then
			return -numDate
		else
			return nil
		end
	else
		return tonumber(stringDate)
	end
end

function p.dayBefore(frame)
	local day = frame.args[1]
	local month = p.monthToNumber(frame.args[2])
	mw.log('day: '..day)
	mw.log('month: '..month)
	local endtime = os.time({day = day - 1, month = month, year = os.date("%Y")})
	return os.date("%d", endtime):match("0*(%d+)") .. ' ' .. p.numberToMonth(os.date("%m", endtime))
end

function p.dayAfter(frame)
	local day = frame.args[1]
	local month = p.monthToNumber(frame.args[2])
	local endtime = os.time({day = day + 1, month = month, year = os.date("%Y")})
	return os.date("%d", endtime):match("0*(%d+)") .. ' ' .. p.numberToMonth(os.date("%m", endtime))
end

return p