Zum Inhalt springen

Modul:Time

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

Modul: Dokumentation

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


--[=[ Time 2022-10-31
Modul for processing time information; point and span objects

Author: Vollbracht

== for use in other modules ==
* constant.PRECISIONLEVEL	table of valid precision values
			constants:
				MIN		lowest valid precision level (value for t.precision)
				MINUSE	lowest index of PRECISIONLEVEL table
				MAX		highest valid precision level
				[MINUSE] .. [MAX]	precision level name (unit)
			function
				unit(level)	unit name of smallest significant time element
* point	object representing a point of time
			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:isLessthan(time)	comparator method
				Time:equals(time)		comparator method

]=]

local p = {
	constant = {
		PRECISIONLEVEL = {
			MIN = 0,		-- lowest valid precision
			MINUSE = 9,		-- lowest available index
			MAXUSE = 14,	-- highest valid precision
			MAX=15,			-- highest available index
			-- unused: 'E8', 'E7', 'E6', 'E5', 'E4',
			-- [6]='millennium', [7]='century', [8]='decade',
			[9]='year', [10]='month', [11]='day',
			[12]='hour', [13]='min', [14]='sec', [15]='msec',
		},
		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'
		},
		URI = {
			GREGORIAN = "http://www.wikidata.org/entity/Q1985727",
			JULIAN = "http://www.wikidata.org/entity/Q1985786"
		},
		MONTHLENGTH = {31,28,31,30,31,30,31,31,30,31,30,31}
	},
	point = {},
	duration = {}
}

p.constant.STARTOFGREGORIAN = {
	era = '+',
	year = 1582,
	month = 8,
	day = 15,
	precision = 11,
	timezone = 0,
	calendarmodel = p.constant.URI.GREGORIAN,
	__index = function(table, key)
		return p.point[key]
	end
}
setmetatable(p.constant.STARTOFGREGORIAN, p.point)

--[[
	p.constant.PRECISIONLEVEL
	constant constructor:
]]
local precisionlevel = function()
	o = {}
	o.__index = function(table, key)
		return p.constant.PRECISIONLEVEL[key]
	end
	o.unit = function(this, level)
		if level < o.MIN then return '' end
		if level < o.MINUSE then return o[o.MINUSE] end
		if level > o.MAXUSE then return o[o.MAXUSE] end
		local result = o[i]
		if result then return result end
	end
	return o
end

---------------------- point ----------------------
-- Point of time
----------------------

--[[
	constructor
	parameter:
		source:	string that can be processed by
					mw.language.getContentLanguage():formatDate
]]
function p.point:new(source)
	local o = nil
	if source then
		if type(source) == 'table' then
			o = source
		else
			o = {}
			local fsIn = "+Y-m-d\\TH:i:s\\Z"
			local fsOut = "(.)(%d+)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)Z"
			local t = mw.language.getContentLanguage():formatDate(fsIn, source)
			o.era, o.year, o.month, o.day, o.hour, o.min, o.sec = t:match(fsOut)
			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)
			if t:match('00:00:00Z') then o.precision = 11
			elseif t:match('00:00Z') then o.precision = 12
			elseif t:match('00Z') then o.precision = 13
			else o.precision = 14
			end
		end
		setmetatable(o, self)
		self.__index = self
		if not o.calendarmodel then
			if o:isLessthan(p.constant.STARTOFGREGORIAN) then
				o.calendarmodel = p.constant.URI.JULIAN
				o.BC = true
			else
				o.calendarmodel = p.constant.URI.GREGORIAN
			end
		end
		return o
	else
		local d = os.date("!*t")
		for k, v in pairs(p.point) do d[k] = v end
		d.era = '+'
		d.precision = 14
		d.timezone = 0
		d.calendarmodel = p.constant.URI.GREGORIAN
		return d
	end
end

