Zum Inhalt springen

Modul:Wikidata/Time

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 31. Oktober 2022 um 14:46 Uhr durch Vollbracht (Diskussion | Beiträge). Sie kann sich erheblich von der aktuellen Version unterscheiden.
Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus


--[=[ Wikidata/Time 2022-10-28
Module for processing wikidata time information

Author: Vollbracht

== for use in other modules ==
* service	object representing a wikidata time property
			fields:	year, month, day, hour, min, sec,
					precision, timezone, calendarmodel
			constructor:
				Time:new(value)		processes snack.datavalue.value e.g.
			method:
				Time:format(fmtStr)		string representation for time object
				Time:qualified(snak)	snak and qualification state
				Time:isLessthan(time)	comparator method
				Time:equals(time)		comparator method
* service.LT(a, b)			comparator function for table.sort e.g.
* service.equals(a, b)		comparator function
* service.timify(values)	table of time objects by table of values

== for use in templates ==

{{#invoke:Wikidata/Time|now|<format string>}}	current system time
			(mainly for testing purposes;
			 format string optional: defaults to wikidata time string format)

]=]
	
--Module globals
local p = { service={
	-- more selective variant to '(.)(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z':
	MATCHING = '(.)(%d+)%-([0-1]%d)%-([0-3]%d)T([0-2]%d):([0-6]%d):([0-6]%d)Z',
	PRECISIONLEVEL = {
		-- unused:
		'E8', 'E7', 'E6', 'E5', 'E4',
		-- use with caution (only 'year' is in time object) :
		[6]='millennium', [7]='century', [8]='decade', [9]='year',
		-- use at will (fields in time object):
		[10]='month', [11]='day', [12]='hour', [13]='min', [14]='sec'
	},
	DEFAULTFORMAT = {
		[6]='yyyye=( v.Chr.)', [7]='yyyye=( v.Chr.)', [9]='yyyye=( v.Chr.)',
		[9]='yyyye=( v.Chr.)', [10]='mm/yyyy', [11]='dd.mm.yyyy', [12]='HH Uhr',
		[13]='HH:MM Uhr', [14]='E=(+)e=(-)yyyy-mm-ddTHH:MM:SSZ'
	},
	PoT='point of time'
} }

local _, DateTime = pcall(require, "Modul:DateTime")

--------------------------- local functions -----------------------------

--------------------------- service functions -----------------------------

--[[
	constructor
	generates a Time object
	parameters:
		source:	(optional) table of possible time informations as of
				mw.wikibase.getBestStatements(a, b)[1].mainsnak.datavalue.value
				current system time if nil
				NO MANDANTORY INFOS IN TABLE! OBJECT MAY CONTAIN NO TIME DATA!
]]
function p.service:new(source)
	-- time object is UTC system time if no source specified
	if not source or source == '' then
		d = os.date("!*t")
		for k, v in pairs(p.service) do d[k] = v end
		d.era = '+'
		d.precision = 14
		d.timezone = 0
		d.calendarmodel = "http://www.wikidata.org/entity/Q1985727"
		d.__tostring=function()
			return d:format('+yyyy-mm-ddTHH:MM:SSZ')
		end
		return d
	end
	o = {}
	setmetatable(o, self)
	self.__index = self
	if type(source) == "table" then
		local ts = source["time"]
		if not ts then
			o.__tostring=function()
				return 'Time not initialized!'
			end
			return o
		end
		o.era, o.year, o.month, o.day, o.hour, o.min, o.sec
			= ts:match(o.MATCHING)
		o.year = tonumber(o.year)
		o.month = tonumber(o.month)
		o.day = tonumber(o.day)
		o.hour = tonumber(o.hour)
		o.min = tonumber(o.min)
		o.sec = tonumber(o.sec)
		o.precision = source.precision
		o.timezone = source.timezone
		o.calendarmodel = source.calendarmodel
		o.__tostring=function()
			return o:format()
		end
	end
	return o
end

--[[
	Time.isLessthan(a, b)
	datetime compare
	parameters:
		a, b	2 service.time objects
	returns:	true	if on lowest precision level a is before b
				false	if necessary properties inavailable or
						if a is same time or later than b on given
						precision level
]]
p.service.isLessthan = function(a, b)
	if not a then return false end
	if not b then return false end
	local precision = 11
	if a.precision then precision = a.precision end
	if b.precision and b.precision < precision then
		precision = b.precision
	end
	if a.era and b.era then
		if a.era ~= b.era then return b.era == '+' end
		if a.year and b.year then
			local na = tonumber(a.year)
			local nb = tonumber(b.year)
			if na ~= nb then return (a.era == '+') == (na < nb) end
		else return false end
	end
	for i = 10, 14 do
		if precision < i then return false end
		local aa = a[p.service.PRECISIONLEVEL[i]]
		local ab = b[p.service.PRECISIONLEVEL[i]]
		if aa and ab then
			local na = tonumber(aa)
			local nb = tonumber(ab)
			if na ~= nb then return na < nb end
		else return false end
		i = i + 1
	end
	return false
end
--[[
	Time.LT(a, b)
	comparator function for sort e.g.
]]
p.service.LT = function(a, b)
	if a.time then a = a.time end
	if b.time then b = b.time end
	if not a.isLessthan then return false end
	return a.isLessthan(b)
end

--[[
	Time.equals(a, b)
	compares any objects in terms of time properties
	parameters:	two of any kind
	returns:	true	*	if both objects represent the same time at lowest
							common precision level
						*	if both objects lack the same time properties
						*	if both are no objects but of simple data type
				false	*	if the objects represent different times at lowest
							common precision level
						*	if only one lacks a relevant time property
						*	if only one is of simple data type even if the other
							doesn't contain time properties at all
]]
p.service.equals = function(a, b)
	if not a == not b then return true end
	if not a then return false end
	if not b then return false end
	if type(a) ~= 'table' then return type (b) == 'table' end
	if type (b) ~= 'table' then return false end
	local precision = 11
	if a.precision then precision = a.precision end
	if b.precision and b.precision < precision then
		precision = b.precision
	end
	if a.era then
		if a.era == '+' then
			if b.era and b.era ~= '+' then return false end
		else
			if b.era then
				if b.era == '+' then return false end
			else return false end
		end
	else
		if b.era and b.era ~= '+' then return false end
	end
	if precision < 9 then precision = 9 end
	for i = 9, precision do
		local aa = a[p.service.PRECISIONLEVEL[i]]
		local ab = b[p.service.PRECISIONLEVEL[i]]
		if not aa ~= not ab then return false end
		if aa and aa ~= ab then return false end
		i = i + 1
	end
	return true
end

--[[
	Time:format(fmtStr)
	return formated date time value based on given format string
	parameters:
		fmtStr:	limited format string evaluating each appearance of keys:
				either
					%y or %Y for year
					%m for month
					%d for day of month
					%H for 24-hour hour value
					%M for minute
					%S for second
				or
					yy or yyyy for year
					m or mm for month
					d or dd for day of month
					H or HH for 24-hour hour value
					M or MM for minute
					S or SS for second
					e=(<content>)	for content string appearing
									if era is '-' (BC)
					E=(<content>)	for content string appearing
									if era is '+' (CE)
		returns	date time value string
]]
p.service.format = function(this, fmtStr)
	function d2(value)
		if value > 9 then return value end
		return '0' .. value
	end
	if not fmtStr then
		if this.precision then
			if this.precision < 6 then return '' end
			fmtStr = this.DEFAULTFORMAT[this.precision]
		else return '' end
	end
	if fmtStr:match('%%') then
		if this.year then
			fmtStr = fmtStr:gsub('%%Y', this.year)
			if this.year < 100 then fmtStr = fmtStr:gsub('%%y', this.year)
			else fmtStr = fmtStr:gsub('%%y', d2(this.year % 100)) end
		end
		if this.month then fmtStr = fmtStr:gsub('%%m', d2(this.month)) end
		if this.month then fmtStr = fmtStr:gsub('%%d', d2(this.day)) end
		if this.month then fmtStr = fmtStr:gsub('%%H', d2(this.hour)) end
		if this.month then fmtStr = fmtStr:gsub('%%M', d2(this.min)) end
		if this.month then fmtStr = fmtStr:gsub('%%S', d2(this.sec)) end
	else
		if this.year then
			fmtStr = fmtStr:gsub('yyyy', this.year)
			fmtStr = fmtStr:gsub('jjjj', this.year)
			if this.year < 100 then
				fmtStr = fmtStr:gsub('yy', this.year)
				fmtStr = fmtStr:gsub('jj', this.year)
			else
				fmtStr = fmtStr:gsub('yy', d2(this.year % 100))
				fmtStr = fmtStr:gsub('jj', d2(this.year % 100))
			end
		end
		if this.month then
			fmtStr = fmtStr:gsub('mm', d2(this.month))
			fmtStr = fmtStr:gsub('m', this.month)
		end
		if this.day then
			fmtStr = fmtStr:gsub('dd', d2(this.day))
			fmtStr = fmtStr:gsub('tt', d2(this.day))
			fmtStr = fmtStr:gsub('d', this.day)
			fmtStr = fmtStr:gsub('t', this.day)
		end
		if this.hour then
			fmtStr = fmtStr:gsub('HH', d2(this.hour))
			fmtStr = fmtStr:gsub('H', this.hour)
		end
		if this.min then
			fmtStr = fmtStr:gsub('MM', d2(this.min))
			fmtStr = fmtStr:gsub('M', this.min)
		end
		if this.sec then
			fmtStr = fmtStr:gsub('SS', d2(this.sec))
			fmtStr = fmtStr:gsub('S', this.sec)
		end
		if this.era then
			if this.era == '+' then
				fmtStr = fmtStr:gsub('e=%(.*%)', '')
				fmtStr = fmtStr:gsub('E=%((.*)%)', '%1')
			else
				fmtStr = fmtStr:gsub('E=%(.*%)', '')
				fmtStr = fmtStr:gsub('e=%((.*)%)', '%1')
			end
		end
	end
	return fmtStr
end

--[[
	Time:qualified(Statement)
	Statement and its qualification state
	parameters:
		Statement	a Statement that is to be qualified
	returns:	Statement, true		if this time qualifies the snack
				Statement, false	if Statement is independent from this time
				nil, false			if this time disqualifies the snack
				nil, true			if Statement is nil (Ex falso quodlibet)
]]
p.service.qualified = function(this, Statement)
	if not snack then return nil, true end
	if type(Statement) ~= 'table' then return Statement, false end
	if not Statement.qualifiers then return Statement, false end
	local hasCriteria = false
	-- point of time
	for _, pot in ipairs(Statement.qualifiers.P585) do
		local comp = p.service:new(pot.datavalue.value)
		if not this:equals(comp) then return nil, false end
		hasCriteria = true;
	end
	-- start time
	for _, pot in ipairs(Statement.qualifiers.P580) do
		local comp = p.service:new(pot.datavalue.value)
		if this:isLessthan(comp) then return nil, false end
		hasCriteria = true;
	end
	-- end time
	for _, pot in ipairs(Statement.qualifiers.P582) do
		local comp = p.service:new(pot.datavalue.value)
		if comp:isLessthan(this) then return nil, false end
		hasCriteria = true;
	end
	return Statement, hasCriteria
end

--[[
	Time:getFiltered(statemens)
	new claim list without time disqualified claims
	parameters:
		statemens	data source as of mw.wikidata.getBestStatements
	returns:	refined claimlist
]]
p.service.getFiltered = function(this, statements)
	local result = {}
	for _, v in ipairs(claimlist) do
		local tested, tv = this:qualified(v)
		if tested then table.insert(result, tested) end
	end
	return result
end

--[[
	Time.timify(values)
	table of time objects by table of values
	can process statements as of mw.wikidata.getBestStatements('Q79822', 'P569')
	or qualifiers for a statement
	parameters:
		values:	table: {<source>, <source>, ...}
				<source>: (requirements as of time:new() source parameter)
	returns:	table:	{<time object>, <time object>, ...}
						Time objects of <source>-elements that could be
						converted are in this result table. The others are
						just ignored. Hence contains initialized time
						objects only.
				nil if result table empty
]]
p.service.timify = function(values)
	local result = {}
	for _, v in ipairs(values) do
		local value = v
		if value.mainsnak then value = value.mainsnak end
		if value.datavalue then value = value.datavalue end
		if value.value then value = value.value end
		local t = service.time:new(v)
		if t.year then table.insert(result, t) end
	end
	if #result == 0 then return nil end
	return result
end

--[[
	Time.filtered(claimlist, timeStr)
	new claim list without time disqualified claims
	parameters:
		claimlist	data source as of mw.wikidata.getBestStatements
		timeStr		time given by user
	returns:	refined claimlist
]]
p.service.filtered = function(statements, timeStr)
	local myTime = nil
	if not timeStr then myTime = p.service:new() end
	if timeStr:match('^%d+$') then
		myTime = p.service:new({})
		myTime.era = '+'
		myTime.year = tonumber(timeStr)
		myTime.precision=9
		myTime.__tostring=function() -- just in case s.o. edits filtered(..)
			return timeStr
		end
	else
		local t = mw.language.getContentLanguage():formatDate(
												"+Y-m-d\\TH:i:s\\Z", timeStr)
		local p = 14
		if t:match('00:00:00Z') then p = 11
		elseif t:match('00:00Z') then p = 12
		elseif t:match('00Z') then p = 13
		end
		local source={
			time=t,
			precision=p,
			calendarmodel='http://www.wikidata.org/entity/Q1985727',
			timezone=0
		}
		myTime = p.service:new(source)
	end
	return myTime:getFiltered(statements)
end

--------------------------- template functions -----------------------------

--[[
	{{#invoke:Wikidata/Time|now|<format string>}}
	current system time
]]
p.now = function(frame)
	return p.service:new():format(frame.args[1])
end

p.test = function(frame)
	-- test constructions
	local t1 = p.service:new()
	mw.logObject(t1, 'p.service:new()')
	mw.logObject(DateTime, 'DateTime')
	mw.logObject(DateTime.Prototypes, 'DateTime.Prototypes')
end

return p