Nicht angemeldeter Benutzer - Bearbeiten von Seiten ist nur als angemeldeter Benutzer möglich.
Modul:DateTime: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
(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, 10:36 Uhr
Vorlagenprogrammierung | Diskussionen | Lua | Unterseiten | |||
Modul | Deutsch | English
|
Modul: | Dokumentation |
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus |
Dieses Modul (und die Dokumentation) basieren (teilweise) auf Modul:DateTime aus der freien Enzyklopädie Wikipedia in der Fassung 127005311 vom 14. December 2019 und steht unter der GNU Lizenz für freie Dokumentation und der Creative Commons Attribution/Share Alike. Auf Wikipedia ist eine Liste der Autoren verfügbar. Veränderungen seither in Imedwiki. Veränderungen seither in Wikipedia.Weiteres zum Import aus Wikipedia siehe Seite Imedwiki:Import aus Wikipedia.
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