--[[
	days(pointOfTime)
	theoretical number of days since 1.1.0000
	returns:	positive integer or nil (year before christian counting)
]]
local days = function(point)
	if not point.year then return nil end
	if point.year < 1 then return nil end
	if not point.month then return nil end
	if not point.day then return nil end
	local yb = point.year - 1
	local list = {	yb, yb,
					point.year, point.year, point.year, point.year, point.year,
					point.year, point.year, point.year, point.year, point.year}
	local y = list[point.month]
	list = {0, 31, 59, 90, 120, 151, 181, 212, 242, 273, 303, 334}
	local s = math.floor(y / 4)
	mw.log('days by ')
	mw.log(point.calendarmodel)
	if point.calendarmodel == p.constant.URI.GREGORIAN then
		s = s - math.floor(y / 100)
		s = s + math.floor(y / 400)
	else
		s = s -2
	end
	return yb * 365 + s + list[point.month] + point.day
end

--[[
	pointOfTime:dayOfTheWeek
	returns:	number from 1 (sunday) to 7 (saturday) or
				nil (year before christian counting)
]]
p.point.dayOfTheWeek = function(this)
	local d = days(this)
	if d then return (days(this)+1) % 7 + 1
	else return nil end
end

--[[
	pointOfTime:setPrecision(level)
	pointOfTime.precision initialization by date string merely is a guess
]]
p.point.setPrecision = function(this, level)
	this.precision = level
end

local byDays = function(days, calendar)
	if not days then return nil end
	local o = {}
	if calendar then o.calendarmodel = calendar --####
	elseif days >= 577675 then o.calendarmodel = p.constant.URI.GREGORIAN
	else o.calendarmodel = p.constant.URI.JULIAN end
	o.era = '+'
	local febl = 28
	if o.calendarmodel == p.constant.URI.GREGORIAN then
		local yb = math.floor(days / 365.2425)
		days = days - math.floor(yb * 365.2425)
		o.year = yb + 1
		if (o.year % 400 == 0 or o.year % 100 > 0 and o.year % 4 == 0)
		and days > 60 then
			days = days - 1
			febl = 29
		end
	else 
		o.BC = true
		local yb = math.floor(days / 365.25)
		days = days - math.floor(yb * 365.25) + 2
		o.year = yb + 1
		if o.year % 4 == 0 and days > 60 then
			days = days - 1
			febl = 29
		end
	end
	list = {334, 303, 273, 242, 212, 181, 151, 120, 90, 59, 31, 0}
	local i = 1
	while not o.day do
		if days > list[i] then
			o.month = 13 - i
			o.day = days - list[i]
		end
		i = i + 1
	end
	list = {31, febl, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
	if o.day > list[o.month] then
		o.day = o.day - list[o.month]
		o.month = o.month + 1
	end
	if o.month > 12 then
		o.month = 1
		o.year = o.year + 1
	end
	o.precision = 11
	return p.point:new(o)
end

--[[
	pointOfTime: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 with all requested information or ''
]]
p.point.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
			fmtStr = p.constant.DEFAULTFORMAT[this.precision]
			if not fmtStr then return '' end
		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
		else
			if fmtStr:match('%%[yY]') then return '' end
		end
		if this.month then fmtStr = fmtStr:gsub('%%m', d2(this.month))
		else if fmtStr:match('%%m') then return '' end end
		if this.month then fmtStr = fmtStr:gsub('%%d', d2(this.day))
		else if fmtStr:match('%%d') then return '' end end
		if this.month then fmtStr = fmtStr:gsub('%%H', d2(this.hour))
		else if fmtStr:match('%%H') then return '' end end
		if this.month then fmtStr = fmtStr:gsub('%%M', d2(this.min))
		else if fmtStr:match('%%M') then return '' end end
		if this.month then fmtStr = fmtStr:gsub('%%S', d2(this.sec))
		else if fmtStr:match('%%S') then return '' end 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
		else if fmtStr:match('[jy][jy]') then return '' end end
		if this.month then
			fmtStr = fmtStr:gsub('mm', d2(this.month))
			fmtStr = fmtStr:gsub('m', this.month)
		else if fmtStr:match('m') then return '' end 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)
		else if fmtStr:match('[dt]') then return '' end end
		if this.hour then
			fmtStr = fmtStr:gsub('HH', d2(this.hour))
			fmtStr = fmtStr:gsub('H', this.hour)
		else if fmtStr:match('H') then return '' end end
		if this.min then
			fmtStr = fmtStr:gsub('MM', d2(this.min))
			fmtStr = fmtStr:gsub('M', this.min)
		else if fmtStr:match('M') then return '' end end
		if this.sec then
			fmtStr = fmtStr:gsub('SS', d2(this.sec))
			fmtStr = fmtStr:gsub('S', this.sec)
		else if fmtStr:match('S') then return '' end 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

