Амодуль:Wikidata/Formatters/time

Аматериал Авикипедиа аҟынтә - зхы иақәиҭу аенциклопедиа

require "Амодуль:No globals"

local p = {}

local lib = require 'Амодуль:Wikidata/lib'
local i18n = mw.loadData('Амодуль:Wikidata/i18n')

local function shouldConvertGregorian(timevalue, precision)
	return (
		(precision or timevalue.precision) > timevalue.PRECISION.MONTH
		and timevalue.calendar == timevalue.CALENDAR.JULIAN
		and timevalue > timevalue.newFromIso8601('1582-10-04')
	)
end

-- todo: move to lib
function p.yearDifference(sooner, later)
	local age = later.year - sooner.year
	if sooner.precision > 10 then
		if later.precision > 10 then
			if sooner.month > later.month then
				age = age - 1
			elseif sooner.month == later.month and sooner.day > later.day then
				age = age - 1
			end
			return age, age
		elseif later.precision == 10 then
			if sooner.month == later.month then
				return age - 1, age - 1 .. '–' .. age
			end
			if sooner.month > later.month then
				age = age - 1
			end
			return age, age
		end
	elseif sooner.precision == 10 then
		if later.precision > 9 then
			if sooner.month == later.month then
				return age - 1, age - 1 .. '–' .. age
			end
			if sooner.month > later.month then
				age = age - 1
			end
			return age, age
		end
	end
	return age - 1, age - 1 .. '–' .. age
end

