Modul:DateTime: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
Diese Vorlage(n) wurde(n) fast unverändert von der deutschsprachigen Wikipedia übernommen. Es wurden nur geringfügige technische, stilistische und organisatorische Anpassungen ans ÖsterreichWiki durchgeführt.
(2015-03-16) |
(2015-07-15) |
||
Zeile 1: | Zeile 1: | ||
-- | local DateTime -- Date and time utilities | ||
Date and time utilities | local Serial = "2015-07-15" | ||
local Calc = { } | |||
-- | |||
local | |||
local Parser = { } | local Parser = { } | ||
local Private = { } | local Private = { } | ||
local Prototypes = { } | local Prototypes = { } | ||
local Templates = { } | |||
local World = { slang = "en", | local World = { slang = "en", | ||
monthsLong = { }, | monthsLong = { }, | ||
Zeile 17: | Zeile 13: | ||
local Nbsp = mw.ustring.char( 160 ) | local Nbsp = mw.ustring.char( 160 ) | ||
local Tab = mw.ustring.char( 9 ) | local Tab = mw.ustring.char( 9 ) | ||
local Frame | |||
World.era = { en = { "BC", "AD" } } | World.era = { en = { "BC", "AD" } } | ||
World.monthsAbbr = { en = { n = 3 } } | World.monthsAbbr = { en = { n = 3 } } | ||
Zeile 116: | Zeile 113: | ||
return mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) ) | return mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) ) | ||
.. mw.ustring.lower( mw.ustring.sub( a, 2 ) ) | .. mw.ustring.lower( mw.ustring.sub( a, 2 ) ) | ||
end -- | end -- capitalize() | ||
Zeile 131: | Zeile 128: | ||
DateTime = function ( assign, alien ) | local function frame() | ||
-- Create | if not Frame then | ||
Frame = mw.getCurrentFrame() | |||
end | |||
return Frame | |||
end -- frame() | |||
local Signature = "__datetime" | |||
local Metatable = { | |||
__index = function ( self, access ) | |||
local r = self[ Signature ][ access ] | |||
if r == nil then | |||
if access == "serial" then | |||
r = Serial | |||
elseif access == "suite" then | |||
r = "DateTime" | |||
else | |||
for k, v in pairs( Prototypes ) do | |||
if k == access then | |||
r = Prototypes[ k ] | |||
end | |||
end -- for k, v | |||
end | |||
end | |||
return r | |||
end, | |||
__newindex = function ( self, access, assign ) | |||
if type( access ) == "string" then | |||
local data = self[ Signature ] | |||
if assign == nil then | |||
local val = data[ access ] | |||
data[ access ] = nil | |||
if not Prototypes.fair( data ) then | |||
data[ access ] = val | |||
end | |||
elseif Prototypes.fair( data, | |||
access, | |||
assign ) then | |||
data[ access ] = assign | |||
end | |||
end | |||
return | |||
end, | |||
__add = function ( op1, op2 ) | |||
return Prototypes.future( op1, op2 ) | |||
end, | |||
__eq = function ( op1, op2 ) | |||
return Prototypes.flow( op1, op2, "eq" ) | |||
end, | |||
__lt = function ( op1, op2 ) | |||
return Prototypes.flow( op1, op2, "lt" ) | |||
end, | |||
__le = function ( op1, op2 ) | |||
return Prototypes.flow( op1, op2, "le" ) | |||
end | |||
} | |||
DateTime = function ( assign, alien, add ) | |||
-- Create object (constructor) | |||
-- Parameter: | -- Parameter: | ||
-- assign -- string, with initial timestamp, or nil | -- assign -- string, with initial timestamp, or nil | ||
Zeile 138: | Zeile 196: | ||
-- false -- empty object | -- false -- empty object | ||
-- alien -- string, with language code, or nil | -- alien -- string, with language code, or nil | ||
-- add -- string, with interval (PHP strtotime), or nil | |||
-- Returns: | -- Returns: | ||
-- table, as DateTime object | -- table, as DateTime object | ||
Zeile 143: | Zeile 202: | ||
local r | local r | ||
Private.foreign() | Private.foreign() | ||
r = Private.factory( assign, alien ) | r = Private.factory( assign, alien, add ) | ||
if type( r ) == "table" then | if type( r ) == "table" then | ||
r = { [ Signature ] = r } | |||
setmetatable( r, Metatable ) | |||
r | |||
setmetatable( r, | |||
end | end | ||
return r | return r | ||
Zeile 190: | Zeile 212: | ||
Calc.months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } | |||
-- Calc.fast = function ( at ) | |||
-- -- Quick scan of full ISO stamp | |||
-- -- Parameter: | |||
-- -- apply -- string, ISO | |||
-- -- Returns: | |||
-- -- table, with numeric components | |||
-- local r = { } | |||
-- r.year = tonumber( at:sub( 1, 4 ) ) | |||
-- r.month = tonumber( at:sub( 6, 2 ) ) | |||
-- r.dom = tonumber( at:sub( 9, 2 ) ) | |||
-- r.hour = tonumber( at:sub( 12, 2 ) ) | |||
-- r.min = tonumber( at:sub( 14, 2 ) ) | |||
-- r.sec = tonumber( at:sub( 17, 2 ) ) | |||
-- if at:sub( 19, 1 ) == "." then | |||
-- r.msec = tonumber( at:sub( 20, 3 ) ) | |||
-- if #at > 22 then | |||
-- r.mysec = tonumber( at:sub( 23, 3 ) ) | |||
-- end | |||
-- end | |||
-- return r | |||
-- end -- Calc.fast() | |||
Calc.fair = function ( adjust ) | |||
-- Normalize numeric components | |||
-- Parameter: | -- Parameter: | ||
-- | -- adjust -- table, with raw numbers | ||
-- | local order = { "mysec", "msec", "sec", "min", "hour", | ||
-- | "dom", "month", "year" } | ||
-- | local ranges = { year = { min = -999, | ||
-- add | max = 9999 }, | ||
month = { min = 1, | |||
max = 12, | |||
mod = 12 }, | |||
dom = { min = 1, | |||
max = 28 }, | |||
hour = { mod = 24 }, | |||
min = { mod = 60 }, | |||
sec = { mod = 60 }, | |||
msec = { mod = 1000 }, | |||
mysec = { mod = 1000 } } | |||
local m, max, min, move, n, range | |||
for k, v in pairs( order ) do | |||
n = adjust[ v ] | |||
if n or move then | |||
range = ranges[ v ] | |||
min = range.min or 0 | |||
max = range.max or ( range.mod - 1 ) | |||
if move then | |||
n = ( n or 0 ) + move | |||
move = false | |||
end | |||
if n < min or n > max then | |||
if range.mod then | |||
m = n % range.mod | |||
move = ( n - m ) / range.mod | |||
n = min + m | |||
else -- dom | |||
if adjust.month and adjust.year and n > 1 and | |||
adjust.month >= 1 and | |||
adjust.month <= 12 and | |||
adjust.year > 1900 then | |||
max = Calc.months[ adjust.month ] | |||
if adjust.month == 2 and | |||
( adjust.year % 4 ~= 0 or | |||
adjust.year % 400 == 0 ) then | |||
max = 28 | |||
end | |||
if n <= max then | |||
max = false | |||
end | |||
end | |||
if max then | |||
m = n % 30 | |||
move = ( n - m ) / 30 | |||
n = 1 + m | |||
end | |||
end | |||
end | |||
adjust[ v ] = n | |||
end | |||
end -- for k, v | |||
end -- Calc.fair() | |||
Calc.future = function ( add ) | |||
-- Parse move interval | |||
-- Parameter: | |||
-- add -- string, with GNU relative items | |||
-- Returns: | -- Returns: | ||
-- table, | -- table, with numeric components, or false | ||
local r, token | |||
local | local units = { year = true, | ||
month = true, | |||
fortnight = { slot = "dom", mult = 14 }, | |||
week = { slot = "dom", mult = 7 }, | |||
dom = true, | |||
hour = true, | |||
min = true, | |||
sec = true } | |||
local story = string.format( " %s ", add:lower() ) | |||
:gsub( "%s+", " " ) | |||
:gsub( " yesterday ", " -1 dom " ) | |||
:gsub( " tomorrow ", " 1 dom " ) | |||
:gsub( "(%l)s ", "%1 " ) | |||
:gsub( " day ", " dom " ) | |||
:gsub( " minute ", " min " ) | |||
:gsub( " second ", " sec " ) | |||
local feed = function () | |||
local slice | |||
token, slice = story:match( "^( (%S+)) " ) | |||
return slice | |||
end | |||
local fed = function () | |||
story = story:sub( #token + 1 ) | |||
end | |||
local m, n, s, u | |||
while true do | |||
s = feed() | |||
if s then | |||
n = 1 | |||
if s:match( "^[+-]?%d+$" ) then | |||
n = tonumber( s ) | |||
fed() | |||
s = feed() | |||
end | |||
if s then | |||
u = units[ s ] | |||
end | |||
if s and u then | |||
fed() | |||
if u ~= true then | |||
s = u.slot | |||
n = n * u.mult | |||
end | |||
if feed() == "ago" then | |||
if n > 0 then | |||
n = - n | |||
end | |||
fed() | |||
end | |||
r = r or { } | |||
r[ s ] = ( r[ s ] or 0 ) + n | |||
else | |||
r = false | |||
break -- while true | |||
end | |||
else | else | ||
break -- while true | |||
end | end | ||
end -- while true | |||
return r | |||
end -- Calc.future() | |||
Parser.digitsHeading = function ( analyse, alone, amount, add ) | |||
-- String analysis, if digits only or at least 4 digits heading | |||
-- Parameter: | |||
-- analyse -- string to be scanned, starting with digit | |||
-- digits only, else starting with exactly 4 digits | |||
-- alone -- true, if only digits | |||
-- amount -- number of heading digits | |||
-- add -- table, to be extended | |||
-- Returns: | |||
-- table, extended if parsed | |||
-- false, if invalid text format | |||
local r = add | |||
if alone then | |||
-- digits only | |||
if amount <= 4 then | |||
r.year = tonumber( analyse ) | |||
elseif n == 14 then | |||
-- timestamp | |||
r.year = tonumber( analyse:sub( 1, 4 ) ) | |||
r.month = tonumber( analyse:sub( 5, 2 ) ) | |||
r.dom = tonumber( analyse:sub( 7, 2 ) ) | |||
r.hour = tonumber( analyse:sub( 9, 2 ) ) | |||
r.min = tonumber( analyse:sub( 11, 2 ) ) | |||
r.sec = tonumber( analyse:sub( 13, 2 ) ) | |||
else | else | ||
r = false | r = false | ||
end | end | ||
elseif amount == | elseif amount == 4 then | ||
local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" ) | |||
local s, | r.year = tonumber( s ) | ||
if sep == "-" then | |||
-- ISO | |||
s, sep, sx = sx:match( "^(%d%d)(-?)(.*)$" ) | |||
if s then | |||
r.month = tonumber( s ) | |||
r.month2 = true | |||
if sep == "-" then | |||
s, sep, sx = sx:match( "^(%d%d?)([ T]?)(.*)$" ) | |||
if s then | |||
r.dom = tonumber( s ) | |||
if sep == "T" then | |||
r.month2 = nil | |||
if | |||
s, | |||
if s = | |||
if | |||
r. | |||
else | else | ||
s = | r.dom2 = ( #s == 2 ) | ||
end | |||
if sep then | |||
r = Parser.time( sx, r, sep == "T" ) | |||
end | end | ||
else | else | ||
r = false | |||
end | end | ||
elseif sx and sx ~= "" then | |||
r = false | |||
end | end | ||
else | else | ||
r = false | |||
end | end | ||
elseif sep:lower() == "w" then | |||
if sx then | |||
s = sx:match( "^(%d%d?)$" ) | |||
if s then | |||
r.week = tonumber( s ) | |||
if r.week < 1 or r.week > 53 then | |||
r = false | |||
end | |||
else | |||
r = false | |||
end | |||
else | |||
r = false | |||
end | |||
else | |||
r = false | |||
end | |||
if r then | |||
r.iso = true | |||
end | end | ||
elseif amount == 8 then | |||
-- ISO compact | |||
local s, sz = analyse:match( "^%d+T(%d+)([.+-]?%d*%a*)$" ) | |||
if s then | if s then | ||
local n = #s | |||
if n == 2 or n == 4 or n == 6 then | |||
r.year = tonumber( analyse:sub( 1, 4 ) ) | |||
r.month = tonumber( analyse:sub( 5, 6 ) ) | |||
r.dom = tonumber( analyse:sub( 7, 8 ) ) | |||
r.hour = tonumber( analyse:sub( 10, 11 ) ) | |||
if n > 2 then | |||
r.min = tonumber( s:sub( 3, 4 ) ) | |||
if n == 6 then | |||
r.sec = tonumber( s:sub( 5, 6 ) ) | |||
end | |||
n, s = sz:match( "^(%.%d+)([+-]?[%a%d]*)$" ) | |||
if n then | |||
n = n .. "00" | |||
r.msec = tonumber( n:sub( 1, 3 ) ) | |||
if #n >= 6 then | |||
r.mysec = tonumber( n:sub( 4, 6 ) ) | |||
end | |||
sz = s | |||
end | |||
end | |||
if sz ~= "" then | |||
s, sz = sz:match( "^([+-]?)(%a*)$" ) | |||
if s == "" then | |||
if sz:match( "^(%u)$" ) then | |||
r.zone = sz | |||
else | |||
s = false | |||
end | |||
elseif #s == 1 then | |||
r.zone = s .. sz | |||
end | else | ||
return | s = false | ||
end -- Parser. | end | ||
end | |||
else | |||
s = false | |||
end | |||
end | |||
if s then | |||
r = false | |||
end | |||
end | |||
return r | |||
end -- Parser.digitsHeading() | |||
Parser. | Parser.eraGermanEnglish = function ( analyse ) | ||
-- String analysis, | -- String analysis, for German and English era | ||
-- v. Chr. v. u. Z. n. Chr. AD BC A.D. B.C. B.C.E. | |||
-- Parameter: | -- Parameter: | ||
-- analyse -- string | |||
-- analyse -- string | |||
-- Returns: | -- Returns: | ||
-- table, | -- 1 -- table, with boolean era, if any | ||
local | -- 2 -- string, with era stripped off, if any | ||
local s, | local rO = { } | ||
if | local rS = analyse | ||
local s, switch = analyse:match( "^(.+) ([vn])%. ?Chr%.$" ) | |||
if switch then | |||
rS = s | |||
rO.bc = ( switch == "v" ) | |||
elseif analyse:find( " v%. ?u%. ?Z%.$" ) then | |||
rS = analyse:match( "^(.+) v%. ?u%. ?Z%.$" ) | |||
rO.bc = true | |||
elseif analyse:find( " B%.? ?C%.? ?E?%.?$" ) then | |||
rS = analyse:match( "^(.+) B%.? ?C%.? ?E?%.?$" ) | |||
rO.bc = true | |||
elseif analyse:find( "^A%.? ?D%.? " ) then | |||
rS = analyse:match( "^A%.? ?D%.? (.*)$" ) | |||
rO.bc = false | |||
end | |||
return rO, rS | |||
end -- Parser.eraGermanEnglish() | |||
Parser.european = function ( ahead, adhere, analyse, assign ) | |||
-- String analysis, retrieve date style: DOM MONTH YEAR | |||
-- Parameter: | |||
-- ahead -- string, with first digits, not more than 2 | |||
-- adhere -- string, with first separator; not ":" | |||
-- 23 | -- analyse -- string, remainder following adhere | ||
s, sx = | -- assign -- table | ||
-- Returns: | |||
-- table, extended if parsed | |||
local r = assign | |||
local s, s2, sx | |||
if adhere == "." or adhere == ". " then | |||
-- 23.12.2013 | |||
-- 23. Dezember 2013 | |||
s, sx = analyse:match( "^(%d%d?)%.(.*)$" ) | |||
if s then | if s then | ||
r = Parser.putDate( false, s, ahead, assign ) | |||
r = Parser.yearTime( sx, r ) | |||
else | else | ||
r = false | s, sx = mw.ustring.match( analyse, | ||
"^ ?([%a&;]+%.?) ?(.*)$" ) | |||
if s then | |||
local n = Parser.monthNumber( s ) | |||
if n then | |||
r.month = n | |||
r.dom = tonumber( ahead ) | |||
r.dom2 = ( #ahead == 2 ) | |||
r = Parser.yearTime( sx, r ) | |||
else | |||
r = false | |||
end | |||
else | |||
r = false | |||
end | |||
end | end | ||
else | elseif adhere == " " then | ||
r = false | -- 23 Dec 2013 | ||
end | s, sx = mw.ustring.match( analyse, | ||
return r | "^([%a&;]+%.?) ?(.*)$" ) | ||
end -- Parser.european() | if s then | ||
local n = Parser.monthNumber( s ) | |||
if n then | |||
r.month = n | |||
r.dom = tonumber( ahead ) | |||
r.dom2 = ( #ahead == 2 ) | |||
r = Parser.yearTime( sx, r ) | |||
else | |||
r = false | |||
end | |||
else | |||
r = false | |||
end | |||
else | |||
r = false | |||
end | |||
return r | |||
end -- Parser.european() | |||
Zeile 461: | Zeile 651: | ||
rO = false | rO = false | ||
end | end | ||
if | if rO then | ||
rO.iso = true | |||
else | |||
rS = false | rS = false | ||
end | end | ||
Zeile 640: | Zeile 832: | ||
s = sx:match( "^%.(%d+)$" ) | s = sx:match( "^%.(%d+)$" ) | ||
if s then | if s then | ||
r.msec = tonumber( s ) | s = s .. "00" | ||
r.msec = tonumber( s:sub( 1, 3 ) ) | |||
if #s >= 6 then | |||
r.mysec = tonumber( s:sub( 4, 6 ) ) | |||
end | |||
else | else | ||
r = false | r = false | ||
Zeile 861: | Zeile 1.057: | ||
Private.factory = function ( assign, alien ) | Private.factory = function ( assign, alien, add ) | ||
-- Create DateTime table (constructor) | -- Create DateTime table (constructor) | ||
-- Parameter: | -- Parameter: | ||
Zeile 868: | Zeile 1.064: | ||
-- false -- empty object | -- false -- empty object | ||
-- alien -- string, with language code, or nil | -- alien -- string, with language code, or nil | ||
-- add -- string, with interval (PHP strtotime), or nil | |||
-- Returns: | -- Returns: | ||
-- table, for DateTime object | -- table, for DateTime object | ||
-- string or false, if failed | -- string or false, if failed | ||
local l = true | local l = true | ||
local slang = mw.text.trim( alien or World.slang or "en" ) | local slang = mw.text.trim( alien or World.slang or "en" ) | ||
local r | |||
if assign == false then | if assign == false then | ||
r = { } | r = { } | ||
else | else | ||
local stamp = ( assign or "now" ) | local stamp = ( assign or "now" ) | ||
local shift | |||
if add then | |||
shift = Private.future( add ) | |||
end | |||
r = false | |||
if stamp == "now" then | if stamp == "now" then | ||
stamp = | stamp = frame():callParserFunction( "#timel", "c", shift ) | ||
shift = false | |||
else | else | ||
local seconds = stamp:match( "^#(%d+)$" ) | local seconds = stamp:match( "^#(%d+)$" ) | ||
Zeile 888: | Zeile 1.090: | ||
end | end | ||
end | end | ||
l, r = pcall( Private.fetch, stamp, slang ) | l, r = pcall( Private.fetch, stamp, slang, shift ) | ||
end | end | ||
if l and type( r ) == "table" then | if l and type( r ) == "table" then | ||
Zeile 900: | Zeile 1.102: | ||
Private.fetch = function ( analyse, alien ) | Private.fetch = function ( analyse, alien, add ) | ||
-- Retrieve object from string | -- Retrieve object from string | ||
-- Parameter: | -- Parameter: | ||
-- analyse -- string to be interpreted | -- analyse -- string to be interpreted | ||
-- alien -- string with language code, or nil | -- alien -- string with language code, or nil | ||
-- add -- string, with interval (PHP strtotime), or nil | |||
-- Returns: | -- Returns: | ||
-- table, if parsed | -- table, if parsed | ||
Zeile 940: | Zeile 1.143: | ||
if not Prototypes.fair( r ) then | if not Prototypes.fair( r ) then | ||
r = false | r = false | ||
elseif add then | |||
r = Prototypes.future( r, add ) | |||
end | end | ||
end | end | ||
Zeile 954: | Zeile 1.159: | ||
Private.foreign = | Private.flow = function ( at1, at2 ) | ||
-- Retrieve localization submodule | -- Compare two objects | ||
if not World.localization then | -- Parameter: | ||
-- at1 -- DateTime | |||
-- at2 -- DateTime | |||
-- Returns: | |||
-- -1, 0, 1 or nil if not comparable | |||
local r = 0 | |||
if at1.bc or at2.bc and at1.bc ~= at2.bc then | |||
if at1.bc then | |||
r = -1 | |||
else | |||
r = 1 | |||
end | |||
else | |||
local order = { "year", "month", "week", "dom", | |||
"hour", "min", "sec", "msec", "mysec" } | |||
local life = false | |||
local s, v1, v2 | |||
for i = 1, 9 do | |||
s = order[ i ] | |||
v1 = at1[ s ] | |||
v2 = at2[ s ] | |||
if v1 or v2 then | |||
if v1 and v2 then | |||
if v1 < v2 then | |||
r = -1 | |||
elseif v1 > v2 then | |||
r = 1 | |||
end | |||
elseif life then | |||
if v2 then | |||
r = -1 | |||
else | |||
r = 1 | |||
end | |||
else | |||
r = nil | |||
end | |||
if r ~= 0 then | |||
if at1.bc and r then | |||
r = r * -1 | |||
end | |||
break -- for i | |||
end | |||
life = true | |||
end | |||
end -- for i | |||
end | |||
return r | |||
end -- Private.flow() | |||
Private.foreign = function () | |||
-- Retrieve localization submodule | |||
if not World.localization then | |||
local l, d = pcall( mw.loadData, "Module:DateTime/local" ) | local l, d = pcall( mw.loadData, "Module:DateTime/local" ) | ||
if l then | if l then | ||
Zeile 980: | Zeile 1.239: | ||
Private.future = function ( add ) | |||
-- | -- Normalize move interval | ||
-- Parameter: | -- Parameter: | ||
-- | -- add -- string or number, to be added | ||
-- Returns: | -- Returns: | ||
-- true, if valid; false, if not | -- string, with shift, or false/nil | ||
local r = ( type( self ) == "table" ) | local r | ||
if add then | |||
local s = type( add ) | |||
if s == "string" and add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then | |||
r = tonumber( add ) | |||
s = "number" | |||
else | |||
r = add | |||
end | |||
if s == "number" then | |||
if r == 0 then | |||
r = false | |||
else | |||
r = string.format( "%d second", r ) | |||
end | |||
elseif s ~= "string" then | |||
r = false | |||
end | |||
if r then | |||
r = Calc.future( r ) | |||
end | |||
end | |||
return r | |||
end -- Private.future() | |||
Prototypes.fair = function ( self, access, assign ) | |||
-- Check formal validity of table | |||
-- Parameter: | |||
-- self -- table, to be checked | |||
-- access -- string or nil, single item to be checked | |||
-- assign -- single access value to be checked | |||
-- Returns: | |||
-- true, if valid; false, if not | |||
local r = ( type( self ) == "table" ) | |||
if r then | if r then | ||
local defs = { year = { max = MaxYear }, | local defs = { year = { max = MaxYear }, | ||
month = { min = 1, | month = { min = 1, | ||
max = 12 }, | max = 12 }, | ||
week = { min = 1, | |||
max = 53 }, | |||
dom = { min = 1, | dom = { min = 1, | ||
max = 31 }, | max = 31 }, | ||
Zeile 998: | Zeile 1.292: | ||
min = { max = 59 }, | min = { max = 59 }, | ||
sec = { max = 61 }, | sec = { max = 61 }, | ||
msec = { max = | msec = { max = 999 }, | ||
mysec = { max = 999 } | |||
} | } | ||
local fNum = | local fNum = | ||
function ( k, v ) | function ( k, v ) | ||
Zeile 1.044: | Zeile 1.338: | ||
end | end | ||
if m then | if m then | ||
ret = ( d <= months[ m ] ) | ret = ( d <= Calc.months[ m ] ) | ||
if ret then | if ret then | ||
local y | local y | ||
Zeile 1.105: | Zeile 1.399: | ||
end | end | ||
else | else | ||
local order = { "bc", "year", "month", "dom", | local order = { "bc", "year", "month", "week", "dom", | ||
"hour", "min", "sec", "msec" } | "hour", "min", "sec", "msec", "mysec" } | ||
local life = false | local life = false | ||
local leak = false | local leak = false | ||
local s, v | local s, v | ||
for i = 1, | for i = 1, 10 do | ||
s = order[ i ] | s = order[ i ] | ||
v = self[ s ] | v = self[ s ] | ||
Zeile 1.121: | Zeile 1.415: | ||
if not fNum( s, v ) then | if not fNum( s, v ) then | ||
r = false | r = false | ||
break | break -- for i | ||
end | end | ||
life = true | life = true | ||
leak = true | leak = true | ||
end | end | ||
elseif i == 3 then | |||
if not self.week then | |||
life = false | |||
end | |||
elseif i ~= 4 then | |||
life = false | life = false | ||
end | end | ||
end -- for i | end -- for i | ||
if self.week and ( self.month or self.dom ) then | |||
r = false | |||
end | |||
end | end | ||
end | end | ||
Zeile 1.191: | Zeile 1.492: | ||
Prototypes.format = function ( self, ask, adapt ) | Prototypes.flow = function ( self, another, assert ) | ||
-- Format object as string | -- Compare this object with another timestamp | ||
-- Parameter: | |||
-- self -- table, with numbers etc. | |||
-- another -- DateTime or string or nil (now) | |||
-- assert -- nil, or string with operator | |||
-- "lt", "le", "eq", "ne", "ge", "gt", | |||
-- "<", "<=", "==", "~=", "<>", ">=", "=>", ">" | |||
-- Returns: | |||
-- if assert: true or false | |||
-- else: -1, 0, 1 | |||
-- nil if invalid | |||
local r | |||
if type( self ) == "table" then | |||
local d2 | |||
if type( another ) == "table" then | |||
d2 = another | |||
else | |||
d2 = DateTime( another ) | |||
end | |||
if type( d2 ) == "table" then | |||
r = Private.flow( self, d2 ) | |||
if r and type( assert ) == "string" then | |||
local trsl = { lt = "<", | |||
["<"] = "<", | |||
le = "<=", | |||
["<="] = "<=", | |||
eq = "=", | |||
["=="] = "=", | |||
ne = "<>", | |||
["<>"] = "<>", | |||
["~="] = "<>", | |||
ge = ">=", | |||
[">="] = ">=", | |||
["=>"] = ">=", | |||
gt = ">", | |||
[">"] = ">" } | |||
local same = trsl[ assert:lower() ] | |||
if same then | |||
local s = "=" | |||
if r < 0 then | |||
s = "<" | |||
elseif r > 0 then | |||
s = ">" | |||
end | |||
r = ( same:find( s, 1, true ) ~= nil ) | |||
else | |||
r = nil | |||
end | |||
end | |||
end | |||
end | |||
return r | |||
end -- Prototypes.flow() | |||
Prototypes.format = function ( self, ask, adapt ) | |||
-- Format object as string | |||
-- Parameter: | -- Parameter: | ||
-- self -- table, with numbers etc. | -- self -- table, with numbers etc. | ||
Zeile 1.198: | Zeile 1.556: | ||
-- adapt -- table, with options, or nil | -- adapt -- table, with options, or nil | ||
-- .lang -- string, with particular language code | -- .lang -- string, with particular language code | ||
-- .london -- true: UTC output; default: local | |||
-- .lonely -- true: permit lonely hour | -- .lonely -- true: permit lonely hour | ||
-- Returns: | -- Returns: | ||
Zeile 1.249: | Zeile 1.608: | ||
stamp, self.sec ) | stamp, self.sec ) | ||
if self.msec then | if self.msec then | ||
stamp = string.format( "%s.% | stamp = string.format( "%s.%03d", | ||
stamp, self.msec ) | stamp, self.msec ) | ||
if self.mysec then | |||
stamp = string.format( "%s%03d", | |||
stamp, | |||
self.mysec ) | |||
end | |||
end | end | ||
end | end | ||
Zeile 1.349: | Zeile 1.713: | ||
Prototypes.future = function ( self, add ) | |||
-- | -- Relative move by interval | ||
-- Parameter: | -- Parameter: | ||
-- | -- self -- table, to be used as base | ||
-- | -- add -- string or number, to be added | ||
-- Returns: | -- Returns: | ||
-- | -- table, with shift | ||
local r, raw, rel, shift | |||
if type( self ) == "table" then | |||
r = self | |||
shift = add | |||
elseif type( add ) == "table" then | |||
r = add | |||
shift = self | |||
end | |||
if r then | |||
raw = r[ Signature ] | |||
rel = Private.future( shift ) | |||
end | |||
if raw and rel then | |||
for k, v in pairs( rel ) do | |||
raw[ k ] = ( raw[ k ] or 0 ) + v | |||
end -- for k, v | |||
Calc.fair( raw ) | |||
end | |||
return r | |||
end -- Prototypes.future() | |||
Templates.flow = function ( frame, action ) | |||
-- Comparison invokation | |||
-- Parameter: | |||
-- frame -- object | |||
-- Returns: | |||
-- string, either "" or "1" | |||
local r | |||
local s1 = frame.args[ 1 ] | |||
local s2 = frame.args[ 2 ] | |||
if s1 then | |||
s1 = mw.text.trim( s1 ) | |||
if s1 == "" then | |||
s1 = false | |||
end | end | ||
end | |||
local | if s2 then | ||
s2 = mw.text.trim( s2 ) | |||
if s2 == "" then | |||
s2 = false | |||
end | |||
end | |||
if s1 or s2 then | |||
local l | |||
Frame = frame | |||
l, r = pcall( Prototypes.flow, | |||
DateTime( s1 ), s2, action ) | |||
if r == true then | |||
r = "1" | |||
end | |||
end | |||
return r or "" | |||
end -- Templates.flow() | |||
World.templates.formatter = function ( assigned, ask, adapt ) | |||
-- Retrieve format specification string | |||
-- Parameter: | |||
-- assigned -- table, with numbers etc. | |||
-- ask -- string, format spec, or nil | |||
-- adapt -- table, with options | |||
-- .lang -- string, with particular language code | |||
-- .lonely -- true: permit lonely hour | |||
-- Returns: | |||
-- 1 -- string | |||
-- 2 -- string or nil; append suffix (zone) | |||
local r1, r2 | |||
if not ask or ask == "" then | |||
r1 = "c" | |||
else | |||
local template = World.templates[ ask ] | |||
r1 = ask | |||
if not template then | |||
local slang = ( adapt.lang or assigned.lang or World.slang ) | |||
local tmp = World.templates[ slang ] | |||
if tmp then | |||
template = tmp[ ask ] | |||
end | |||
end | |||
if type( template ) == "table" then | |||
local low = ( ask == "ISO" or ask == "ISO-T" ) | |||
r1 = template.spec | |||
if assigned.year then | |||
if not assigned.dom then | if not assigned.dom then | ||
r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" ) | r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" ) | ||
:gsub( "^ ", "" ) | :gsub( "^ ", "" ) | ||
if not assigned.month then | |||
r1 = r1:gsub( "[ .%-]?[FmMnt][ .%-]*", "" ) | |||
end | |||
end | |||
else | |||
r1 = r1:gsub( " ?[yY] ?", "" ) | |||
if not assigned.dom then | |||
r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" ) | |||
:gsub( "^ ", "" ) | |||
end | end | ||
end | end | ||
Zeile 1.402: | Zeile 1.831: | ||
stamp = "H:i:s" | stamp = "H:i:s" | ||
if assigned.msec then | if assigned.msec then | ||
stamp = string.format( "%s.% | stamp = string.format( "%s.%03d", | ||
stamp, | stamp, | ||
assigned.msec ) | assigned.msec ) | ||
if assigned.mysec then | |||
stamp = string.format( "%s.%03d", | |||
stamp, | |||
assigned.mysec ) | |||
end | |||
end | end | ||
end | end | ||
Zeile 1.539: | Zeile 1.973: | ||
function p.test( args ) | function p.test( args ) | ||
local slang = args[ 3 ] | |||
local obj = DateTime( args[ 1 ], slang or "de", args[ 4 ] ) | |||
local r | local r | ||
if type( obj ) == "table" then | if type( obj ) == "table" then | ||
local spec = args[ 2 ] | |||
local opt | local opt | ||
if spec then | if spec then | ||
spec = mw.text.trim( spec ) | spec = mw.text.trim( spec ) | ||
end | end | ||
if slang then | if slang then | ||
opt = { lang = mw.text.trim( slang ) } | opt = { lang = mw.text.trim( slang ) } | ||
end | end | ||
r = obj:format( spec, opt ) | r = obj:format( spec, opt ) | ||
else | else | ||
r = ( args.noerror or "0" ) | r = ( args.noerror or "0" ) | ||
if r == "0" then | if r == "0" then | ||
r = fault( "Format nicht erkannt" ) | r = fault( "Format nicht erkannt" ) | ||
else | else | ||
r = "" | r = "" | ||
end | end | ||
end | end | ||
return r | return r | ||
end -- p.test | end -- p.test | ||
function p.failsafe( frame ) | |||
local since = frame.args[ 1 ] | |||
local r | |||
if since then | |||
since = mw.text.trim( since ) | |||
if since == "" then | |||
since = false | |||
end | |||
end | |||
if since then | |||
if since > Serial then | |||
r = "" | |||
else | |||
r = Serial | |||
end | |||
else | |||
r = Serial | |||
end | |||
return r | |||
end -- p.failsafe | |||
function p.format( frame ) | |||
-- 1 -- stamp | |||
-- 2 -- spec | |||
-- 3 -- slang | |||
-- 4 -- shift | |||
local l, r | |||
local v = { frame.args[ 1 ], | |||
frame.args[ 2 ], | |||
frame.args[ 3 ], | |||
frame.args[ 4 ], | |||
noerror = frame.args.noerror } | |||
if not v[ 1 ] or v[ 1 ] == "now" then | |||
v[ 1 ] = frame:callParserFunction( "#timel", "c", v[ 4 ] ) | |||
v[ 4 ] = false | |||
end | |||
Frame = frame | |||
l, r = pcall( p.test, v ) | |||
if not l then | |||
r = fault( r ) | |||
end | |||
return r | |||
end -- p.format | |||
function p. | function p.lt( frame ) | ||
return Templates.flow( frame, "lt" ) | |||
end -- p.lt | |||
function p.le( frame ) | |||
return Templates.flow( frame, "le" ) | |||
end -- p.le | |||
function p.eq( frame ) | |||
return Templates.flow( frame, "eq" ) | |||
end -- p.eq | |||
function p.ne( frame ) | |||
return Templates.flow( frame, "ne" ) | |||
end -- p.ne | |||
function p.ge( frame ) | |||
return | return Templates.flow( frame, "ge" ) | ||
end -- p. | end -- p.ge | ||
function p.gt( frame ) | |||
return Templates.flow( frame, "gt" ) | |||
end -- p.gt | |||
Version vom 15. Juli 2015, 08:36 Uhr
local DateTime -- Date and time utilities
local Serial = "2015-07-15"
local Calc = { }
local Parser = { }
local Private = { }
local Prototypes = { }
local Templates = { }
local World = { slang = "en",
monthsLong = { },
monthsParse = { },
months4 = { } }
local MaxYear = 2099
local Nbsp = mw.ustring.char( 160 )
local Tab = mw.ustring.char( 9 )
local Frame
World.era = { en = { "BC", "AD" } }
World.monthsAbbr = { en = { n = 3 } }
World.monthsLong.en = { "January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
}
World.monthsParse.en = { [ "Apr" ] = 4,
[ "Aug" ] = 8,
[ "Dec" ] = 12,
[ "Feb" ] = 2,
[ "Jan" ] = 1,
[ "Jul" ] = 7,
[ "Jun" ] = 6,
[ "Mar" ] = 3,
[ "May" ] = 5,
[ "Nov" ] = 11,
[ "Oct" ] = 10,
[ "Sep" ] = 9
}
World.months4.en = { [ 6 ] = true,
[ 7 ] = true }
World.templates = { [ "ISO" ] =
{ spec = "Y-m-d",
lift = true },
[ "ISO-T" ] =
{ spec = "c" },
[ "timestamp" ] =
{ spec = "YmdHis" },
[ "default" ] =
{ spec = "H:i, j M Y",
long = true },
[ "$dmy" ] =
{ spec = "H:i, j M Y",
long = true },
[ "$ymd" ] =
{ spec = "H:i, Y M j",
long = true },
[ "$dmyt" ] =
{ spec = "j M Y, H:i",
long = true },
[ "$dmyts" ] =
{ spec = "j M Y, H:i:s",
long = true },
[ "data-sort-type:date" ] =
{ spec = "j M Y" }
}
World.templates.en = { }
World.zones = {
[ "!" ] = "YXWVUTSRQPONZABCDEFGHIKLM",
UTC = 0,
GMT = 0 -- Greenwich Mean Time
}
World.zones.en = {
BST = 100, -- British Summer Time
IST = 100, -- Irish Summer Time
WET = 0, -- Western Europe Time
WEST = 100, -- Western Europe Summer Time
CET = 100, -- Central Europe Time
CEST = 200, -- Central Europe Summer Time
EET = 200, -- Eastern Europe Time
EEST = 300, -- Eastern Europe Summer Time
MSK = 300, -- Moscow Time
MSD = 400, -- Moscow Summer Time
NST = -330, -- Newfoundland Standard Time
NDT = -230, -- Newfoundland Daylight Time
AST = -400, -- Atlantic Standard Time
ADT = -300, -- Atlantic Daylight Time
EST = -500, -- Eastern Standard Time
EDT = -400, -- Eastern Daylight Saving Time
CST = -600, -- Central Standard Time
CDT = -500, -- Central Daylight Saving Time
MST = -700, -- Mountain Standard Time
MDT = -600, -- Mountain Daylight Saving Time
PST = -800, -- Pacific Standard Time
PDT = -700, -- Pacific Daylight Saving Time
AKST = -900, -- Alaska Standard Time
AKDT = -800, -- Alaska Standard Daylight Saving Time
HST = -1000 -- Hawaiian Standard Time
}
local function capitalize( a )
-- Upcase first character, downcase anything else
-- Parameter:
-- a -- string
-- Returns:
-- string
return mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) )
.. mw.ustring.lower( mw.ustring.sub( a, 2 ) )
end -- capitalize()
local function fault( a )
-- Format error message by class=error
-- Parameter:
-- a -- string, error message
-- Returns:
-- string, HTML span
return string.format( "<span class=\"error\">%s</span>", a )
end -- fault()
local function frame()
if not Frame then
Frame = mw.getCurrentFrame()
end
return Frame
end -- frame()
local Signature = "__datetime"
local Metatable = {
__index = function ( self, access )
local r = self[ Signature ][ access ]
if r == nil then
if access == "serial" then
r = Serial
elseif access == "suite" then
r = "DateTime"
else
for k, v in pairs( Prototypes ) do
if k == access then
r = Prototypes[ k ]
end
end -- for k, v
end
end
return r
end,
__newindex = function ( self, access, assign )
if type( access ) == "string" then
local data = self[ Signature ]
if assign == nil then
local val = data[ access ]
data[ access ] = nil
if not Prototypes.fair( data ) then
data[ access ] = val
end
elseif Prototypes.fair( data,
access,
assign ) then
data[ access ] = assign
end
end
return
end,
__add = function ( op1, op2 )
return Prototypes.future( op1, op2 )
end,
__eq = function ( op1, op2 )
return Prototypes.flow( op1, op2, "eq" )
end,
__lt = function ( op1, op2 )
return Prototypes.flow( op1, op2, "lt" )
end,
__le = function ( op1, op2 )
return Prototypes.flow( op1, op2, "le" )
end
}
DateTime = function ( assign, alien, add )
-- Create object (constructor)
-- Parameter:
-- assign -- string, with initial timestamp, or nil
-- nil -- now
-- false -- empty object
-- alien -- string, with language code, or nil
-- add -- string, with interval (PHP strtotime), or nil
-- Returns:
-- table, as DateTime object
-- string or false, if failed
local r
Private.foreign()
r = Private.factory( assign, alien, add )
if type( r ) == "table" then
r = { [ Signature ] = r }
setmetatable( r, Metatable )
end
return r
end -- DateTime()
Calc.months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
-- Calc.fast = function ( at )
-- -- Quick scan of full ISO stamp
-- -- Parameter:
-- -- apply -- string, ISO
-- -- Returns:
-- -- table, with numeric components
-- local r = { }
-- r.year = tonumber( at:sub( 1, 4 ) )
-- r.month = tonumber( at:sub( 6, 2 ) )
-- r.dom = tonumber( at:sub( 9, 2 ) )
-- r.hour = tonumber( at:sub( 12, 2 ) )
-- r.min = tonumber( at:sub( 14, 2 ) )
-- r.sec = tonumber( at:sub( 17, 2 ) )
-- if at:sub( 19, 1 ) == "." then
-- r.msec = tonumber( at:sub( 20, 3 ) )
-- if #at > 22 then
-- r.mysec = tonumber( at:sub( 23, 3 ) )
-- end
-- end
-- return r
-- end -- Calc.fast()
Calc.fair = function ( adjust )
-- Normalize numeric components
-- Parameter:
-- adjust -- table, with raw numbers
local order = { "mysec", "msec", "sec", "min", "hour",
"dom", "month", "year" }
local ranges = { year = { min = -999,
max = 9999 },
month = { min = 1,
max = 12,
mod = 12 },
dom = { min = 1,
max = 28 },
hour = { mod = 24 },
min = { mod = 60 },
sec = { mod = 60 },
msec = { mod = 1000 },
mysec = { mod = 1000 } }
local m, max, min, move, n, range
for k, v in pairs( order ) do
n = adjust[ v ]
if n or move then
range = ranges[ v ]
min = range.min or 0
max = range.max or ( range.mod - 1 )
if move then
n = ( n or 0 ) + move
move = false
end
if n < min or n > max then
if range.mod then
m = n % range.mod
move = ( n - m ) / range.mod
n = min + m
else -- dom
if adjust.month and adjust.year and n > 1 and
adjust.month >= 1 and
adjust.month <= 12 and
adjust.year > 1900 then
max = Calc.months[ adjust.month ]
if adjust.month == 2 and
( adjust.year % 4 ~= 0 or
adjust.year % 400 == 0 ) then
max = 28
end
if n <= max then
max = false
end
end
if max then
m = n % 30
move = ( n - m ) / 30
n = 1 + m
end
end
end
adjust[ v ] = n
end
end -- for k, v
end -- Calc.fair()
Calc.future = function ( add )
-- Parse move interval
-- Parameter:
-- add -- string, with GNU relative items
-- Returns:
-- table, with numeric components, or false
local r, token
local units = { year = true,
month = true,
fortnight = { slot = "dom", mult = 14 },
week = { slot = "dom", mult = 7 },
dom = true,
hour = true,
min = true,
sec = true }
local story = string.format( " %s ", add:lower() )
:gsub( "%s+", " " )
:gsub( " yesterday ", " -1 dom " )
:gsub( " tomorrow ", " 1 dom " )
:gsub( "(%l)s ", "%1 " )
:gsub( " day ", " dom " )
:gsub( " minute ", " min " )
:gsub( " second ", " sec " )
local feed = function ()
local slice
token, slice = story:match( "^( (%S+)) " )
return slice
end
local fed = function ()
story = story:sub( #token + 1 )
end
local m, n, s, u
while true do
s = feed()
if s then
n = 1
if s:match( "^[+-]?%d+$" ) then
n = tonumber( s )
fed()
s = feed()
end
if s then
u = units[ s ]
end
if s and u then
fed()
if u ~= true then
s = u.slot
n = n * u.mult
end
if feed() == "ago" then
if n > 0 then
n = - n
end
fed()
end
r = r or { }
r[ s ] = ( r[ s ] or 0 ) + n
else
r = false
break -- while true
end
else
break -- while true
end
end -- while true
return r
end -- Calc.future()
Parser.digitsHeading = function ( analyse, alone, amount, add )
-- String analysis, if digits only or at least 4 digits heading
-- Parameter:
-- analyse -- string to be scanned, starting with digit
-- digits only, else starting with exactly 4 digits
-- alone -- true, if only digits
-- amount -- number of heading digits
-- add -- table, to be extended
-- Returns:
-- table, extended if parsed
-- false, if invalid text format
local r = add
if alone then
-- digits only
if amount <= 4 then
r.year = tonumber( analyse )
elseif n == 14 then
-- timestamp
r.year = tonumber( analyse:sub( 1, 4 ) )
r.month = tonumber( analyse:sub( 5, 2 ) )
r.dom = tonumber( analyse:sub( 7, 2 ) )
r.hour = tonumber( analyse:sub( 9, 2 ) )
r.min = tonumber( analyse:sub( 11, 2 ) )
r.sec = tonumber( analyse:sub( 13, 2 ) )
else
r = false
end
elseif amount == 4 then
local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" )
r.year = tonumber( s )
if sep == "-" then
-- ISO
s, sep, sx = sx:match( "^(%d%d)(-?)(.*)$" )
if s then
r.month = tonumber( s )
r.month2 = true
if sep == "-" then
s, sep, sx = sx:match( "^(%d%d?)([ T]?)(.*)$" )
if s then
r.dom = tonumber( s )
if sep == "T" then
r.month2 = nil
else
r.dom2 = ( #s == 2 )
end
if sep then
r = Parser.time( sx, r, sep == "T" )
end
else
r = false
end
elseif sx and sx ~= "" then
r = false
end
else
r = false
end
elseif sep:lower() == "w" then
if sx then
s = sx:match( "^(%d%d?)$" )
if s then
r.week = tonumber( s )
if r.week < 1 or r.week > 53 then
r = false
end
else
r = false
end
else
r = false
end
else
r = false
end
if r then
r.iso = true
end
elseif amount == 8 then
-- ISO compact
local s, sz = analyse:match( "^%d+T(%d+)([.+-]?%d*%a*)$" )
if s then
local n = #s
if n == 2 or n == 4 or n == 6 then
r.year = tonumber( analyse:sub( 1, 4 ) )
r.month = tonumber( analyse:sub( 5, 6 ) )
r.dom = tonumber( analyse:sub( 7, 8 ) )
r.hour = tonumber( analyse:sub( 10, 11 ) )
if n > 2 then
r.min = tonumber( s:sub( 3, 4 ) )
if n == 6 then
r.sec = tonumber( s:sub( 5, 6 ) )
end
n, s = sz:match( "^(%.%d+)([+-]?[%a%d]*)$" )
if n then
n = n .. "00"
r.msec = tonumber( n:sub( 1, 3 ) )
if #n >= 6 then
r.mysec = tonumber( n:sub( 4, 6 ) )
end
sz = s
end
end
if sz ~= "" then
s, sz = sz:match( "^([+-]?)(%a*)$" )
if s == "" then
if sz:match( "^(%u)$" ) then
r.zone = sz
else
s = false
end
elseif #s == 1 then
r.zone = s .. sz
else
s = false
end
end
else
s = false
end
end
if s then
r = false
end
end
return r
end -- Parser.digitsHeading()
Parser.eraGermanEnglish = function ( analyse )
-- String analysis, for German and English era
-- v. Chr. v. u. Z. n. Chr. AD BC A.D. B.C. B.C.E.
-- Parameter:
-- analyse -- string
-- Returns:
-- 1 -- table, with boolean era, if any
-- 2 -- string, with era stripped off, if any
local rO = { }
local rS = analyse
local s, switch = analyse:match( "^(.+) ([vn])%. ?Chr%.$" )
if switch then
rS = s
rO.bc = ( switch == "v" )
elseif analyse:find( " v%. ?u%. ?Z%.$" ) then
rS = analyse:match( "^(.+) v%. ?u%. ?Z%.$" )
rO.bc = true
elseif analyse:find( " B%.? ?C%.? ?E?%.?$" ) then
rS = analyse:match( "^(.+) B%.? ?C%.? ?E?%.?$" )
rO.bc = true
elseif analyse:find( "^A%.? ?D%.? " ) then
rS = analyse:match( "^A%.? ?D%.? (.*)$" )
rO.bc = false
end
return rO, rS
end -- Parser.eraGermanEnglish()
Parser.european = function ( ahead, adhere, analyse, assign )
-- String analysis, retrieve date style: DOM MONTH YEAR
-- Parameter:
-- ahead -- string, with first digits, not more than 2
-- adhere -- string, with first separator; not ":"
-- analyse -- string, remainder following adhere
-- assign -- table
-- Returns:
-- table, extended if parsed
local r = assign
local s, s2, sx
if adhere == "." or adhere == ". " then
-- 23.12.2013
-- 23. Dezember 2013
s, sx = analyse:match( "^(%d%d?)%.(.*)$" )
if s then
r = Parser.putDate( false, s, ahead, assign )
r = Parser.yearTime( sx, r )
else
s, sx = mw.ustring.match( analyse,
"^ ?([%a&;]+%.?) ?(.*)$" )
if s then
local n = Parser.monthNumber( s )
if n then
r.month = n
r.dom = tonumber( ahead )
r.dom2 = ( #ahead == 2 )
r = Parser.yearTime( sx, r )
else
r = false
end
else
r = false
end
end
elseif adhere == " " then
-- 23 Dec 2013
s, sx = mw.ustring.match( analyse,
"^([%a&;]+%.?) ?(.*)$" )
if s then
local n = Parser.monthNumber( s )
if n then
r.month = n
r.dom = tonumber( ahead )
r.dom2 = ( #ahead == 2 )
r = Parser.yearTime( sx, r )
else
r = false
end
else
r = false
end
else
r = false
end
return r
end -- Parser.european()
Parser.isoDate = function ( analyse, assign )
-- String analysis, retrieve month heading ISO date
-- Parameter:
-- analyse -- string, with heading hyphen
-- assign -- table
-- Returns:
-- 1 -- table, extended if parsed
-- 2 -- stripped string, or false, if invalid text format
local rO, rS
if analyse:match( "^%-%-?[0-9]" ) then
local n, s
rO = assign
rS = analyse:sub( 2 )
s = rS:match( "^([012][0-9])%-" )
if s then
n = tonumber( s )
if n >= 1 and n <= 12 then
rO.month = n
rS = rS:sub( 3 )
else
rO = false
end
end
if rO then
if rS:byte( 1, 1 ) == 45 then
local suffix
s = rS:match( "^%-([012][0-9])" )
if s then
n = tonumber( s )
if n >= 1 and n <= 31 then
rO.dom = n
rS = rS:sub( 4 )
else
rO = false
end
else
rS:sub( 2 )
end
else
rO = false
end
if rO then
if #rS > 0 then
if rO.dom then
n = rS:byte( 1, 1 )
if n == 32 or n == 84 then
rS = rS:sub( 2 )
else
rO = false
end
else
rO = false
end
end
end
end
else
rO = false
end
if rO then
rO.iso = true
else
rS = false
end
return rO, rS
end -- Parser.isoDate()
Parser.monthHeading = function ( analyse, assign )
-- String analysis, retrieve month heading date (US only)
-- Parameter:
-- analyse -- string, with heading word
-- assign -- table
-- Returns:
-- 1 -- table, extended if parsed
-- 2 -- stripped string, or false, if invalid text format
local rO = assign
local rS = analyse
local s, sep = mw.ustring.match( analyse, "^([%a&;]+%.?)([^%a%.]?)" )
if s then
-- might begin with month name "December 23, 2013"
local n = Parser.monthNumber( s )
if n then
rO.month = n
if sep == "" then
rS = ""
else
local s2, s3
n = mw.ustring.len( s ) + 1
s = mw.ustring.sub( analyse, n )
s2 = s:match( "^ (%d%d%d?%d?)$" )
if s2 then
rO.year = tonumber( s2 )
rS = ""
else
s2, s3, rS = s:match( "^ (%d+), (%d+)( ?.*)$" )
if s2 and s3 then
n = #s2
if n <= 2 and #s3 == 4 then
rO.dom = tonumber( n )
rO.year = tonumber( s3 )
rO.dom2 = ( n == 2 )
else
rO = false
end
else
rO = false
end
end
end
else
rO = false
end
else
rO = false
end
if not rO then
rS = false
end
return rO, rS
end -- Parser.monthHeading()
Parser.monthNumber = function ( analyse )
-- String analysis, retrieve month number
-- Parameter:
-- analyse -- string, with month name including any period
-- Returns:
-- number, 1...12 if found
-- false or nil, if not detected
local r = false
local s = mw.ustring.match( analyse, "^([%a&;]+)%.?$" )
if s then
local given
s = capitalize( s )
for k, v in pairs( World.monthsLong ) do
given = World.monthsParse[ k ]
if given then
r = given[ s ]
end
if not r then
given = World.monthsLong[ k ]
for i = 1, 12 do
if given[ i ] == s then
r = i
break
end
end -- for i
end
if r then
break
end
end -- for k, v
end
return r
end -- Parser.monthNumber()
Parser.putDate = function ( aYear, aMonth, aDom, assign )
-- Store date strings
-- Parameter:
-- aYear -- string, with year, or false
-- aMonth -- string, with numeric month
-- aDom -- string, with day of month
-- assign -- table
-- Returns:
-- table, extended
local r = assign
if aYear then
r.year = tonumber( aYear )
end
r.month = tonumber( aMonth )
r.dom = tonumber( aDom )
r.month2 = ( #aMonth == 2 )
r.dom2 = ( #aDom == 2 )
return r
end -- Parser.putDate()
Parser.time = function ( analyse, assign, adjusted )
-- String analysis, retrieve time components
-- Parameter:
-- analyse -- string, with time part
-- assign -- table
-- adjusted -- true: fixed length of 2 digits expected
-- Returns:
-- table, extended if parsed
-- false, if invalid text format
local r = assign
if analyse ~= "" then
local s, sx = analyse:match( "^(%d+)(:?.*)$" )
if s then
local n = #s
if n <= 2 then
r.hour = tonumber( s )
if not adjusted then
r.hour2 = ( n == 2 )
end
else
sx = false
r = false
end
if sx then
s, sx = sx:match( "^:(%d+)(:?(.*))$" )
if s then
if #s == 2 then
r.min = tonumber( s )
if sx == "" then
sx = false
end
else
sx = false
r = false
end
if sx then
local sep
local scan = "^([:,] ?)(%d+)(.*)$"
sep, s, sx = sx:match( scan )
if sep == ":" then
if #s == 2 then
r.sec = tonumber( s )
end
elseif sep == ", " then
r = Parser.wikiDate( s .. sx, r )
sx = false
else
r = false
end
end
else
r = false
end
end
if sx and sx ~= "" then
s = sx:match( "^%.(%d+)$" )
if s then
s = s .. "00"
r.msec = tonumber( s:sub( 1, 3 ) )
if #s >= 6 then
r.mysec = tonumber( s:sub( 4, 6 ) )
end
else
r = false
end
end
else
r = false
end
end
return r
end -- Parser.time()
Parser.wikiDate = function ( analyse, assign )
-- String analysis, for date after wiki ~~~~~ signature time
-- dmy 10:28, 30. Dez. 2013
-- ymd 10:28, 2013 Dez. 30
-- Parameter:
-- analyse -- string
-- assign -- table
-- Returns:
-- table, extended if parsed
-- false, if invalid text format
local r
local s = analyse:match( "^(2%d%d%d) " )
local sx
if s then
-- ymd "10:28, 2013 Dez. 30"
local n = false
r = assign
r.year = tonumber( s )
s = analyse:sub( 6 )
s, sx = mw.ustring.match( analyse:sub( 6 ),
"^([%a&;]+)%.? (%d%d?)$" )
if s then
n = Parser.monthNumber( s )
if n then
r.month = n
end
end
if n then
r.dom = tonumber( sx )
r.dom2 = ( #sx == 2 )
else
r = false
end
else
-- dmy "10:28, 30. Dez. 2013"
local sep
s, sep, sx = analyse:match( "^(%d%d?)(%.? ?)(%a.+)$" )
if s then
r = Parser.european( s, sep, sx, assign )
else
r = false
end
end
return r
end -- Parser.wikiDate()
Parser.yearTime = function ( analyse, assign )
-- String analysis, for possible year and possible time
-- Parameter:
-- analyse -- string, starting with year
-- assign -- table
-- Returns:
-- table, extended if parsed
-- false, if invalid text format
local r = assign
local n = #analyse
if n > 0 then
local s, sx
if n == 4 then
if analyse:match( "^%d%d%d%d$" ) then
s = analyse
sx = false
end
else
s = analyse:match( "^(%d%d%d%d)[ ,]" )
if s then
sx = analyse:sub( 5 )
else
local suffix
s, sx, suffix = analyse:match( "^(%d+)([ ,]?)(.*)$" )
if s then
local j = #sx
n = #s
if n < 4 and ( j == 1 or #suffix == 0 ) then
sx = analyse:sub( n + j )
else
s = false
end
end
end
end
if s then
r.year = tonumber( s )
if sx then
s, sx = sx:match( "^(,? ?)(%d.*)$" )
if #s >= 1 then
r = Parser.time( sx, r )
end
end
else
r = false
end
end
return r
end -- Parser.yearTime()
Parser.zone = function ( analyse, assign )
-- String analysis, for time zone
-- +/-nn +/-nnnn (AAAa)
-- Parameter:
-- analyse -- string
-- assign -- table
-- Returns:
-- 1 -- table, with number or string zone, if any, or false
-- 2 -- string, with zone stripped off, if any
local rO = assign
local rS = analyse
local s, sign, shift, sub
s = "^(.+)([+-])([01]%d):?(%d?%d?)$"
s, sign, shift, sub = analyse:match( s )
if sign then
if s:find( ":%d%d *$" ) then
if sub then
if #sub == 2 then
rO.zone = tonumber( shift .. sub )
else
rO = false
end
else
rO.zone = tonumber( shift ) * 100
end
if rO then
if sign == "-" then
rO.zone = - rO.zone
end
rS = mw.text.trim( s )
end
end
elseif analyse:find( "%(.*%)$" ) then
s, shift = analyse:match( "^(.+)%((%a%a%a%a?)%)$" )
if shift then
rO.zone = shift:upper()
rS = mw.text.trim( s )
else
rO = false
end
else
s, shift = analyse:match( "^(.+%d) ?(%a+)$" )
if shift then
local n = #shift
if n == 1 then
rO.zone = shift:upper()
elseif n == 3 then
if shift == "UTC" or shift == "GMT" then
rO.zone = 0
end
end
if rO.zone then
rS = s
end
end
end
return rO, rS
end -- Parser.zone()
Parser.GermanEnglish = function ( analyse )
-- String analysis, for German and English formats
-- Parameter:
-- analyse -- string, with date or time or parts of it
-- Returns:
-- table, if parsed
-- false, if invalid text format
local r, s = Parser.eraGermanEnglish( analyse )
r, s = Parser.zone( s, r )
if r then
local start, sep, sx = s:match( "^(%d+)([ %-%.:WwT]?)(.*)$" )
if start then
-- begins with one or more digits (ASCII)
local n = #start
local lazy = ( start == s and
( n >=4 or type( r.bc == "boolean" ) ) )
if n == 4 or n == 8 or lazy then
r = Parser.digitsHeading( s, lazy, n, r )
elseif n <= 2 then
if sep == ":" then
r, s = Parser.time( s, r )
elseif sep == "" then
r = false
else
r = Parser.european( start, sep, sx, r )
end
else
r = false
end
else
local rM, sM = Parser.monthHeading( s, r )
if rM then
r = rM
else
r, sM = Parser.isoDate( s, r )
end
if r and sM ~= "" then
r = Parser.time( sM, r )
end
end
end
return r
end -- Parser.GermanEnglish()
Private.factory = function ( assign, alien, add )
-- Create DateTime table (constructor)
-- Parameter:
-- assign -- string, with initial timestamp, or nil
-- nil -- now
-- false -- empty object
-- alien -- string, with language code, or nil
-- add -- string, with interval (PHP strtotime), or nil
-- Returns:
-- table, for DateTime object
-- string or false, if failed
local l = true
local slang = mw.text.trim( alien or World.slang or "en" )
local r
if assign == false then
r = { }
else
local stamp = ( assign or "now" )
local shift
if add then
shift = Private.future( add )
end
r = false
if stamp == "now" then
stamp = frame():callParserFunction( "#timel", "c", shift )
shift = false
else
local seconds = stamp:match( "^#(%d+)$" )
if seconds then
stamp = os.date( "!%Y-%m-%dT%H:%M:%S",
tonumber( seconds ) )
end
end
l, r = pcall( Private.fetch, stamp, slang, shift )
end
if l and type( r ) == "table" then
if slang ~= "" then
r.lang = slang
end
end
return r
end -- Private.factory()
Private.fetch = function ( analyse, alien, add )
-- Retrieve object from string
-- Parameter:
-- analyse -- string to be interpreted
-- alien -- string with language code, or nil
-- add -- string, with interval (PHP strtotime), or nil
-- Returns:
-- table, if parsed
-- false, if invalid text format
-- string, if serious error (args)
local r
if type( analyse ) == "string" then
r = analyse:gsub( " ", " " )
:gsub( " ", " " )
:gsub( "&#x[aA]0;", " " )
:gsub( " ", " " )
:gsub( Nbsp, " " )
:gsub( Tab, " " )
:gsub( " +", " " )
:gsub( "%[%[", "" )
:gsub( "%]%]", "" )
r = mw.text.trim( r )
if r == "" then
r = { }
else
local slang = ( alien or "" )
if slang == "" then
slang = "en"
else
local s = slang:match( "^(%a+)%-" )
if s then
slang = s
end
end
slang = slang:lower()
if slang == "en" or slang == "de" then
local l
l, r = pcall( Parser.GermanEnglish, r )
if l and r then
if not Prototypes.fair( r ) then
r = false
elseif add then
r = Prototypes.future( r, add )
end
end
else
r = "unknown language"
end
end
else
r = "bad type"
end
return r
end -- Private.fetch()
Private.flow = function ( at1, at2 )
-- Compare two objects
-- Parameter:
-- at1 -- DateTime
-- at2 -- DateTime
-- Returns:
-- -1, 0, 1 or nil if not comparable
local r = 0
if at1.bc or at2.bc and at1.bc ~= at2.bc then
if at1.bc then
r = -1
else
r = 1
end
else
local order = { "year", "month", "week", "dom",
"hour", "min", "sec", "msec", "mysec" }
local life = false
local s, v1, v2
for i = 1, 9 do
s = order[ i ]
v1 = at1[ s ]
v2 = at2[ s ]
if v1 or v2 then
if v1 and v2 then
if v1 < v2 then
r = -1
elseif v1 > v2 then
r = 1
end
elseif life then
if v2 then
r = -1
else
r = 1
end
else
r = nil
end
if r ~= 0 then
if at1.bc and r then
r = r * -1
end
break -- for i
end
life = true
end
end -- for i
end
return r
end -- Private.flow()
Private.foreign = function ()
-- Retrieve localization submodule
if not World.localization then
local l, d = pcall( mw.loadData, "Module:DateTime/local" )
if l then
local wk
if d.slang then
World.slang = d.slang
end
for k, v in pairs( d ) do
wk = World[ k ]
if wk and wk.en then
for subk, subv in pairs( v ) do
wk[ subk ] = subv
end -- for k, v
else
World[ k ] = v
end
end -- for k, v
end
World.localization = true
end
end -- Private.foreign()
Private.future = function ( add )
-- Normalize move interval
-- Parameter:
-- add -- string or number, to be added
-- Returns:
-- string, with shift, or false/nil
local r
if add then
local s = type( add )
if s == "string" and add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then
r = tonumber( add )
s = "number"
else
r = add
end
if s == "number" then
if r == 0 then
r = false
else
r = string.format( "%d second", r )
end
elseif s ~= "string" then
r = false
end
if r then
r = Calc.future( r )
end
end
return r
end -- Private.future()
Prototypes.fair = function ( self, access, assign )
-- Check formal validity of table
-- Parameter:
-- self -- table, to be checked
-- access -- string or nil, single item to be checked
-- assign -- single access value to be checked
-- Returns:
-- true, if valid; false, if not
local r = ( type( self ) == "table" )
if r then
local defs = { year = { max = MaxYear },
month = { min = 1,
max = 12 },
week = { min = 1,
max = 53 },
dom = { min = 1,
max = 31 },
hour = { max = 23 },
min = { max = 59 },
sec = { max = 61 },
msec = { max = 999 },
mysec = { max = 999 }
}
local fNum =
function ( k, v )
local ret = true
local dk = defs[ k ]
if dk then
if type( dk.max ) == "number" then
ret = ( type( v ) == "number" )
if ret then
local min
if dk.min then
min = dk.min
else
min = 0
end
ret = ( v >= min and v <= dk.max
and math.floor( v ) == v )
if ret and dk.f then
ret = dk.f( v )
end
end
end
end
return ret
end -- fNum()
defs.dom.f =
function ()
local ret
local d
if access == "dom" then
d = assign
else
d = self.dom
end
if d then
ret = ( d <= 28 )
if not ret then
local m
if access == "month" then
m = assign
else
m = self.month
end
if m then
ret = ( d <= Calc.months[ m ] )
if ret then
local y
if access == "year" then
y = assign
else
y = self.year
end
if d == 29 and m == 2 and y then
if y % 4 ~= 0 or y % 400 == 0 then
ret = false
end
end
end
end
end
else
ret = true
end
return ret
end -- defs.dom.f()
defs.sec.f =
function ()
local ret
local second
if access == "sec" then
second = assign
else
second = self.sec
end
if second then
ret = ( second <= 59 )
if not ret and self.leap then
ret = true
end
end
return ret
end -- defs.sec.f()
if access or assign then
r = ( type( access ) == "string" )
if r then
local def = defs[ access ]
if def then
r = fNum( access, assign )
if r then
if def == "dom" or
def == "month" or
def == "year" then
r = defs.dom.f()
end
end
elseif access == "lang" then
r = ( type( assign ) == "string" )
if r then
r = assign:match( "^%l%l%l?-?%a*$" )
end
elseif access == "london" then
r = ( type( assign ) == "boolean" )
end
end
else
local order = { "bc", "year", "month", "week", "dom",
"hour", "min", "sec", "msec", "mysec" }
local life = false
local leak = false
local s, v
for i = 1, 10 do
s = order[ i ]
v = self[ s ]
if v then
if not life and leak then
-- gap detected
r = false
break
else
if not fNum( s, v ) then
r = false
break -- for i
end
life = true
leak = true
end
elseif i == 3 then
if not self.week then
life = false
end
elseif i ~= 4 then
life = false
end
end -- for i
if self.week and ( self.month or self.dom ) then
r = false
end
end
end
return r
end -- Prototypes.fair()
Prototypes.figure = function ( self, assign )
-- Assign month by name
-- Parameter:
-- self -- table, to be filled
-- assign -- string, with month name
-- Returns:
-- number 1...12, if valid; false, if not
local r = false
if type( self ) == "table" and type( assign ) == "string" then
r = Parser.monthNumber( assign )
if r then
self.month = r
end
end
return r
end -- Prototypes.figure()
Prototypes.first = function ( self )
-- Retrieve abbreviated month name in current language
-- Parameter:
-- self -- table, to be evaluated
-- Returns:
-- string, if defined; false, if not
local r
if type( self ) == "table" and self.month then
local slang = ( self.lang or World.slang )
r = World.monthsLong[ slang ]
if r then
local brief = World.monthsAbbr[ slang ]
r = r[ self.month ]
if brief then
local ex = brief[ self.month ]
local s = brief.suffix
if ex then
r = ex[ 2 ]
else
local n = brief.n or 3
r = mw.ustring.sub( r, 1, n )
end
if s then
r = r .. s
end
end
end
else
r = false
end
return r
end -- Prototypes.first()
Prototypes.flow = function ( self, another, assert )
-- Compare this object with another timestamp
-- Parameter:
-- self -- table, with numbers etc.
-- another -- DateTime or string or nil (now)
-- assert -- nil, or string with operator
-- "lt", "le", "eq", "ne", "ge", "gt",
-- "<", "<=", "==", "~=", "<>", ">=", "=>", ">"
-- Returns:
-- if assert: true or false
-- else: -1, 0, 1
-- nil if invalid
local r
if type( self ) == "table" then
local d2
if type( another ) == "table" then
d2 = another
else
d2 = DateTime( another )
end
if type( d2 ) == "table" then
r = Private.flow( self, d2 )
if r and type( assert ) == "string" then
local trsl = { lt = "<",
["<"] = "<",
le = "<=",
["<="] = "<=",
eq = "=",
["=="] = "=",
ne = "<>",
["<>"] = "<>",
["~="] = "<>",
ge = ">=",
[">="] = ">=",
["=>"] = ">=",
gt = ">",
[">"] = ">" }
local same = trsl[ assert:lower() ]
if same then
local s = "="
if r < 0 then
s = "<"
elseif r > 0 then
s = ">"
end
r = ( same:find( s, 1, true ) ~= nil )
else
r = nil
end
end
end
end
return r
end -- Prototypes.flow()
Prototypes.format = function ( self, ask, adapt )
-- Format object as string
-- Parameter:
-- self -- table, with numbers etc.
-- ask -- string, format spec, or nil
-- adapt -- table, with options, or nil
-- .lang -- string, with particular language code
-- .london -- true: UTC output; default: local
-- .lonely -- true: permit lonely hour
-- Returns:
-- string, or false, if invalid
local r = false
if type( self ) == "table" then
local opts = { lang = self.lang }
local babel, slang
if type( adapt ) == "table" then
if type( adapt.lang ) == "string" then
local i = adapt.lang:find( "-", 3, true )
if i then
slang = adapt.lang
opts.lang = slang:sub( 1, i - 1 )
else
opts.lang = adapt.lang
end
end
opts.lang = opts.lang:lower()
opts.london = adapt.london
opts.lonely = adapt.lonely
end
babel = mw.language.new( opts.lang )
if babel then
local shift, show, stamp, suffix, limit4, locally
if self.month then
stamp = World.monthsLong.en[ self.month ]
if self.year then
stamp = string.format( "%s %04d", stamp, self.year )
end
if self.dom then
stamp = string.format( "%d %s", self.dom, stamp )
end
if ask and ask:find( "Mon4" ) then
local mon4 = World.months4[ opts.lang ]
if mon4 then
if mon4[ self.month ] then
limit4 = true
end
end
end
elseif self.year then
stamp = string.format( "%04d", self.year )
end
if self.hour then
stamp = string.format( "%s %02d:", stamp, self.hour )
if self.min then
stamp = string.format( "%s%02d", stamp, self.min )
if self.sec then
stamp = string.format( "%s:%02d",
stamp, self.sec )
if self.msec then
stamp = string.format( "%s.%03d",
stamp, self.msec )
if self.mysec then
stamp = string.format( "%s%03d",
stamp,
self.mysec )
end
end
end
else
stamp = stamp .. "00"
end
if self.zone then
stamp = stamp .. World.zones.formatter( self, "+-" )
end
end
show, suffix = World.templates.formatter( self, ask, opts )
if limit4 then
show = show:gsub( "M", "F" )
end
if type( opts.london ) == "boolean" then
locally = not opts.london
else
locally = true
end
r = babel:formatDate( show, stamp, locally )
r = r:gsub( " $", "" )
if self.year and self.year < 1000 then
r = r:gsub( string.format( "%04d", self.year ),
tostring( self.year ) )
end
if self.month then
local bucket, m, suite, x
if show:find( "F" ) then
suite = "monthsLong"
elseif show:find( "M" ) then
suite = "monthsAbbr"
end
bucket = World[ suite ]
if bucket then
m = bucket[ opts.lang ]
if slang then
x = bucket[ slang ]
end
if m then
local base = m[ self.month ]
local ex
if x then
ex = x[ self.month ]
end
if suite == "monthsAbbr" then
local stop
if ex then
stop = x.suffix
base = ex
else
stop = m.suffix
end
if base and stop then
local shift, std
std = string.format( "%s%%%s",
base[ 1 ], stop )
shift = string.format( "%s%s",
base[ 2 ], stop )
r = mw.ustring.gsub( r, std, shift )
end
elseif suite == "monthsLong" then
if base and ex then
r = mw.ustring.gsub( r, base, ex )
end
end
end
end
end
if suffix then
r = r .. suffix
end
end
end
return r
end -- Prototypes.format()
Prototypes.full = function ( self )
-- Retrieve month name in current language
-- Parameter:
-- self -- table, to be evaluated
-- Returns:
-- string, if defined; false, if not
local r
if type( self ) == "table" and self.month then
local slang = ( self.lang or World.slang )
r = World.monthsLong[ slang ]
if r then
r = r[ self.month ]
end
else
r = false
end
return r
end -- Prototypes.full()
Prototypes.future = function ( self, add )
-- Relative move by interval
-- Parameter:
-- self -- table, to be used as base
-- add -- string or number, to be added
-- Returns:
-- table, with shift
local r, raw, rel, shift
if type( self ) == "table" then
r = self
shift = add
elseif type( add ) == "table" then
r = add
shift = self
end
if r then
raw = r[ Signature ]
rel = Private.future( shift )
end
if raw and rel then
for k, v in pairs( rel ) do
raw[ k ] = ( raw[ k ] or 0 ) + v
end -- for k, v
Calc.fair( raw )
end
return r
end -- Prototypes.future()
Templates.flow = function ( frame, action )
-- Comparison invokation
-- Parameter:
-- frame -- object
-- Returns:
-- string, either "" or "1"
local r
local s1 = frame.args[ 1 ]
local s2 = frame.args[ 2 ]
if s1 then
s1 = mw.text.trim( s1 )
if s1 == "" then
s1 = false
end
end
if s2 then
s2 = mw.text.trim( s2 )
if s2 == "" then
s2 = false
end
end
if s1 or s2 then
local l
Frame = frame
l, r = pcall( Prototypes.flow,
DateTime( s1 ), s2, action )
if r == true then
r = "1"
end
end
return r or ""
end -- Templates.flow()
World.templates.formatter = function ( assigned, ask, adapt )
-- Retrieve format specification string
-- Parameter:
-- assigned -- table, with numbers etc.
-- ask -- string, format spec, or nil
-- adapt -- table, with options
-- .lang -- string, with particular language code
-- .lonely -- true: permit lonely hour
-- Returns:
-- 1 -- string
-- 2 -- string or nil; append suffix (zone)
local r1, r2
if not ask or ask == "" then
r1 = "c"
else
local template = World.templates[ ask ]
r1 = ask
if not template then
local slang = ( adapt.lang or assigned.lang or World.slang )
local tmp = World.templates[ slang ]
if tmp then
template = tmp[ ask ]
end
end
if type( template ) == "table" then
local low = ( ask == "ISO" or ask == "ISO-T" )
r1 = template.spec
if assigned.year then
if not assigned.dom then
r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" )
:gsub( "^ ", "" )
if not assigned.month then
r1 = r1:gsub( "[ .%-]?[FmMnt][ .%-]*", "" )
end
end
else
r1 = r1:gsub( " ?[yY] ?", "" )
if not assigned.dom then
r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" )
:gsub( "^ ", "" )
end
end
if template.lift and
(assigned.dom or
not (assigned.month or assigned.year or assigned.bc)
) then
local stamp = false
if assigned.hour then
if assigned.min then
stamp = "H:i"
if assigned.sec then
stamp = "H:i:s"
if assigned.msec then
stamp = string.format( "%s.%03d",
stamp,
assigned.msec )
if assigned.mysec then
stamp = string.format( "%s.%03d",
stamp,
assigned.mysec )
end
end
end
elseif adapt.lonely then
stamp = "H"
end
end
if low or ask:find( "hh:mm:ss" ) then
if stamp then
r1 = string.format( "%s %s", r1, stamp )
end
end
if stamp then
if low or template.long then
local scheme
if template.long then
scheme = mw.language.getContentLanguage()
scheme = scheme.code
end
r2 = World.zones.formatter( assigned, scheme )
end
end
end
if type ( assigned.bc ) == "boolean" then
local eras = World.era[ adapt.lang ] or World.era.en
local i
if not r2 then
r2 = ""
end
if assigned.bc then
i = 1
else
i = 2
end
r2 = string.format( "%s %s", r2, eras[ i ] )
end
end
end
return r1, r2
end -- World.templates.formatter()
World.zones.formatter = function ( assigned, align )
-- Retrieve time zone specification string
-- Parameter:
-- assigned -- table, with numbers etc.
-- .zone should be available
-- align -- string, format spec, or nil
-- nil, false, "+-" -- +/- 0000
-- "Z" -- single letter
-- "UTC" -- "UTC", if appropriate
-- "de" -- try localized
-- Returns:
-- string
local r = ""
local move = 0
if assigned.zone then
local s = type( assigned.zone )
if s == "string" then
s = assigned.zone:upper()
if #s == 1 then
-- "YXWVUTSRQPONZABCDEFGHIKLM"
move = World.zones[ "!" ]:find( s )
if move then
move = ( move - 13 ) * 100
assigned.zone = move
else
assigned.zone = false
end
else
local code = World.zones[ s ]
if not code then
local slang = ( assigned.lang or
World.slang )
local tmp = World.zones[ slang ]
if tmp then
code = tmp[ s ]
end
end
if code then
move = code
assigned.zone = move
end
end
elseif s == "number" then
move = assigned.zone
end
end
if move then
local spec = "+-"
if align then
if align == "Z" then
if move % 100 == 0 then
r = World.zones[ "!" ]:sub( move / 100 + 13, 1 )
spec = false
end
elseif align ~= "+-" then
if move == 0 then
r = " UTC"
spec = false
else
local part = World.zones[ align ]
if part then
for k, v in pairs( part ) do
if v == move then
r = string.format( " (%s)", k )
spec = false
break
end
end -- for k, v
end
end
end
end
if spec == "+-" then
if move < 0 then
spec = "%4.4d"
else
spec = "+%4.4d"
end
r = string.format( spec, move )
r = string.format( "%s:%s",
r:sub( 1, 3), r:sub( 4 ) )
end
end
return r
end -- World.zones.formatter()
-- Export
local p = { }
function p.test( args )
local slang = args[ 3 ]
local obj = DateTime( args[ 1 ], slang or "de", args[ 4 ] )
local r
if type( obj ) == "table" then
local spec = args[ 2 ]
local opt
if spec then
spec = mw.text.trim( spec )
end
if slang then
opt = { lang = mw.text.trim( slang ) }
end
r = obj:format( spec, opt )
else
r = ( args.noerror or "0" )
if r == "0" then
r = fault( "Format nicht erkannt" )
else
r = ""
end
end
return r
end -- p.test
function p.failsafe( frame )
local since = frame.args[ 1 ]
local r
if since then
since = mw.text.trim( since )
if since == "" then
since = false
end
end
if since then
if since > Serial then
r = ""
else
r = Serial
end
else
r = Serial
end
return r
end -- p.failsafe
function p.format( frame )
-- 1 -- stamp
-- 2 -- spec
-- 3 -- slang
-- 4 -- shift
local l, r
local v = { frame.args[ 1 ],
frame.args[ 2 ],
frame.args[ 3 ],
frame.args[ 4 ],
noerror = frame.args.noerror }
if not v[ 1 ] or v[ 1 ] == "now" then
v[ 1 ] = frame:callParserFunction( "#timel", "c", v[ 4 ] )
v[ 4 ] = false
end
Frame = frame
l, r = pcall( p.test, v )
if not l then
r = fault( r )
end
return r
end -- p.format
function p.lt( frame )
return Templates.flow( frame, "lt" )
end -- p.lt
function p.le( frame )
return Templates.flow( frame, "le" )
end -- p.le
function p.eq( frame )
return Templates.flow( frame, "eq" )
end -- p.eq
function p.ne( frame )
return Templates.flow( frame, "ne" )
end -- p.ne
function p.ge( frame )
return Templates.flow( frame, "ge" )
end -- p.ge
function p.gt( frame )
return Templates.flow( frame, "gt" )
end -- p.gt
p.DateTime = function ( ... )
return DateTime( ... )
end -- p.DateTime
return p