Modul:Time
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
----------------------
local autoprecision = function(o, default)
if o.precision then return o.precision end
if o.sec then return 14 end
if o.min then return 13 end
if o.hour then return 12 end
if o.day then return 11 end
if o.month then return 10 end
return default
end
--[[
constructor
parameter:
source: either string that can be processed by
mw.language.getContentLanguage():formatDate
or table containing time information:
In this case negative julianian dates are accepted with an
offset of -1: 0 is translated to 1 BC, -1 = 2 BC, etc.
ATTENTION: This is usual but not in wikidata
]]
function p.point:new(source)
local o = nil
if source then
if type(source) == 'table' then
o = source
if o.year then
if o.year <= 0 then
o.era = '-'
o.year = 1 - o.year
o.calendarmodel = p.constant.URI.JULIAN
o.BC = true
else
if not o.era then o.era = '+' end
if o.BC then o.BC = false end
end
o.precision = autoprecision(o, 9)
else
o.era = 'n'
if o.BC then o.BC = false end
o.precision = autoprecision(o, 0)
end
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 lang = mw.language.getContentLanguage()
local success, t = pcall(lang.formatDate, lang, fsIn, source)
o.valid = success
if success then
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)
else
o.year = 0
o.month = 0
o.day = 0
end
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 < 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 and a.era ~= 'n' and b.era ~= 'n' 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 and not b then return true end
if not a then return false end
if not b then return false end
mw.logObject(a, '===== a')
mw.logObject(b, '===== b')
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)
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)
result = result .. testLine("a ~= b", a ~= b, false)
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)
result = result .. testLine("day of the week", b:dayOfTheWeek(), 2)
a = p.point:new('1582-08-04')
result = result .. testLine("days before", days(a), 577674)
result = result .. testLine("day of the week", a:dayOfTheWeek(), 1)
return result .. '</table>'
end
return p