-- todo: move to lib
function p.julianToGregorian(timevalue)
	-- https://en.wikipedia.org/wiki/Gregorian_calendar#Difference_between_Gregorian_and_Julian_calendar_dates
	local year = timevalue.year
	local diff = math.floor(year / 100) - math.floor(year / 400) - 2
	if year % 100 == 0 and year % 400 ~= 0 then
		if timevalue.month == 2 and 30 - timevalue.day > diff then
			diff = diff - 1
		end
	end
	local feb_days
	if year % 4 == 0 then
		feb_days = 29
	else
		feb_days = 28
	end
	local days = { 31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
	local newvalue = mw.clone(timevalue)
	newvalue.calendar = newvalue.CALENDAR.GREGORIAN
	newvalue.day = newvalue.day + diff
	if newvalue.day > days[newvalue.month] then
		newvalue.day = newvalue.day - days[newvalue.month]
		newvalue.month = newvalue.month + 1
		if newvalue.month > 12 then
			newvalue.month = 1
			newvalue.year = newvalue.year + 1
		end
	end
	return newvalue
end

local function stripZero(date, lang, year)
	-- удалить ведущие нули (год < 1000 и > -1000)
	return mw.ustring.gsub(
		date,
		lang:formatNum(0) .. '+(' .. lang:formatNum(year, { noCommafy = true }) .. ')',
		'%1'
	)
end

function p.getRawValue(value, options)
	local Time = require 'Амодуль:Time'
	local timevalue = Time.newFromWikidataValue(value)
	if (
		lib.IsOptionTrue(options, 'gregorian')
		and shouldConvertGregorian(timevalue)
	) then
		timevalue = p.julianToGregorian(timevalue)
	end
	return timevalue
end

function p.formatRawValue(timevalue, options)
	local lang = mw.getContentLanguage()
	local precision = math.min(timevalue.precision, options.precision or timevalue.precision)
	local linked_app = ''
	if not lib.IsOptionTrue(options, 'nolink') then
		linked_app = '-linked'
	end
	local year, BCE, BCE_app
	if timevalue.year < 0 then
		year = -timevalue.year - 1
		BCE = true
		if tostring(options.showera) ~= 'false' then
			BCE_app = '-BCE'
		else
			BCE_app = ''
		end
	else
		year = timevalue.year
		BCE = false
		BCE_app = ''
	end

	local newstring
	-- follows Wikibase/lib/includes/Formatters/MwTimeIsoFormatter.php
	local map = {
		[timevalue.PRECISION.GY] = { 'Gannum', 1e9, 1 },
		[timevalue.PRECISION.MY100] = { 'Mannum', 1e8, 100 },
		[timevalue.PRECISION.MY10] = { 'Mannum', 1e7, 10 },
		[timevalue.PRECISION.MY] = { 'Mannum', 1e6, 1 },
		[timevalue.PRECISION.KY100] = { 'annum', 1e5, 1e5 },
		[timevalue.PRECISION.KY10] = { 'annum', 1e4, 1e4 },
		[timevalue.PRECISION.KY] = { 'millenium', 1e3, 1, math.ceil },
		[timevalue.PRECISION.YEAR100] = { 'century', 100, 1, math.ceil },
		[timevalue.PRECISION.YEAR10] = { 'decade', 10, 10, math.floor },
		[timevalue.PRECISION.YEAR] = 'year',
		[timevalue.PRECISION.MONTH] = 'year-month',
		[timevalue.PRECISION.DAY] = 'year-month-day',
		-- TODO: hours, minutes and seconds (not on Wikidata yet)
	}
	if precision >= timevalue.PRECISION.YEAR then
		local key = map[math.min(precision, timevalue.PRECISION.DAY)] .. BCE_app
		local pattern = i18n.date[key .. linked_app] or i18n.date[key]
		if shouldConvertGregorian(timevalue, precision) then
			local newvalue = p.julianToGregorian(timevalue)
			local jul_pattern
			if newvalue.year ~= timevalue.year then
				jul_pattern = i18n.date['year-month-day']
			elseif newvalue.month ~= timevalue.month then
				jul_pattern = i18n.date['month-day']
			else
				jul_pattern = i18n.date.day
			end
			if timevalue.month == 2 and timevalue.day == 29 then
				if year % 100 == 0 and year % 400 ~= 0 then
					timevalue.year = 2000 -- hack to force formatting 29th February
				end
			end
			newstring = mw.ustring.format('%s%s / %s%s',
				lang:formatDate(jul_pattern, tostring(timevalue)),
				i18n.date.julian,
				lang:formatDate(pattern, tostring(newvalue)),
				i18n.date.gregorian)
		else
			local timestring
			if BCE then
				timestring = mw.ustring.sub(tostring(timevalue), 2)
			else
				timestring = tostring(timevalue)
			end
			newstring = stripZero(lang:formatDate(pattern, timestring), lang, year)
		end
		if lib.IsOptionTrue(options, 'showera') and not BCE then
			newstring = mw.getCurrentFrame():preprocess(
				mw.message.newRawMessage(i18n.date.CE, newstring):plain())
		end
	elseif precision >= timevalue.PRECISION.KY then  -- uses custom messages from Wikidata/i18n
		-- follows Wikibase/lib/includes/Formatters/MwTimeIsoFormatter.php
		local key = map[precision][1] .. BCE_app
		local shift = map[precision][2]
		local unshift = map[precision][3]
		local func = map[precision][4]
		local number = func(year / shift) * unshift
		newstring = mw.getCurrentFrame():preprocess(
			mw.message.newRawMessage(i18n.date[key .. linked_app] or i18n.date[key])
				:params(lang:formatNum(number, { noCommafy = true }))
				:plain()
			)
		if lib.IsOptionTrue(options, 'showera') and not BCE then
			newstring = mw.getCurrentFrame():preprocess(
				mw.message.newRawMessage(i18n.date.CE, newstring):plain())
		end
	else -- uses messages from Wikibase extension
		local round = function (x)
			local int, fract = math.modf(x)
			if fract < .5 then
				return int
			else
				return int + 1
			end
		end
		-- follows Wikibase/lib/includes/Formatters/MwTimeIsoFormatter.php
		local key = 'wikibase-time-precision' .. BCE_app .. '-' .. map[precision][1]
		local shift = map[precision][2]
		local unshift = map[precision][3]
		local number = round(year / shift) * unshift
		newstring = mw.getCurrentFrame():preprocess(
			mw.message.new(key)
				:numParams(number)
				:plain()
		)
	end
	return newstring
end

function p.formatValue(value, options)
	local timevalue = p.getRawValue(value, options)
	local formatted = p.formatRawValue(timevalue, options)
	if lib.IsOptionTrue(options, 'birthdate') and timevalue.precision >= timevalue.PRECISION.YEAR then
		local statements = mw.wikibase.getBestStatements(options.id, 'P570')
		local Filterers = require 'Амодуль:Wikidata/Filterers'
		Filterers.filterStatements(statements, { somevalue = true })
		if #statements == 0 then
			local Time = require 'Амодуль:Time'
			local osDate = os.date('*t')
			local currentTime = Time.new{
				year = osDate.year,
				month = osDate.month,
				day = osDate.day,
				precision = Time.PRECISION.DAY,
				calendar = Time.CALENDAR.GREGORIAN,
			}
			local age, age_text = p.yearDifference(timevalue, currentTime)
			if age >= 0 and age < 150 then
				formatted = mw.ustring.format('%s (%s)', formatted,
					mw.getCurrentFrame():preprocess(
						mw.message.newRawMessage(i18n.date.age)
							:params(age_text)
							:numParams(age)
							:plain()
					)
				)
			end
			if age >= 100 then
				formatted = formatted .. lib.category('centenarians-living')
			elseif age < 0 then
				formatted = formatted .. lib.category('failed-age-computing')
			end
		end
	elseif lib.IsOptionTrue(options, 'deathdate') and timevalue.precision >= timevalue.PRECISION.YEAR then
		local statements = mw.wikibase.getBestStatements(options.id, 'P569')
		local Filterers = require 'Амодуль:Wikidata/Filterers'
		Filterers.filterStatements(statements, { somevalue = false, novalue = false })
		if #statements > 0 then
			local Formatters = require 'Амодуль:Wikidata/Formatters'
			local TableTools = require 'Амодуль:TableTools'
			local ages = {}
			local invalid = 'INVALID'
			local age, age_text
			local min_age = math.huge
			for _, statement in ipairs(statements) do
				local birthvalue = Formatters.getRawValue(statement.mainsnak)
				if birthvalue.precision >= birthvalue.PRECISION.YEAR then
					age, age_text = p.yearDifference(birthvalue, timevalue)
					ages[age_text] = true
					if age < min_age then
						min_age = age
					end
				else
					ages[invalid] = true
				end
			end
			if TableTools.size(ages) == 1 and not ages[invalid] then
				formatted = mw.ustring.format('%s (%s)', formatted,
					mw.getCurrentFrame():preprocess(
						mw.message.newRawMessage(i18n.date['in-the-age'])
							:params(age_text)
							:numParams(min_age)
							:plain()
					)
				)
			end
			if min_age >= 100 and min_age < math.huge then
				formatted = formatted .. lib.category('centenarians')
			elseif min_age < 0 then
				formatted = formatted .. lib.category('failed-age-computing')
			end
		end
	end
	return formatted
end

return p