--[[
	pointOfTime.isLessthan(a, b)
	datetime compare
	parameters:
		a, b	2 pointOfTime 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.point.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.constant.PRECISIONLEVEL[i]]
		local ab = b[p.constant.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
--[[
	pointOfTime.LT(a, b)
	comparator function for sort e.g.
]]
p.point.__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
--[[
	pointOfTime.LE(a, b)
	comparator function
]]
p.point.__le = function(a, b)
	if a.time then a = a.time end
	if b.time then b = b.time end
	if not b.isLessthan then return false end
	return not b:isLessthan(a)
end
--[[
	pointOfTime.GT(a, b)
	comparator function
]]
p.point.__gt = function(a, b)
	if a.time then a = a.time end
	if b.time then b = b.time end
	if not b.isLessthan then return false end
	return b:isLessthan(a)
end
--[[
	pointOfTime.LE(a, b)
	comparator function
]]
p.point.__ge = 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 not 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.point.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.constant.PRECISIONLEVEL[i]]
		local ab = b[p.constant.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
p.point.__eq = p.point.equals

--[[
	pointOfTime:gregorian()
	gregorian variant of a julian date
]]
p.point.gregorian = function(this)
	if this.calendarmodel == p.constant.URI.GREGORIAN then return this end
	local result = {}
	for k, v in pairs(this) do result[k] = v end
	setmetatable(result, self)
	self.__index = self
	local yDif = this.year - 1600
	local sdty = 0
	local sdtc = 0
	local cDif = 0
	local dDif = 0
	if this.year % 100 == 0 then
		if this.month > 2 then sdty = 1 end
		cDif = yDif / 100
		if yDif > 0 then
			
			if this.month > 2 then sdtc = 1 end
		else
			if this.month <= 2 then sdtc = -1 end
		end
	else
		if this.year % 4 == 0 and this.month > 2 then sdty = 1 end
		cDif = math.floor(yDif / 100)
	end
	local cDif = 10 + cDif - math.floor(yDif / 400)
end

---------------------- template service ----------------------
-- test, etc.
----------------------

local testLine = function(a, b, c)
	local result = '<tr style="background-color:'
	if type(b) == 'boolean' then if b then b = 'true' else b = 'false' end
	elseif b == nil then b = 'nil'
	elseif type(b) == 'table' then
		local t = b.format
		if t then b = t(b) end
	end
	if type(c) == 'boolean' then if c then c = 'true' else c = 'false' end
	elseif c == nil then c = 'nil'
	elseif type(c) == 'table' then
		local t = c.format
		if t then c = t(c) end
	end
	if b == c then result = result .. '#4f4">'
	else result = result .. '#f44">' end
	return	result .. '<td>' .. a .. '</td><td>' .. b .. '</td><td>' .. c
		..	'</td></tr>'
end

p.test = function()
	local result = '<table class="wikitable">'
	local a = p.point:new('1.3.2000')
	mw.logObject(a)
	result = result .. testLine("a:format('mm-dd')", a:format('mm-dd'), '03-01')
	local b = p.constant.STARTOFGREGORIAN
	result = result .. testLine("a, b", a, b)
	result = result .. testLine("a < b", a < b, false)
	result = result .. testLine("a > b", a > b, true)
	a = p.point:new('1582-08-15')
	result = result .. testLine("a, b", a, b)
	result = result .. testLine("a < b", a < b, false)
	result = result .. testLine("a == b", a == b, true)
	a = p.point:new()
	result = result .. testLine("today", a:format('d.m.yyyy'), '2.11.2022')
	result = result .. testLine("day of the week", a:dayOfTheWeek(), 4)
	result = result .. testLine("days of gregor", days(b), 577675)
	a = p.point:new('1582-08-04')
	result = result .. testLine("days before", days(a), 577674)
	for i = 50000, 50300 do
		a = byDays(i)
		mw.logObject(a, '=============== a:')
		b = byDays(days(a))
		result = result .. testLine("a, b: " .. i, a, b)
	end
	return result .. '</table>'
end

return p