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.
(update) |
K (32 Versionen importiert: Doku-Vorlage) |
||
(27 dazwischenliegende Versionen von 8 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
-- | local DateTime = { serial = "2020-04-08", | ||
Date and time | suite = "DateTime", | ||
item = 20652535 } | |||
-- Date and time objects | |||
local Failsafe = DateTime | |||
local GlobalMod = DateTime | |||
local Calc = { } | |||
local | local Meta = { } | ||
local Parser = { } | local Parser = { } | ||
local Private = { } | local Private = { } | ||
local Prototypes = { } | local Prototypes = { } | ||
local Templates = { } | |||
local World = { slang = "en", | local World = { slang = "en", | ||
monthsLong = { }, | monthsLong = { }, | ||
monthsParse = { } } | monthsParse = { }, | ||
months4 = { } } | |||
local MaxYear = 2099 | |||
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 43: | Zeile 47: | ||
[ "Sep" ] = 9 | [ "Sep" ] = 9 | ||
} | } | ||
World.months4.en = { [ 6 ] = true, | |||
[ 7 ] = true } | |||
World.templates = { [ "ISO" ] = | World.templates = { [ "ISO" ] = | ||
{ spec = "Y-m-d", | { spec = "Y-m-d", | ||
Zeile 49: | Zeile 55: | ||
{ spec = "c" }, | { spec = "c" }, | ||
[ "timestamp" ] = | [ "timestamp" ] = | ||
{ spec = "YmdHis" } | { 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.templates.en = { } | ||
Zeile 84: | Zeile 107: | ||
HST = -1000 -- Hawaiian Standard Time | HST = -1000 -- Hawaiian Standard Time | ||
} | } | ||
local foreignModule = function ( access, advanced, append, alt, alert ) | |||
-- Fetch global module | |||
-- Precondition: | |||
-- access -- string, with name of base module | |||
-- advanced -- true, for require(); else mw.loadData() | |||
-- append -- string, with subpage part, if any; or false | |||
-- alt -- number, of wikidata item of root; or false | |||
-- alert -- true, for throwing error on data problem | |||
-- Postcondition: | |||
-- Returns whatever, probably table | |||
-- 2019-10-20 | |||
local storage = access | |||
local fun, lucky, r | |||
if advanced then | |||
fun = require | |||
else | |||
fun = mw.loadData | |||
end | |||
if append then | |||
storage = string.format( "%s/%s", storage, append ) | |||
end | |||
lucky, r = pcall( fun, "Module:" .. storage ) | |||
if not lucky then | |||
local suited | |||
GlobalMod.globalModules = GlobalMod.globalModules or { } | |||
suited = GlobalMod.globalModules[ access ] | |||
if not suited and | |||
type( alt ) == "number" and | |||
alt > 0 then | |||
suited = string.format( "Q%d", alt ) | |||
suited = mw.wikibase.getSitelink( suited ) | |||
GlobalMod.globalModules[ access ] = suited or true | |||
end | |||
if type( suited ) == "string" then | |||
storage = suited | |||
if append then | |||
storage = string.format( "%s/%s", storage, append ) | |||
end | |||
lucky, r = pcall( fun, storage ) | |||
end | |||
if not lucky and alert then | |||
error( "Missing or invalid page: " .. storage, 0 ) | |||
end | |||
end | |||
return r | |||
end -- foreignModule() | |||
Zeile 95: | Zeile 167: | ||
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 105: | Zeile 177: | ||
-- Returns: | -- Returns: | ||
-- string, HTML span | -- string, HTML span | ||
local e = mw.html.create( "span" ) | |||
:addClass( "error" ) | |||
:wikitext( a ) | |||
return tostring( e ) | |||
end -- fault() | end -- fault() | ||
DateTime = function ( assign, alien ) | local function frame() | ||
-- Create | if not Frame then | ||
Frame = mw.getCurrentFrame() | |||
end | |||
return Frame | |||
end -- frame() | |||
Meta.localized = false | |||
Meta.serial = DateTime.serial | |||
Meta.signature = "__datetime" | |||
Meta.suite = "{DateTime}" | |||
Meta.components = { lang = "string", | |||
bc = "boolean", | |||
year = "number", | |||
month = "number", | |||
week = "number", | |||
dom = "number", | |||
hour = "number", | |||
min = "number", | |||
sec = "number", | |||
msec = "number", | |||
mysec = "number", | |||
zone = false, | |||
leap = "boolean", | |||
jul = "boolean" } | |||
Meta.order = { "bc", "year", "month", "week", "dom", | |||
"hour", "min", "sec", "msec", "mysec" } | |||
Meta.tableI = { -- instance metatable | |||
__index = function ( self, access ) | |||
local r = self[ Meta.signature ][ access ] | |||
if r == nil then | |||
if access == "serial" then | |||
r = Meta.serial | |||
elseif access == "suite" then | |||
r = "DateTime" | |||
else | |||
r = Prototypes[ access ] | |||
end | |||
end | |||
return r | |||
end, | |||
__newindex = function ( self, access, assign ) | |||
if type( access ) == "string" then | |||
local data = self[ Meta.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, true ) | |||
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, | |||
__tostring = function ( e ) | |||
return Prototypes.tostring( e ) | |||
end, | |||
__call = function ( func, ... ) | |||
return Meta.fiat( ... ) | |||
end | |||
} -- Meta.tableI | |||
Meta.tableL = { -- library metatable | |||
__index = function ( self, access ) | |||
local r | |||
if access == "serial" then | |||
r = Meta.serial | |||
elseif access == "suite" then | |||
r = Meta.suite | |||
end | |||
return r | |||
end, | |||
__newindex = function () | |||
return | |||
end, | |||
__tostring = function () | |||
return Meta.suite | |||
end, | |||
__call = function ( func, ... ) | |||
return Meta.fiat( ... ) | |||
end | |||
} -- Meta.tableL | |||
Meta.fiat = function ( assign, alien, add ) | |||
-- Create instance object (constructor) | |||
-- Parameter: | -- Parameter: | ||
-- assign -- string, with initial timestamp, or nil | -- assign -- string, with initial timestamp, or nil | ||
-- nil -- now | -- nil -- now | ||
-- false -- empty object | -- false -- empty object | ||
-- table -- clone this object, or copy from raw | |||
-- ignore remaining parameters | |||
-- 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 122: | Zeile 299: | ||
local r | local r | ||
Private.foreign() | Private.foreign() | ||
r = Private.factory( assign, alien ) | if type( assign ) == "table" then | ||
if assign.suite == Meta.suite and | |||
getmetatable( assign ) == Meta.tableI then | |||
r = assign[ Meta.signature ] | |||
else | |||
r = Private.from( assign ) | |||
end | |||
else | |||
r = Private.factory( assign, alien, add ) | |||
end | |||
if type( r ) == "table" then | if type( r ) == "table" then | ||
r = { [ Meta.signature ] = r } | |||
setmetatable( r, Meta.tableI ) | |||
setmetatable( r, | |||
end | end | ||
return r | return r | ||
end -- DateTime() | end -- Meta.fiat() | ||
setmetatable( DateTime, Meta.tableL ) | |||
DateTime.serial = nil | |||
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 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, s | |||
for i = 10, 2, -1 do | |||
s = Meta.order[ i ] | |||
n = adjust[ s ] | |||
if n or move then | |||
range = ranges[ s ] | |||
if range then | |||
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 | |||
adjust.month >= 1 and | |||
adjust.month <= 12 and | |||
adjust.year > 1900 then | |||
if n > 0 then | |||
max = Calc.final( adjust ) | |||
while n > max do | |||
n = n - max | |||
if adjust.month < 12 then | |||
adjust.month = adjust.month + 1 | |||
else | |||
adjust.month = 1 | |||
adjust.year = adjust.year + 1 | |||
end | |||
max = Calc.final( adjust ) | |||
end -- while n <= max | |||
else | |||
while n < 1 do | |||
if adjust.month == 1 then | |||
adjust.month = 12 | |||
adjust.year = adjust.year - 1 | |||
else | |||
adjust.month = adjust.month - 1 | |||
end | |||
max = Calc.final( adjust ) | |||
n = n + max | |||
end -- while n < 1 | |||
end | |||
end | |||
end | |||
end | |||
adjust[ s ] = n | |||
end | |||
end | |||
end -- for i | |||
end -- Calc.fair() | |||
Calc.final = function ( adjust ) | |||
-- Retrieve number of days in particular month | |||
-- Parameter: | |||
-- adjust -- table, with date specification | |||
-- Returns: | |||
-- number, of days in month | |||
local r = Calc.months[ adjust.month ] | |||
if adjust.month == 2 and | |||
( adjust.year % 4 ~= 0 or | |||
adjust.year % 400 == 0 ) then | |||
r = 28 | |||
end | |||
return r | |||
end -- Calc.final() | |||
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() | |||
Zeile 176: | Zeile 526: | ||
if amount <= 4 then | if amount <= 4 then | ||
r.year = tonumber( analyse ) | r.year = tonumber( analyse ) | ||
elseif | elseif amount == 14 then | ||
-- timestamp | -- timestamp | ||
r.year = tonumber( analyse:sub( 1, 4 ) ) | r.year = tonumber( analyse:sub( 1, 4 ) ) | ||
r.month = tonumber( analyse:sub( 5, | r.month = tonumber( analyse:sub( 5, 6 ) ) | ||
r.dom = tonumber( analyse:sub( 7, | r.dom = tonumber( analyse:sub( 7, 8 ) ) | ||
r.hour = tonumber( analyse:sub( 9, | r.hour = tonumber( analyse:sub( 9, 10 ) ) | ||
r.min = tonumber( analyse:sub( 11, | r.min = tonumber( analyse:sub( 11, 12 ) ) | ||
r.sec = tonumber( analyse:sub( 13, | r.sec = tonumber( analyse:sub( 13, 14 ) ) | ||
else | else | ||
r = false | r = false | ||
end | end | ||
elseif amount == 4 then | elseif amount == 4 then | ||
local s, sep, sx = analyse:match( "^(%d+)([%-%.: | local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" ) | ||
r.year = tonumber( s ) | r.year = tonumber( s ) | ||
if sep == "-" then | if sep == "-" then | ||
Zeile 218: | Zeile 568: | ||
end | end | ||
elseif sep:lower() == "w" then | elseif sep:lower() == "w" then | ||
if | if sx then | ||
s = | s = sx:match( "^(%d%d?)$" ) | ||
if s then | if s then | ||
r.week = tonumber( s ) | r.week = tonumber( s ) | ||
if r.week < 1 or r.week > 53 then | |||
r = false | |||
end | |||
else | else | ||
r = false | r = false | ||
Zeile 230: | Zeile 583: | ||
else | else | ||
r = false | r = false | ||
end | |||
if r then | |||
r.iso = true | |||
end | end | ||
elseif amount == 8 then | elseif amount == 8 then | ||
Zeile 250: | Zeile 606: | ||
n = n .. "00" | n = n .. "00" | ||
r.msec = tonumber( n:sub( 1, 3 ) ) | r.msec = tonumber( n:sub( 1, 3 ) ) | ||
sz | if #n >= 6 then | ||
r.mysec = tonumber( n:sub( 4, 6 ) ) | |||
end | |||
sz = s | |||
end | end | ||
end | end | ||
Zeile 347: | Zeile 706: | ||
-- 23 Dec 2013 | -- 23 Dec 2013 | ||
s, sx = mw.ustring.match( analyse, | s, sx = mw.ustring.match( analyse, | ||
"^([%a&;]+%.?) (.*)$" ) | "^([%a&;]+%.?) ?(.*)$" ) | ||
if s then | if s then | ||
local n = Parser.monthNumber( s ) | local n = Parser.monthNumber( s ) | ||
Zeile 366: | Zeile 725: | ||
return r | return r | ||
end -- Parser.european() | 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() | |||
Zeile 400: | Zeile 828: | ||
n = #s2 | n = #s2 | ||
if n <= 2 and #s3 == 4 then | if n <= 2 and #s3 == 4 then | ||
rO.dom = tonumber( | rO.dom = tonumber( s2 ) | ||
rO.year = tonumber( s3 ) | rO.year = tonumber( s3 ) | ||
rO.dom2 = ( n == 2 ) | rO.dom2 = ( n == 2 ) | ||
Zeile 511: | Zeile 939: | ||
if #s == 2 then | if #s == 2 then | ||
r.min = tonumber( s ) | r.min = tonumber( s ) | ||
if sx == "" then | if sx == "" then | ||
sx = false | sx = false | ||
end | end | ||
Zeile 540: | Zeile 968: | ||
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 623: | Zeile 1.055: | ||
if s then | if s then | ||
sx = analyse:sub( 5 ) | 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 | ||
end | end | ||
Zeile 695: | Zeile 1.139: | ||
if rO.zone then | if rO.zone then | ||
rS = s | rS = s | ||
end | end | ||
end | end | ||
Zeile 735: | Zeile 1.177: | ||
end | end | ||
else | else | ||
r, | local rM, sM = Parser.monthHeading( s, r ) | ||
if r and | if rM then | ||
r = Parser.time( | r = rM | ||
else | |||
r, sM = Parser.isoDate( s, r ) | |||
end | |||
if r and sM ~= "" then | |||
r = Parser.time( sM, r ) | |||
end | end | ||
end | end | ||
Zeile 746: | Zeile 1.193: | ||
Private.factory = function ( assign, alien ) | Private.factory = function ( assign, alien, add ) | ||
-- Create DateTime table (constructor) | -- Create DateTime table (constructor) | ||
-- Parameter: | -- Parameter: | ||
Zeile 753: | Zeile 1.200: | ||
-- 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 | |||
local seconds = stamp:match( "^#(%d+)$" ) | |||
if seconds then | |||
stamp = os.date( "!%Y-%m-%dT%H:%M:%S", | |||
tonumber( seconds ) ) | |||
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 779: | Zeile 1.238: | ||
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 -- table, with interval, or nil | |||
-- Returns: | -- Returns: | ||
-- table, if parsed | -- table, if parsed | ||
Zeile 790: | Zeile 1.250: | ||
local r | local r | ||
if type( analyse ) == "string" then | if type( analyse ) == "string" then | ||
local strip = mw.ustring.char( 0x5B, 0x200E, 0x200F, 0x5D ) | |||
r = analyse:gsub( " ", " " ) | r = analyse:gsub( " ", " " ) | ||
:gsub( " ", " " ) | :gsub( " ", " " ) | ||
:gsub( "&#x[aA]0;", " " ) | |||
:gsub( " ", " " ) | :gsub( " ", " " ) | ||
:gsub( Nbsp, " " ) | :gsub( Nbsp, " " ) | ||
:gsub( Tab, " " ) | :gsub( Tab, " " ) | ||
:gsub( " +", " " ) | :gsub( " +", " " ) | ||
:gsub( "%[%[", "" ) | |||
:gsub( "%]%]", "" ) | |||
:gsub( strip, "" ) | |||
r = mw.text.trim( r ) | r = mw.text.trim( r ) | ||
if r == "" then | if r == "" then | ||
r = { } | r = { } | ||
else | else | ||
local slang = ( alien or "" ) | local slang = ( alien or "" ) | ||
local parser = { en = "GermanEnglish", | |||
de = "GermanEnglish", | |||
frr = "GermanEnglish", | |||
nds = "GermanEnglish" } | |||
local suitable | |||
if slang == "" then | if slang == "" then | ||
slang = "en" | slang = "en" | ||
Zeile 809: | Zeile 1.279: | ||
end | end | ||
end | end | ||
slang = slang:lower() | slang = slang:lower() | ||
suitable = parser[ slang ] | |||
if suitable then | |||
local l | local l | ||
l, r = pcall( Parser | l, r = pcall( Parser[ suitable ], r ) | ||
if l and r then | if l and r then | ||
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 | ||
else | |||
r = "invalid format" | |||
end | end | ||
else | else | ||
r = "unknown language" | r = "unknown language: " .. slang | ||
end | end | ||
end | end | ||
Zeile 830: | Zeile 1.305: | ||
Private.foreign = | 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 life = false | |||
local s, v1, v2 | |||
for i = 2, 10 do | |||
s = Meta.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 | -- Retrieve localization submodule | ||
if not | if not Meta.localized then | ||
local | local d = foreignModule( DateTime.suite, | ||
if | false, | ||
"local", | |||
DateTime.item ) | |||
if type( d ) == "table" then | |||
local wk | local wk | ||
if d.slang then | if d.slang then | ||
Meta.suite = string.format( "%s %s", | |||
Meta.suite, d.slang ) | |||
World.slang = d.slang | World.slang = d.slang | ||
end | end | ||
Zeile 850: | Zeile 1.382: | ||
end -- for k, v | end -- for k, v | ||
end | end | ||
Meta.localized = true | |||
end | end | ||
end -- Private.foreign() | end -- Private.foreign() | ||
Private.from = function ( attempt ) | |||
-- Create valid raw table from arbitrary table | |||
-- Parameter: | |||
-- attempt -- table, to be evaluated | |||
-- Returns: | |||
-- table, with valid components, or nil | |||
local data = { } | |||
local r | |||
for k, v in pairs( Meta.components ) do | |||
if v then | |||
v = ( type( attempt[ k ] ) == v ) | |||
else | |||
v = true | |||
end | |||
if v then | |||
data[ k ] = attempt[ k ] | |||
end | |||
end -- for k, v | |||
if Prototypes.fair( data ) then | |||
r = data | |||
end | |||
return r | |||
end -- Private.from() | |||
Private.future = function ( add ) | |||
-- Normalize move interval | |||
-- Parameter: | |||
-- add -- string or number, to be added | |||
-- Returns: | |||
-- table, 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" | |||
end | |||
if s == "number" then | |||
if r == 0 then | |||
r = false | |||
else | |||
r = string.format( "%d second", r or add ) | |||
end | |||
elseif s == "string" then | |||
r = add | |||
else | |||
r = false | |||
end | |||
if r then | |||
r = Calc.future( r ) | |||
end | |||
end | |||
return r | |||
end -- Private.future() | |||
Prototypes.clone = function ( self ) | |||
-- Clone object | |||
-- Parameter: | |||
-- self -- table, with object, to be cloned | |||
-- Returns: | |||
-- table, with object | |||
local r = { [ Meta.signature ] = self[ Meta.signature ] } | |||
setmetatable( r, Meta.tableI ) | |||
return r | |||
end -- Prototypes.clone() | |||
Prototypes.failsafe = function ( self, atleast ) | |||
-- Retrieve versioning and check for compliance | |||
-- Precondition: | |||
-- atleast -- string, with required version or "wikidata" or "~" | |||
-- or false | |||
-- Postcondition: | |||
-- Returns string -- with queried version, also if problem | |||
-- false -- if appropriate | |||
local last = ( atleast == "~" ) | |||
local since = atleast | |||
local r | |||
if last or since == "wikidata" then | |||
local item = Meta.item | |||
since = false | |||
if type( item ) == "number" and item > 0 then | |||
local entity = mw.wikibase.getEntity( string.format( "Q%d", | |||
item ) ) | |||
if type( entity ) == "table" then | |||
local seek = Failsafe.serialProperty or "P348" | |||
local vsn = entity:formatPropertyValues( seek ) | |||
if type( vsn ) == "table" and | |||
type( vsn.value ) == "string" and | |||
vsn.value ~= "" then | |||
if last and | |||
vsn.value == ( Meta.serial or DateTime.serial ) then | |||
r = false | |||
else | |||
r = vsn.value | |||
end | |||
end | |||
end | |||
end | |||
end | |||
if type( r ) == "nil" then | |||
if not since or since <= Meta.serial then | |||
r = Meta.serial | |||
else | |||
r = false | |||
end | |||
end | |||
return r | |||
end -- Prototypes.failsafe() | |||
Zeile 859: | Zeile 1.508: | ||
-- Check formal validity of table | -- Check formal validity of table | ||
-- Parameter: | -- Parameter: | ||
-- self -- table to be checked | -- self -- table, to be checked | ||
-- access -- string or nil, single item to be checked | -- access -- string or nil, single item to be checked | ||
-- assign -- single access value to be checked | -- assign -- single access value to be checked | ||
Zeile 866: | Zeile 1.515: | ||
local r = ( type( self ) == "table" ) | local r = ( type( self ) == "table" ) | ||
if r then | if r then | ||
local defs = { year = { max = | local defs = { year = { max = MaxYear }, | ||
month = { max = 12 }, | month = { min = 1, | ||
dom = { max = 31 }, | max = 12 }, | ||
week = { min = 1, | |||
max = 53 }, | |||
dom = { min = 1, | |||
max = 31 }, | |||
hour = { max = 23 }, | hour = { max = 23 }, | ||
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 880: | Zeile 1.533: | ||
local dk = defs[ k ] | local dk = defs[ k ] | ||
if dk then | if dk then | ||
if type( dk.max ) == "number" then | |||
ret = ( type( v ) == "number" ) | ret = ( type( v ) == "number" ) | ||
if ret then | if ret then | ||
ret = ( v >= | 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 ) | and math.floor( v ) == v ) | ||
if ret and dk.f then | if ret and dk.f then | ||
Zeile 913: | Zeile 1.571: | ||
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 922: | Zeile 1.580: | ||
end | end | ||
if d == 29 and m == 2 and y then | if d == 29 and m == 2 and y then | ||
if y % 4 ~= 0 | if y % 4 ~= 0 or | ||
( y % 100 == 0 and | |||
y % 400 ~= 0 ) then | |||
ret = false | ret = false | ||
end | end | ||
Zeile 964: | Zeile 1.624: | ||
end | end | ||
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 | ||
end | end | ||
else | else | ||
local life = false | |||
local leak = false | |||
local life = false | |||
local leak = false | |||
local s, v | local s, v | ||
for i = 1, | for i = 1, 10 do | ||
s = order[ i ] | s = Meta.order[ i ] | ||
v = self[ s ] | v = self[ s ] | ||
if v then | if v then | ||
Zeile 983: | Zeile 1.648: | ||
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 999: | Zeile 1.671: | ||
Prototypes.format = function ( self, ask, | 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.fix = function ( self ) | |||
-- Adapt this object to local time if no explicit zone given | |||
-- Parameter: | |||
-- self -- table, with numbers etc. | |||
if type( self ) == "table" and | |||
not self.zone then | |||
local seconds = Prototypes.format( self, "Z" ) | |||
Prototypes.future( self, - tonumber( seconds ) ) | |||
end | |||
end -- Prototypes.fix() | |||
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 base, other, r | |||
if type( self ) == "table" then | |||
base = self | |||
other = another | |||
elseif type( another ) == "table" then | |||
base = another | |||
other = self | |||
end | |||
if base then | |||
if type( other ) ~= "table" then | |||
other = Meta.fiat( other ) | |||
end | |||
if type( other ) == "table" then | |||
r = Private.flow( base, other ) | |||
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 | -- Format object as string | ||
-- Parameter: | -- Parameter: | ||
-- self -- table, with numbers etc. | -- self -- table, with numbers etc. | ||
-- ask -- string, format spec, or nil | -- 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: | -- Returns: | ||
-- string, or false, if invalid | -- string, or false, if invalid | ||
local r = false | local r = false | ||
if type( self ) == "table" then | if type( self ) == "table" then | ||
local slang = ( | local slang = self.lang or "en" | ||
local opts = { lang = slang } | |||
local babel | |||
if type( adapt ) == "table" then | |||
if type( adapt.lang ) == "string" then | |||
local i = adapt.lang:find( "-", 3, true ) | |||
if i then | |||
slang = adapt.lang:lower() | |||
opts.lang = slang:sub( 1, i - 1 ) | |||
else | |||
opts.lang = adapt.lang:lower() | |||
end | |||
end | |||
opts.london = adapt.london | |||
opts.lonely = adapt.lonely | |||
end | |||
babel = mw.language.new( opts.lang:lower() ) | |||
if babel then | if babel then | ||
local show | local shift, show, stamp, suffix, limit4, locally | ||
if self.month then | if self.month then | ||
stamp = World.monthsLong.en[ self.month ] | stamp = World.monthsLong.en[ self.month ] | ||
Zeile 1.023: | Zeile 1.838: | ||
if self.dom then | if self.dom then | ||
stamp = string.format( "%d %s", self.dom, stamp ) | stamp = string.format( "%d %s", self.dom, stamp ) | ||
end | |||
if ask and ask:find( "Mon4", 1, true ) then | |||
local mon4 = World.months4[ opts.lang:lower() ] | |||
if mon4 and mon4[ self.month ] then | |||
limit4 = true | |||
end | |||
end | end | ||
elseif self.year then | elseif self.year then | ||
Zeile 1.028: | Zeile 1.849: | ||
end | end | ||
if self.hour then | if self.hour then | ||
stamp = string.format( "%s %02d:", stamp, self.hour ) | if stamp then | ||
stamp = stamp .. " " | |||
else | |||
stamp = "" | |||
end | |||
stamp = string.format( "%s%02d:", stamp, self.hour ) | |||
if self.min then | if self.min then | ||
stamp = string.format( "%s%02d", stamp, self.min ) | stamp = string.format( "%s%02d", stamp, self.min ) | ||
Zeile 1.035: | Zeile 1.861: | ||
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.046: | Zeile 1.877: | ||
end | end | ||
end | end | ||
show, suffix = World.templates.formatter( self, ask, | show, suffix = World.templates.formatter( self, ask, opts ) | ||
if | if limit4 then | ||
show = show:gsub( "M", "F" ) | |||
end | |||
if type( opts.london ) == "boolean" then | |||
locally = not opts.london | |||
else | |||
locally = true | locally = true | ||
end | end | ||
r = babel:formatDate( show, stamp, locally ) | r = babel:formatDate( show, stamp, locally ) | ||
r = r:gsub( " $", "" ) | |||
if self.year and self.year < 1000 then | if self.year and self.year < 1000 then | ||
r = r:gsub( string.format( "%04d", self.year ), | r = r:gsub( string.format( "%04d", self.year ), | ||
tostring( self.year ) ) | tostring( self.year ) ) | ||
end | |||
if self.month then | |||
local bucket, m, suite, x | |||
if show:find( "F", 1, true ) then | |||
suite = "monthsLong" | |||
elseif show:find( "M", 1, true ) then | |||
suite = "monthsAbbr" | |||
end | |||
bucket = World[ suite ] | |||
if bucket then | |||
m = bucket[ opts.lang:lower() ] | |||
if slang then | |||
x = bucket[ slang:lower() ] | |||
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 | end | ||
if suffix then | if suffix then | ||
Zeile 1.067: | Zeile 1.945: | ||
World.templates.formatter = | 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, allocate ) | |||
-- Relative move by interval | |||
-- Parameter: | |||
-- self -- table, to be used as base | |||
-- add -- string or number, to be added | |||
-- allocate -- true, if a clone shall be returned | |||
-- 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 | |||
if r[ Meta.signature ] then | |||
raw = r[ Meta.signature ] | |||
else | |||
raw = r | |||
end | |||
if type( shift ) == "table" then | |||
rel = shift | |||
else | |||
rel = Private.future( shift ) | |||
end | |||
end | |||
if raw and rel then | |||
if allocate then | |||
r = Prototypes.clone( r ) | |||
raw = r[ Meta.signature ] | |||
end | |||
for k, v in pairs( rel ) do | |||
raw[ k ] = ( raw[ k ] or 0 ) + v | |||
end -- for k, v | |||
Calc.fair( raw ) | |||
r[ Meta.signature ] = raw | |||
end | |||
return r | |||
end -- Prototypes.future() | |||
Prototypes.tostring = function ( self ) | |||
-- Stringify yourself | |||
-- Parameter: | |||
-- self -- table, to be stringified | |||
-- Returns: | |||
-- string | |||
local dels = { false, "", "-", "-", "", "", ":", ":", ".", "" } | |||
local wids = { false, 4, 2, 2, 2, 2, 2, 2, 3, 3 } | |||
local s = "" | |||
local n, r, spec | |||
local f = function ( a ) | |||
n = self[ Meta.order[ a ] ] | |||
s = s .. dels[ a ] | |||
if n then | |||
spec = string.format( "%%s%%0%dd", wids[ a ] ) | |||
s = string.format( spec, s, n ) | |||
end | |||
end -- f() | |||
for i = 2, 5 do | |||
f( i ) | |||
end -- for i | |||
r = s | |||
s = "" | |||
for i = 6, 10 do | |||
f( i ) | |||
end -- for i | |||
if s == "::." then | |||
r = r:gsub( "%-+$", "" ) | |||
else | |||
if r == "--" then | |||
r = s | |||
else | |||
r = string.format( "%sT%s", r, s ) | |||
end | |||
end | |||
r = r:gsub( "%.$", "" ) | |||
return r | |||
end -- Prototypes.tostring() | |||
Prototypes.valueOf = function ( self ) | |||
-- Returns yourselves primitive value (primitive table) | |||
-- Parameter: | |||
-- self -- table, to be dumped | |||
-- Returns: | |||
-- table, or false | |||
local r | |||
if type( self ) == "table" then | |||
r = self[ Meta.signature ] | |||
end | |||
return r or false | |||
end -- Prototypes.valueOf() | |||
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, | |||
Meta.fiat( 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 | -- Retrieve format specification string | ||
-- Parameter: | -- Parameter: | ||
-- assigned -- table, with numbers etc. | -- assigned -- table, with numbers etc. | ||
-- ask -- string, format spec, or nil | -- ask -- string, format spec, or nil | ||
-- | -- adapt -- table, with options | ||
-- .lang -- string, with particular language code | |||
-- .lonely -- true: permit lonely hour | |||
-- Returns: | -- Returns: | ||
-- 1 -- string | -- 1 -- string | ||
Zeile 1.079: | Zeile 2.115: | ||
if not ask or ask == "" then | if not ask or ask == "" then | ||
r1 = "c" | r1 = "c" | ||
elseif ask == "*" then | |||
if World.present then | |||
if assigned.hour then | |||
if assigned.dom or assigned.month or assigned.year then | |||
if World.present.both and | |||
World.present.date and | |||
World.present.time then | |||
r1 = World.present.both | |||
:gsub( "$date", World.present.date ) | |||
:gsub( "$time", World.present.time ) | |||
else | |||
r1 = World.present.date | |||
end | |||
end | |||
r1 = r1 or World.present.time | |||
else | |||
r1 = World.present.date | |||
end | |||
end | |||
r1 = r1 or "c" | |||
else | else | ||
local template = World.templates[ ask ] | local template = World.templates[ ask ] | ||
r1 = ask | r1 = ask | ||
if not template then | if not template then | ||
local slang = ( | local slang = ( adapt.lang or assigned.lang or World.slang ) | ||
local tmp = World.templates[ slang ] | local tmp = World.templates[ slang ] | ||
if tmp then | if tmp then | ||
template = tmp[ ask ] | template = tmp[ ask ] | ||
end | |||
if not template then | |||
local i = slang:find( "-", 3, true ) | |||
if i then | |||
slang = slang:sub( 1, i - 1 ):lower() | |||
tmp = World.templates[ slang ] | |||
if tmp then | |||
template = tmp[ ask ] | |||
end | |||
end | |||
end | end | ||
end | end | ||
if type( template ) == "table" then | if type( template ) == "table" then | ||
local low = ( ask == "ISO" or ask == "ISO-T" ) | |||
r1 = template.spec | r1 = template.spec | ||
if assigned.year then | if assigned.year then | ||
if not assigned.dom then | if not assigned.dom then | ||
r1 = r1:gsub( "[ .]?[ | r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" ) | ||
:gsub( "^ ", "" ) | :gsub( "^ ", "" ) | ||
if not assigned.month then | if not assigned.month then | ||
r1 = r1:gsub( "[ .%-]?[ | r1 = r1:gsub( "[ .%-]?[FmMnt][ .%-]*", "" ) | ||
end | end | ||
end | end | ||
Zeile 1.102: | Zeile 2.169: | ||
r1 = r1:gsub( " ?[yY] ?", "" ) | r1 = r1:gsub( " ?[yY] ?", "" ) | ||
if not assigned.dom then | if not assigned.dom then | ||
r1 = r1:gsub( "[ .]?[ | r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" ) | ||
:gsub( "^ ", "" ) | :gsub( "^ ", "" ) | ||
end | end | ||
end | end | ||
if template.lift | if template.lift and | ||
( assigned.dom or | |||
not ( assigned.month or assigned.year or assigned.bc ) | |||
) then | |||
local stamp = false | local stamp = false | ||
if assigned.hour then | |||
if | 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 | end | ||
elseif adapt.lonely then | |||
stamp = "H" | |||
end | end | ||
end | end | ||
if low or | if low or ask:find( "hh:mm:ss", 1, true ) then | ||
if stamp then | if stamp then | ||
r1 = string.format( "%s %s", r1, stamp ) | r1 = string.format( "%s %s", r1, stamp ) | ||
Zeile 1.129: | Zeile 2.204: | ||
end | end | ||
if stamp then | if stamp then | ||
if low or template.long then | |||
if low or | |||
local scheme | local scheme | ||
if | if template.long then | ||
scheme = | scheme = mw.language.getContentLanguage() | ||
scheme = scheme.code | |||
end | end | ||
r2 = World.zones.formatter( assigned, scheme ) | r2 = World.zones.formatter( assigned, scheme ) | ||
Zeile 1.140: | Zeile 2.215: | ||
end | end | ||
if type ( assigned.bc ) == "boolean" then | if type ( assigned.bc ) == "boolean" then | ||
local eras = World.era[ | local eras = World.era[ adapt.lang ] or World.era.en | ||
local i | local i | ||
if not r2 then | if not r2 then | ||
Zeile 1.179: | Zeile 2.254: | ||
if #s == 1 then | if #s == 1 then | ||
-- "YXWVUTSRQPONZABCDEFGHIKLM" | -- "YXWVUTSRQPONZABCDEFGHIKLM" | ||
move = World.zones[ "!" ]:find( s ) | move = World.zones[ "!" ]:find( s, 1, true ) | ||
if move then | if move then | ||
move = ( move - 13 ) * 100 | move = ( move - 13 ) * 100 | ||
Zeile 1.194: | Zeile 2.269: | ||
if tmp then | if tmp then | ||
code = tmp[ s ] | code = tmp[ s ] | ||
end | |||
if not code and | |||
slang ~= "en" and | |||
World.zones.en then | |||
code = World.zones.en[ s ] | |||
end | end | ||
end | end | ||
Zeile 1.250: | Zeile 2.330: | ||
local p = { } | local p = { } | ||
function p.test( args ) | function p.test( args, alien ) | ||
local slang = args.lang or alien | |||
local obj = Meta.fiat( args[ 1 ], false, args.shift ) | |||
local r | local r | ||
if type( obj ) == "table" then | |||
if type( | |||
local spec = args[ 2 ] | local spec = args[ 2 ] | ||
local | 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 ) } | |||
end | end | ||
r = | 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 | r = fault( "Format invalid" ) | ||
else | else | ||
r = "" | r = "" | ||
Zeile 1.273: | Zeile 2.354: | ||
return r | return r | ||
end -- p.test | end -- p.test | ||
function p.failsafe( frame ) | |||
local s = type( frame ) | |||
local r, since | |||
if s == "table" then | |||
since = frame.args[ 1 ] | |||
elseif s == "string" then | |||
since = mw.text.trim( since ) | |||
if since == "" then | |||
since = false | |||
end | |||
end | |||
return Prototypes.failsafe( false, since ) or "" | |||
end -- p.failsafe | |||
function p.format( frame ) | function p.format( frame ) | ||
-- 1 -- stamp | |||
-- 2 -- spec | |||
-- lang | |||
-- shift | |||
-- noerror | |||
local l, r | local l, r | ||
local v = { frame.args[ 1 ], | local v = { frame.args[ 1 ], | ||
frame.args[ 2 ], | frame.args[ 2 ], | ||
frame.args | shift = frame.args.shift, | ||
noerror = frame.args.noerror } | noerror = frame.args.noerror } | ||
if not v[ 1 ] or v[ 1 ] == "now" then | if not v[ 1 ] or v[ 1 ] == "now" then | ||
v[ 1 ] = frame:callParserFunction( "#timel", "c" ) | v[ 1 ] = frame:callParserFunction( "#timel", "c", v.shift ) | ||
v.shift = false | |||
end | end | ||
l, r = pcall( p.test, v ) | Frame = frame | ||
l, r = pcall( p.test, v, frame.args[ 3 ] or frame.args.lang ) | |||
if not l then | if not l then | ||
r = fault( r ) | r = fault( r ) | ||
Zeile 1.294: | Zeile 2.398: | ||
p. | function p.lt( frame ) | ||
return | 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 | end -- p.DateTime | ||
return p | return p |
Aktuelle Version vom 3. Juli 2020, 20:18 Uhr
local DateTime = { serial = "2020-04-08",
suite = "DateTime",
item = 20652535 }
-- Date and time objects
local Failsafe = DateTime
local GlobalMod = DateTime
local Calc = { }
local Meta = { }
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 foreignModule = function ( access, advanced, append, alt, alert )
-- Fetch global module
-- Precondition:
-- access -- string, with name of base module
-- advanced -- true, for require(); else mw.loadData()
-- append -- string, with subpage part, if any; or false
-- alt -- number, of wikidata item of root; or false
-- alert -- true, for throwing error on data problem
-- Postcondition:
-- Returns whatever, probably table
-- 2019-10-20
local storage = access
local fun, lucky, r
if advanced then
fun = require
else
fun = mw.loadData
end
if append then
storage = string.format( "%s/%s", storage, append )
end
lucky, r = pcall( fun, "Module:" .. storage )
if not lucky then
local suited
GlobalMod.globalModules = GlobalMod.globalModules or { }
suited = GlobalMod.globalModules[ access ]
if not suited and
type( alt ) == "number" and
alt > 0 then
suited = string.format( "Q%d", alt )
suited = mw.wikibase.getSitelink( suited )
GlobalMod.globalModules[ access ] = suited or true
end
if type( suited ) == "string" then
storage = suited
if append then
storage = string.format( "%s/%s", storage, append )
end
lucky, r = pcall( fun, storage )
end
if not lucky and alert then
error( "Missing or invalid page: " .. storage, 0 )
end
end
return r
end -- foreignModule()
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
local e = mw.html.create( "span" )
:addClass( "error" )
:wikitext( a )
return tostring( e )
end -- fault()
local function frame()
if not Frame then
Frame = mw.getCurrentFrame()
end
return Frame
end -- frame()
Meta.localized = false
Meta.serial = DateTime.serial
Meta.signature = "__datetime"
Meta.suite = "{DateTime}"
Meta.components = { lang = "string",
bc = "boolean",
year = "number",
month = "number",
week = "number",
dom = "number",
hour = "number",
min = "number",
sec = "number",
msec = "number",
mysec = "number",
zone = false,
leap = "boolean",
jul = "boolean" }
Meta.order = { "bc", "year", "month", "week", "dom",
"hour", "min", "sec", "msec", "mysec" }
Meta.tableI = { -- instance metatable
__index = function ( self, access )
local r = self[ Meta.signature ][ access ]
if r == nil then
if access == "serial" then
r = Meta.serial
elseif access == "suite" then
r = "DateTime"
else
r = Prototypes[ access ]
end
end
return r
end,
__newindex = function ( self, access, assign )
if type( access ) == "string" then
local data = self[ Meta.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, true )
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,
__tostring = function ( e )
return Prototypes.tostring( e )
end,
__call = function ( func, ... )
return Meta.fiat( ... )
end
} -- Meta.tableI
Meta.tableL = { -- library metatable
__index = function ( self, access )
local r
if access == "serial" then
r = Meta.serial
elseif access == "suite" then
r = Meta.suite
end
return r
end,
__newindex = function ()
return
end,
__tostring = function ()
return Meta.suite
end,
__call = function ( func, ... )
return Meta.fiat( ... )
end
} -- Meta.tableL
Meta.fiat = function ( assign, alien, add )
-- Create instance object (constructor)
-- Parameter:
-- assign -- string, with initial timestamp, or nil
-- nil -- now
-- false -- empty object
-- table -- clone this object, or copy from raw
-- ignore remaining parameters
-- 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()
if type( assign ) == "table" then
if assign.suite == Meta.suite and
getmetatable( assign ) == Meta.tableI then
r = assign[ Meta.signature ]
else
r = Private.from( assign )
end
else
r = Private.factory( assign, alien, add )
end
if type( r ) == "table" then
r = { [ Meta.signature ] = r }
setmetatable( r, Meta.tableI )
end
return r
end -- Meta.fiat()
setmetatable( DateTime, Meta.tableL )
DateTime.serial = nil
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 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, s
for i = 10, 2, -1 do
s = Meta.order[ i ]
n = adjust[ s ]
if n or move then
range = ranges[ s ]
if range then
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
adjust.month >= 1 and
adjust.month <= 12 and
adjust.year > 1900 then
if n > 0 then
max = Calc.final( adjust )
while n > max do
n = n - max
if adjust.month < 12 then
adjust.month = adjust.month + 1
else
adjust.month = 1
adjust.year = adjust.year + 1
end
max = Calc.final( adjust )
end -- while n <= max
else
while n < 1 do
if adjust.month == 1 then
adjust.month = 12
adjust.year = adjust.year - 1
else
adjust.month = adjust.month - 1
end
max = Calc.final( adjust )
n = n + max
end -- while n < 1
end
end
end
end
adjust[ s ] = n
end
end
end -- for i
end -- Calc.fair()
Calc.final = function ( adjust )
-- Retrieve number of days in particular month
-- Parameter:
-- adjust -- table, with date specification
-- Returns:
-- number, of days in month
local r = Calc.months[ adjust.month ]
if adjust.month == 2 and
( adjust.year % 4 ~= 0 or
adjust.year % 400 == 0 ) then
r = 28
end
return r
end -- Calc.final()
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 amount == 14 then
-- timestamp
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( 9, 10 ) )
r.min = tonumber( analyse:sub( 11, 12 ) )
r.sec = tonumber( analyse:sub( 13, 14 ) )
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( s2 )
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 -- table, with interval, or nil
-- Returns:
-- table, if parsed
-- false, if invalid text format
-- string, if serious error (args)
local r
if type( analyse ) == "string" then
local strip = mw.ustring.char( 0x5B, 0x200E, 0x200F, 0x5D )
r = analyse:gsub( " ", " " )
:gsub( " ", " " )
:gsub( "&#x[aA]0;", " " )
:gsub( " ", " " )
:gsub( Nbsp, " " )
:gsub( Tab, " " )
:gsub( " +", " " )
:gsub( "%[%[", "" )
:gsub( "%]%]", "" )
:gsub( strip, "" )
r = mw.text.trim( r )
if r == "" then
r = { }
else
local slang = ( alien or "" )
local parser = { en = "GermanEnglish",
de = "GermanEnglish",
frr = "GermanEnglish",
nds = "GermanEnglish" }
local suitable
if slang == "" then
slang = "en"
else
local s = slang:match( "^(%a+)%-" )
if s then
slang = s
end
end
slang = slang:lower()
suitable = parser[ slang ]
if suitable then
local l
l, r = pcall( Parser[ suitable ], r )
if l and r then
if not Prototypes.fair( r ) then
r = false
elseif add then
r = Prototypes.future( r, add )
end
else
r = "invalid format"
end
else
r = "unknown language: " .. slang
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 life = false
local s, v1, v2
for i = 2, 10 do
s = Meta.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 Meta.localized then
local d = foreignModule( DateTime.suite,
false,
"local",
DateTime.item )
if type( d ) == "table" then
local wk
if d.slang then
Meta.suite = string.format( "%s %s",
Meta.suite, d.slang )
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
Meta.localized = true
end
end -- Private.foreign()
Private.from = function ( attempt )
-- Create valid raw table from arbitrary table
-- Parameter:
-- attempt -- table, to be evaluated
-- Returns:
-- table, with valid components, or nil
local data = { }
local r
for k, v in pairs( Meta.components ) do
if v then
v = ( type( attempt[ k ] ) == v )
else
v = true
end
if v then
data[ k ] = attempt[ k ]
end
end -- for k, v
if Prototypes.fair( data ) then
r = data
end
return r
end -- Private.from()
Private.future = function ( add )
-- Normalize move interval
-- Parameter:
-- add -- string or number, to be added
-- Returns:
-- table, 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"
end
if s == "number" then
if r == 0 then
r = false
else
r = string.format( "%d second", r or add )
end
elseif s == "string" then
r = add
else
r = false
end
if r then
r = Calc.future( r )
end
end
return r
end -- Private.future()
Prototypes.clone = function ( self )
-- Clone object
-- Parameter:
-- self -- table, with object, to be cloned
-- Returns:
-- table, with object
local r = { [ Meta.signature ] = self[ Meta.signature ] }
setmetatable( r, Meta.tableI )
return r
end -- Prototypes.clone()
Prototypes.failsafe = function ( self, atleast )
-- Retrieve versioning and check for compliance
-- Precondition:
-- atleast -- string, with required version or "wikidata" or "~"
-- or false
-- Postcondition:
-- Returns string -- with queried version, also if problem
-- false -- if appropriate
local last = ( atleast == "~" )
local since = atleast
local r
if last or since == "wikidata" then
local item = Meta.item
since = false
if type( item ) == "number" and item > 0 then
local entity = mw.wikibase.getEntity( string.format( "Q%d",
item ) )
if type( entity ) == "table" then
local seek = Failsafe.serialProperty or "P348"
local vsn = entity:formatPropertyValues( seek )
if type( vsn ) == "table" and
type( vsn.value ) == "string" and
vsn.value ~= "" then
if last and
vsn.value == ( Meta.serial or DateTime.serial ) then
r = false
else
r = vsn.value
end
end
end
end
end
if type( r ) == "nil" then
if not since or since <= Meta.serial then
r = Meta.serial
else
r = false
end
end
return r
end -- Prototypes.failsafe()
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 % 100 == 0 and
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 life = false
local leak = false
local s, v
for i = 1, 10 do
s = Meta.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.fix = function ( self )
-- Adapt this object to local time if no explicit zone given
-- Parameter:
-- self -- table, with numbers etc.
if type( self ) == "table" and
not self.zone then
local seconds = Prototypes.format( self, "Z" )
Prototypes.future( self, - tonumber( seconds ) )
end
end -- Prototypes.fix()
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 base, other, r
if type( self ) == "table" then
base = self
other = another
elseif type( another ) == "table" then
base = another
other = self
end
if base then
if type( other ) ~= "table" then
other = Meta.fiat( other )
end
if type( other ) == "table" then
r = Private.flow( base, other )
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 slang = self.lang or "en"
local opts = { lang = slang }
local babel
if type( adapt ) == "table" then
if type( adapt.lang ) == "string" then
local i = adapt.lang:find( "-", 3, true )
if i then
slang = adapt.lang:lower()
opts.lang = slang:sub( 1, i - 1 )
else
opts.lang = adapt.lang:lower()
end
end
opts.london = adapt.london
opts.lonely = adapt.lonely
end
babel = mw.language.new( opts.lang:lower() )
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", 1, true ) then
local mon4 = World.months4[ opts.lang:lower() ]
if mon4 and mon4[ self.month ] then
limit4 = true
end
end
elseif self.year then
stamp = string.format( "%04d", self.year )
end
if self.hour then
if stamp then
stamp = stamp .. " "
else
stamp = ""
end
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", 1, true ) then
suite = "monthsLong"
elseif show:find( "M", 1, true ) then
suite = "monthsAbbr"
end
bucket = World[ suite ]
if bucket then
m = bucket[ opts.lang:lower() ]
if slang then
x = bucket[ slang:lower() ]
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, allocate )
-- Relative move by interval
-- Parameter:
-- self -- table, to be used as base
-- add -- string or number, to be added
-- allocate -- true, if a clone shall be returned
-- 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
if r[ Meta.signature ] then
raw = r[ Meta.signature ]
else
raw = r
end
if type( shift ) == "table" then
rel = shift
else
rel = Private.future( shift )
end
end
if raw and rel then
if allocate then
r = Prototypes.clone( r )
raw = r[ Meta.signature ]
end
for k, v in pairs( rel ) do
raw[ k ] = ( raw[ k ] or 0 ) + v
end -- for k, v
Calc.fair( raw )
r[ Meta.signature ] = raw
end
return r
end -- Prototypes.future()
Prototypes.tostring = function ( self )
-- Stringify yourself
-- Parameter:
-- self -- table, to be stringified
-- Returns:
-- string
local dels = { false, "", "-", "-", "", "", ":", ":", ".", "" }
local wids = { false, 4, 2, 2, 2, 2, 2, 2, 3, 3 }
local s = ""
local n, r, spec
local f = function ( a )
n = self[ Meta.order[ a ] ]
s = s .. dels[ a ]
if n then
spec = string.format( "%%s%%0%dd", wids[ a ] )
s = string.format( spec, s, n )
end
end -- f()
for i = 2, 5 do
f( i )
end -- for i
r = s
s = ""
for i = 6, 10 do
f( i )
end -- for i
if s == "::." then
r = r:gsub( "%-+$", "" )
else
if r == "--" then
r = s
else
r = string.format( "%sT%s", r, s )
end
end
r = r:gsub( "%.$", "" )
return r
end -- Prototypes.tostring()
Prototypes.valueOf = function ( self )
-- Returns yourselves primitive value (primitive table)
-- Parameter:
-- self -- table, to be dumped
-- Returns:
-- table, or false
local r
if type( self ) == "table" then
r = self[ Meta.signature ]
end
return r or false
end -- Prototypes.valueOf()
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,
Meta.fiat( 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"
elseif ask == "*" then
if World.present then
if assigned.hour then
if assigned.dom or assigned.month or assigned.year then
if World.present.both and
World.present.date and
World.present.time then
r1 = World.present.both
:gsub( "$date", World.present.date )
:gsub( "$time", World.present.time )
else
r1 = World.present.date
end
end
r1 = r1 or World.present.time
else
r1 = World.present.date
end
end
r1 = r1 or "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
if not template then
local i = slang:find( "-", 3, true )
if i then
slang = slang:sub( 1, i - 1 ):lower()
tmp = World.templates[ slang ]
if tmp then
template = tmp[ ask ]
end
end
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", 1, true ) 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, 1, true )
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
if not code and
slang ~= "en" and
World.zones.en then
code = World.zones.en[ 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, alien )
local slang = args.lang or alien
local obj = Meta.fiat( args[ 1 ], false, args.shift )
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 invalid" )
else
r = ""
end
end
return r
end -- p.test
function p.failsafe( frame )
local s = type( frame )
local r, since
if s == "table" then
since = frame.args[ 1 ]
elseif s == "string" then
since = mw.text.trim( since )
if since == "" then
since = false
end
end
return Prototypes.failsafe( false, since ) or ""
end -- p.failsafe
function p.format( frame )
-- 1 -- stamp
-- 2 -- spec
-- lang
-- shift
-- noerror
local l, r
local v = { frame.args[ 1 ],
frame.args[ 2 ],
shift = frame.args.shift,
noerror = frame.args.noerror }
if not v[ 1 ] or v[ 1 ] == "now" then
v[ 1 ] = frame:callParserFunction( "#timel", "c", v.shift )
v.shift = false
end
Frame = frame
l, r = pcall( p.test, v, frame.args[ 3 ] or frame.args.lang )
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