Modul:Time
Erscheinungsbild
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
p.point.__ne = function(this)
return not p.point.equals(this)
end
--[[
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, 70000 do
a = byDays(i)
b = byDays(days(a))
if a ~= b then result = result .. testLine("a, b: " .. i, a, b) end
end
return result .. '</table>'
end
return p