Zum Inhalt springen

„Modul:DateTime“ – Versionsunterschied

aus Wikipedia, der freien Enzyklopädie
[gesichtete Version][gesichtete Version]
Inhalt gelöscht Inhalt hinzugefügt
update
gemäss Admin-Anfragen vom 3.10.
 
(26 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
local DateTime = { serial = "2023-10-03",
--[=[ 2014-03-16
suite = "DateTime",
Date and time utilities
item = 20652535 }
]=]
-- Date and time objects

local Failsafe = DateTime

local GlobalMod = DateTime

-- local globals
local Calc = { }
local DateTime
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 = { } }
months4 = { } }
local Nbsp = mw.ustring.char( 160 )
local MaxYear = 2999
local Frame
local Tab = mw.ustring.char( 9 )
DateTime.char = { nbsp = mw.ustring.char( 160 ),
tab = mw.ustring.char( 9 ) }
World.era = { en = { "BC", "AD" } }
World.era = { en = { "BC", "AD" } }
World.monthsAbbr = { en = { n = 3 } }
World.monthsAbbr = { en = { n = 3 } }
Zeile 52: 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 87: 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
-- 2020-01-01
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 )
end
end
return r
end -- foreignModule()




Zeile 98: 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 -- fault()
end -- capitalize()




Zeile 108: Zeile 177:
-- Returns:
-- Returns:
-- string, HTML span
-- string, HTML span
return string.format( "<span class=\"error\">%s</span>", a )
local e = mw.html.create( "span" )
:addClass( "error" )
:wikitext( a )
return tostring( e )
end -- fault()
end -- fault()






DateTime = function ( assign, alien )
local function frame()
if not Frame then
-- Create metatable (constructor)
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 125: 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
local meta = { }
r = { [ Meta.signature ] = r }
local s = "__datetime"
setmetatable( r, Meta.tableI )
meta.__index = function( self, access )
return self[ s ][ access ]
end
meta.__newindex = function( self, access, assign )
if type( access ) == "string" then
local data = self[ s ]
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
r = { [ s ] = r }
r.fair = function ( ... )
return Prototypes.fair( ... )
end
r.figure = function ( ... )
return Prototypes.figure( ... )
end
r.first = function ( ... )
return Prototypes.first( ... )
end
r.format = function ( ... )
return Prototypes.format( ... )
end
r.full = function ( ... )
return Prototypes.full( ... )
end
setmetatable( r, meta )
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 188: Zeile 525:
if amount <= 4 then
if amount <= 4 then
r.year = tonumber( analyse )
r.year = tonumber( analyse )
elseif n == 14 then
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, 2 ) )
r.month = tonumber( analyse:sub( 5, 6 ) )
r.dom = tonumber( analyse:sub( 7, 2 ) )
r.dom = tonumber( analyse:sub( 7, 8 ) )
r.hour = tonumber( analyse:sub( 9, 2 ) )
r.hour = tonumber( analyse:sub( 9, 10 ) )
r.min = tonumber( analyse:sub( 11, 2 ) )
r.min = tonumber( analyse:sub( 11, 12 ) )
r.sec = tonumber( analyse:sub( 13, 2 ) )
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+)([%-%.:w]?)(.*)$" )
local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" )
r.year = tonumber( s )
r.year = tonumber( s )
if sep == "-" then
if sep == "-" then
Zeile 230: Zeile 567:
end
end
elseif sep:lower() == "w" then
elseif sep:lower() == "w" then
if s then
if sx then
s = s:match( "^(%d%d?)$" )
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 242: Zeile 582:
else
else
r = false
r = false
end
if r then
r.iso = true
end
end
elseif amount == 8 then
elseif amount == 8 then
Zeile 262: Zeile 605:
n = n .. "00"
n = n .. "00"
r.msec = tonumber( n:sub( 1, 3 ) )
r.msec = tonumber( n:sub( 1, 3 ) )
sz = s
if #n >= 6 then
r.mysec = tonumber( n:sub( 4, 6 ) )
end
sz = s
end
end
end
end
Zeile 378: Zeile 724:
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( "^%-([0-3][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 412: Zeile 827:
n = #s2
n = #s2
if n <= 2 and #s3 == 4 then
if n <= 2 and #s3 == 4 then
rO.dom = tonumber( n )
rO.dom = tonumber( s2 )
rO.year = tonumber( s3 )
rO.year = tonumber( s3 )
rO.dom2 = ( n == 2 )
rO.dom2 = ( n == 2 )
Zeile 552: Zeile 967:
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 757: Zeile 1.176:
end
end
else
else
r, s = Parser.monthHeading( s, r )
local rM, sM = Parser.monthHeading( s, r )
if r and s ~= "" then
if rM then
r = Parser.time( s, r )
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 768: Zeile 1.192:




Private.factory = function ( assign, alien )
Private.factory = function ( assign, alien, add )
-- Create DateTime table (constructor)
-- Create DateTime table (constructor)
-- Parameter:
-- Parameter:
Zeile 775: Zeile 1.199:
-- 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 r = false
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 = mw.getCurrentFrame():callParserFunction( "#timel",
stamp = frame():callParserFunction( "#timel", "c", shift )
shift = false
"c" )
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 801: Zeile 1.237:




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 812: Zeile 1.249:
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( "&nbsp;", " " )
r = analyse:gsub( "&nbsp;", " " )
:gsub( "&#160;", " " )
:gsub( "&#160;", " " )
:gsub( "&#x[aA]0;", " " )
:gsub( "&#x[aA]0;", " " )
:gsub( "&#32;", " " )
:gsub( "&#32;", " " )
:gsub( Nbsp, " " )
:gsub( DateTime.char.nbsp, " " )
:gsub( Tab, " " )
:gsub( DateTime.char.tab, " " )
:gsub( " +", " " )
:gsub( " +", " " )
: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 834: Zeile 1.278:
end
end
end
end
slang = slang:lower()
slang = slang:lower()
if slang == "en" or slang == "de" then
suitable = parser[ slang ]
if suitable then
local l
local l
l, r = pcall( Parser.GermanEnglish, r )
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 855: Zeile 1.304:




Private.foreign = function ()
Private.field = function ( at, ask, adapt, atleast )
-- Format object as string
-- Parameter:
-- at -- DateTime
-- ask -- string, with 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
-- atleast -- string, with default value, or nil
-- Returns:
-- string, or false, if invalid, or number for julian date
local r, spec
if type( ask ) == "string" then
if ask:sub( 1, 1 ) == "$" then
if ask:sub( 1, 11 ) == "$JulianDate" then
local luxury = ( ask:sub( -2 ) == ",$" )
if ask:sub( 1, 14 ) == "$JulianDateJul" then
at.legacy = true
elseif ask:sub( 1, 15 ) == "$JulianDateGreg" then
else
at.legacy = Private.former( at )
end
r = Private.fixed( at, luxury )
elseif ask:sub( 1, 11 ) == "$JulianCal$" then
adapt.legacy = true
spec = ask:sub( 12 )
elseif ask:sub( 1, 3 ) == "$\"$" then
r = ask:sub( 4 )
else
spec = ask
end
else
spec = ask
end
else
spec = false
end
if not r then
r = Private.format( at, spec, adapt )
end
return r or atleast
end -- Private.field()



Private.fixed = function ( at, advanced )
-- Compute julian date
-- Parameter:
-- at -- DateTime
-- .legacy -- true: at is in Julian calendar
-- advanced -- true: format long number
-- Returns:
-- number, or string
local mM, mMY, mY, nY, r
if at.year then
mY = at.year * 12
else -- actually invalid
mY = 0
end
if at.month then
mMY = at.month
else
mMY = 1
end
mMY = mMY + 57609
if at.dom then
r = at.dom
else
r = 1
end
mM = ( mY + mMY ) * 0.08333333333 -- divided by 12 months
nY = math.floor( mM - 1 )
r = math.floor( nY * 365.25 )
+ math.floor( ( mMY%12 + 4 ) * 30.6 )
+ r
if at.legacy then
r = r - 32205.5
else
r = r - math.floor( nY * 0.01 ) -- no leap day in century
+ math.floor( nY * 0.0025 ) -- but every 400 years
- 32167.5
end
if at.hour then -- divided by 24 hours per day
r = r + at.hour * 0.0416666666666667
else
r = r + 0.5
end
if at.min then -- divided by 1440 minutes per day
r = r + at.min * 0.000694444444
end
if at.sec then -- divided by 86400 seconds per day
r = r + at.min * 0.00001157407407
end
if at.bc then
r = 3442406 - r
if at.legacy then
r = r + 3
end
end
if advanced then
local slang = ( at.lang or World.slang )
local o = mw.language.new( slang )
r = o:formatNum( r )
end
return r
end -- Private.fixed()



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 World.localization then
if not Meta.localized then
local l, d = pcall( mw.loadData, "Module:DateTime/local" )
local d = foreignModule( DateTime.suite,
if l then
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 875: Zeile 1.491:
end -- for k, v
end -- for k, v
end
end
World.localization = true
Meta.localized = true
end
end
end -- Private.foreign()
end -- Private.foreign()



Private.format = function ( at, ask, adapt )
-- Format object as string
-- Parameter:
-- at -- 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 not
local slang = at.lang or "en"
local opts = { lang = slang }
local babel, r
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 at.month then
stamp = World.monthsLong.en[ at.month ]
if at.year then
stamp = string.format( "%s %04d", stamp, at.year )
end
if at.dom then
stamp = string.format( "%d %s", at.dom, stamp )
end
if ask and ask:find( "Mon4", 1, true ) then
local mon4 = World.months4[ opts.lang:lower() ]
if mon4 and mon4[ at.month ] then
limit4 = true
end
end
elseif at.year then
stamp = string.format( "%04d", at.year )
end
if at.hour then
if stamp then
stamp = stamp .. " "
else
stamp = ""
end
stamp = string.format( "%s%02d:", stamp, at.hour )
if at.min then
stamp = string.format( "%s%02d", stamp, at.min )
if at.sec then
stamp = string.format( "%s:%02d",
stamp, at.sec )
if at.msec then
stamp = string.format( "%s.%03d",
stamp, at.msec )
if at.mysec then
stamp = string.format( "%s%03d",
stamp,
at.mysec )
end
end
end
else
stamp = stamp .. "00"
end
if at.zone then
stamp = stamp .. World.zones.formatter( at, "+-" )
end
end
show, suffix = World.templates.formatter( at, 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( "&#160;$", "" )
if at.year and at.year < 1000 then
r = r:gsub( string.format( "%04d", at.year ),
tostring( at.year ) )
end
if at.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[ at.month ]
local ex
if x then
ex = x[ at.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
return r
end -- Private.format()



Private.former = function ( at )
-- Analyze whether Julian calendar
-- Parameter:
-- at -- table, to be evaluated
-- Returns:
-- true, i
local r
if at.year then
if at.year < 1582 then
r = true
elseif at.year == 1582 then
if at.month then
if at.month < 10 then
r = true
elseif at.month == 10 then
r = ( at.dom <= 15 )
end
end
end
end
return r
end -- Private.former()



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:
-- self -- table, or not, with DateTime object, unused
-- atleast -- string, with required version
-- or "wikidata" or "~" or "@" or false
-- Postcondition:
-- Returns string -- with queried version/item, also if problem
-- false -- if appropriate
-- 2020-08-17
local since = atleast
local last = ( since == "~" )
local linked = ( since == "@" )
local link = ( since == "item" )
local r
if last or link or linked or since == "wikidata" then
local item = Meta.item
since = false
if type( item ) == "number" and item > 0 then
local suited = string.format( "Q%d", item )
if linkedlink then
r = suited
else
local entity = mw.wikibase.getEntity( suited )
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
Failsafe.serial ) then
r = false
elseif linked then
if mw.title.getCurrentTitle().prefixedText
== mw.wikibase.getSitelink( suited ) then
r = false
else
r = suited
end
else
r = vsn.value
end
if last then
r = false
else
r = vsn.value
end
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 891: Zeile 1.813:
local r = ( type( self ) == "table" )
local r = ( type( self ) == "table" )
if r then
if r then
local defs = { year = { max = 2099 },
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 = 1000 }
msec = { max = 999 },
mysec = { max = 999 }
}
}
local months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
local fNum =
local fNum =
function ( k, v )
function ( k, v )
Zeile 905: Zeile 1.831:
local dk = defs[ k ]
local dk = defs[ k ]
if dk then
if dk then
local mx = dk.max
if type( dk.max ) == "number" then
if type( mx ) == "number" then
ret = ( type( v ) == "number" )
ret = ( type( v ) == "number" )
if ret then
if ret then
ret = ( v >= 0 and v <= mx
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 919: Zeile 1.850:
return ret
return ret
end -- fNum()
end -- fNum()
if self.bc then
defs.year.max = 999999
end
defs.dom.f =
defs.dom.f =
function ()
function ()
Zeile 938: Zeile 1.872:
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 947: Zeile 1.881:
end
end
if d == 29 and m == 2 and y then
if d == 29 and m == 2 and y then
if y % 4 ~= 0 or y % 400 == 0 then
if y % 4 ~= 0 or
( y % 100 == 0 and
y % 400 ~= 0 ) then
ret = false
ret = false
end
end
Zeile 999: Zeile 1.935:
end
end
else
else
local order = { "bc", "year", "month", "dom",
local life = false
"hour", "min", "sec", "msec" }
local leak = false
local life = false
local leak = false
local s, v
local s, v
for i = 1, 8 do
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 1.015: Zeile 1.949:
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
else
elseif i == 3 then
if not self.week then
life = false
end
elseif i ~= 4 then
life = false
life = false
end
end
end -- for i
end -- for i
if self.week and ( self.month or self.dom ) then
r = false
end
end
end
end
end
Zeile 1.082: Zeile 2.023:
return r
return r
end -- Prototypes.first()
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()




Zeile 1.090: Zeile 2.105:
-- self -- table, with numbers etc.
-- self -- table, with numbers etc.
-- ask -- string, format spec, or nil
-- ask -- string, format spec, or nil
-- table, with multiple formats
-- string may contain multiple formats joined by "|||"
-- adapt -- table, with options, or nil
-- adapt -- table, with options, or nil
-- .lang -- string, with particular language code
-- .lang -- string, with particular language code
-- .london -- true: UTC output; default: local
-- .lonely -- true: permit lonely hour
-- .lonely -- true: permit lonely hour
-- Returns:
-- Returns:
-- string, or false, if invalid
-- string, or false, if invalid, or number for julian date
local r = false
local r
if type( self ) == "table" then
if type( self ) == "table" then
local opts = { lang = self.lang }
local s = type( ask )
local babel
local poly
if type( adapt ) == "table" then
if s == "string" and ask:find( "|||", 1, true ) then
if type( adapt.lang ) == "string" then
poly = mw.text.split( ask, "|||" )
opts.lang = adapt.lang
elseif s == "table" then
end
poly = ask
opts.london = adapt.london
opts.lonely = adapt.lonely
end
end
babel = mw.language.new( opts.lang )
if poly then
if babel then
r = ""
local shift, show, stamp, suffix, limit4, locally
for i = 1, #poly do
if self.month then
r = r .. Private.field( self, poly[ i ], adapt )
stamp = World.monthsLong.en[ self.month ]
end -- for i
if self.year then
else
stamp = string.format( "%s %04d", stamp, self.year )
r = Private.field( self, ask, adapt )
end
if self.dom then
stamp = string.format( "%d %s", self.dom, stamp )
end
if ask and ask:find( "Mon4" ) then
local mon4 = World.months4[ opts.lang ]
if mon4 then
if mon4[ self.month ] then
limit4 = true
end
end
end
elseif self.year then
stamp = string.format( "%04d", self.year )
end
if self.hour then
stamp = string.format( "%s %02d:", stamp, self.hour )
if self.min then
stamp = string.format( "%s%02d", stamp, self.min )
if self.sec then
stamp = string.format( "%s:%02d",
stamp, self.sec )
if self.msec then
stamp = string.format( "%s.%d",
stamp, self.msec )
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( "&#160;$", "" )
if self.year and self.year < 1000 then
r = r:gsub( string.format( "%04d", self.year ),
tostring( self.year ) )
end
if self.month and show:find( "M" ) then
local m = World.monthsAbbr[ opts.lang ]
if m then
local ex = m[ self.month ]
if ex then
local stop = m.suffix
local std = ex[ 1 ]
local shift = ex[ 2 ]
if stop then
std = string.format( "%s%%%s",
std, stop )
shift = string.format( "%s%%%s",
shift, stop )
end
r = mw.ustring.gsub( r, stop, shift )
end
end
end
if suffix then
r = r .. suffix
end
end
end
end
end
return r
return r or false
end -- Prototypes.format()
end -- Prototypes.format()


Zeile 1.211: Zeile 2.157:




World.templates.formatter = function ( assigned, ask, adapt )
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( "%.$", "" )
:gsub( ":+$", "" )
if self.bc then
if self.year then
r = "-" .. r
else
r = r .. " BC"
end
end
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:
Zeile 1.225: Zeile 2.314:
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 ]
Zeile 1.233: Zeile 2.342:
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( "[ .]?[jJ][ .,%-]*", "" )
r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" )
:gsub( "^&#160;", "" )
:gsub( "^&#160;", "" )
if not assigned.month then
if not assigned.month then
r1 = r1:gsub( "[ .%-]?[fFmM][ .%-]*", "" )
r1 = r1:gsub( "[ .%-]?[FmMnt][ .%-]*", "" )
end
end
end
end
Zeile 1.248: Zeile 2.368:
r1 = r1:gsub( " ?[yY] ?", "" )
r1 = r1:gsub( " ?[yY] ?", "" )
if not assigned.dom then
if not assigned.dom then
r1 = r1:gsub( "[ .]?[jJ][ .,%-]*", "" )
r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" )
:gsub( "^&#160;", "" )
:gsub( "^&#160;", "" )
end
end
end
end
if template.lift then
if template.lift and
( assigned.dom or
not ( assigned.month or assigned.year or assigned.bc )
) then
local stamp = false
local stamp = false
local low = ( ask == "ISO" or ask == "ISO-T" )
if assigned.hour then
if assigned.hour then
if assigned.min then
if assigned.min then
Zeile 1.261: Zeile 2.383:
stamp = "H:i:s"
stamp = "H:i:s"
if assigned.msec then
if assigned.msec then
stamp = string.format( "%s.%d",
stamp = string.format( "%s.%03d",
stamp,
stamp,
assigned.msec )
assigned.msec )
if assigned.mysec then
stamp = string.format( "%s.%03d",
stamp,
assigned.mysec )
end
end
end
end
end
Zeile 1.270: Zeile 2.397:
end
end
end
end
if low or ask:find( "hh:mm:ss" ) then
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 )
end
end
elseif ask:find( "hh:mm", 1, true ) and
stamp and
#stamp > 3 then
r1 = string.format( "%s H:i", r1 )
end
end
if stamp then
if stamp then
local dewiki = ( ask == "dewiki" or
if low or template.long then
ask:find( "Zone$" ) )
if low or dewiki then
local scheme
local scheme
if dewiki then
if template.long then
scheme = "de"
scheme = mw.language.getContentLanguage()
scheme = scheme.code
end
end
r2 = World.zones.formatter( assigned, scheme )
r2 = World.zones.formatter( assigned, scheme )
Zeile 1.327: Zeile 2.457:
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.342: Zeile 2.472:
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.398: Zeile 2.533:
local p = { }
local p = { }


function p.test( args )
p.test = function ( args, alien )
local slang = args.lang or alien
local obj = Meta.fiat( args[ 1 ], false, args.shift )
local r
local r
local obj = DateTime( args[ 1 ], "de" )
if type( obj ) == "table" then
if type( obj ) == "table" then
local opt
local spec = args[ 2 ]
local spec = args[ 2 ]
local slang = args[ 3 ]
local opt
if spec then
if spec then
spec = mw.text.trim( spec )
spec = mw.text.trim( spec )
Zeile 1.415: Zeile 2.550:
r = ( args.noerror or "0" )
r = ( args.noerror or "0" )
if r == "0" then
if r == "0" then
r = fault( "Format nicht erkannt" )
r = fault( "Format invalid" )
else
else
r = ""
r = ""
end
if args.errCat then
local cats = mw.text.split( args.errCat, "%s*|%s*" )
for i = 1, #cats do
r = string.format( "%s[[Category:%s]]", r, cats[ i ] )
end -- for i
end
end
end
end
Zeile 1.425: Zeile 2.566:




function p.format( frame )
p.failsafe = function ( 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



p.format = function ( 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[ 3 ],
shift = frame.args.shift,
noerror = frame.args.noerror }
noerror = frame.args.noerror,
errCat = frame.args.errCat }
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.443: Zeile 2.608:




p.DateTime = function ( ... )
p.lt = function ( frame )
return DateTime( ... )
return Templates.flow( frame, "lt" )
end -- p.lt
p.le = function ( frame )
return Templates.flow( frame, "le" )
end -- p.le
p.eq = function ( frame )
return Templates.flow( frame, "eq" )
end -- p.eq
p.ne = function ( frame )
return Templates.flow( frame, "ne" )
end -- p.ne
p.ge = function ( frame )
return Templates.flow( frame, "ge" )
end -- p.ge
p.gt = function ( frame )
return Templates.flow( frame, "gt" )
end -- p.gt



p.DateTime = function ()
return DateTime
end -- p.DateTime
end -- p.DateTime



Aktuelle Version vom 3. Oktober 2023, 22:18 Uhr

Vorlagenprogrammierung Diskussionen Lua Test Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData: 2023-10-03

local DateTime   = { serial = "2023-10-03",
                     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    = 2999
local Frame
DateTime.char = { nbsp = mw.ustring.char( 160 ),
                  tab  = mw.ustring.char( 9 ) }
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&#160;M Y",
                          long = true },
                    [ "$dmy" ] =
                        { spec = "H:i, j&#160;M Y",
                          long = true },
                    [ "$ymd" ] =
                        { spec = "H:i, Y M&#160;j",
                          long = true  },
                    [ "$dmyt" ] =
                        { spec = "j&#160;M Y, H:i",
                          long = true  },
                    [ "$dmyts" ] =
                        { spec = "j&#160;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
    -- 2020-01-01
    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 )
        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( "^%-([0-3][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( "&nbsp;", " " )
                    :gsub( "&#160;", " " )
                    :gsub( "&#x[aA]0;", " " )
                    :gsub( "&#32;", " " )
                    :gsub( DateTime.char.nbsp, " " )
                    :gsub( DateTime.char.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.field = function ( at, ask, adapt, atleast )
    -- Format object as string
    -- Parameter:
    --     at       -- DateTime
    --     ask      -- string, with 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
    --     atleast  -- string, with default value, or nil
    -- Returns:
    --     string, or false, if invalid, or number for julian date
    local r, spec
    if type( ask ) == "string" then
        if ask:sub( 1, 1 ) == "$" then
            if ask:sub( 1, 11 ) == "$JulianDate" then
                local luxury = ( ask:sub( -2 ) == ",$" )
                if ask:sub( 1, 14 ) == "$JulianDateJul" then
                    at.legacy = true
                elseif ask:sub( 1, 15 ) == "$JulianDateGreg" then
                else
                    at.legacy = Private.former( at )
                end
                r = Private.fixed( at, luxury )
            elseif ask:sub( 1, 11 ) == "$JulianCal$" then
                adapt.legacy = true
                spec = ask:sub( 12 )
            elseif ask:sub( 1, 3 ) == "$\"$" then
                r = ask:sub( 4 )
            else
                spec = ask
            end
        else
            spec = ask
        end
    else
        spec = false
    end
    if not r then
        r = Private.format( at, spec, adapt )
    end
    return r or atleast
end -- Private.field()



Private.fixed = function ( at, advanced )
    -- Compute julian date
    -- Parameter:
    --     at        -- DateTime
    --                  .legacy  -- true: at is in Julian calendar
    --     advanced  -- true: format long number
    -- Returns:
    --     number, or string
    local mM, mMY, mY, nY, r
    if at.year then
        mY = at.year * 12
    else    -- actually invalid
        mY = 0
    end
    if at.month then
        mMY = at.month
    else
        mMY = 1
    end
    mMY = mMY + 57609
    if at.dom then
        r = at.dom
    else
        r = 1
    end
    mM = ( mY + mMY )  *  0.08333333333    -- divided by 12 months
    nY = math.floor( mM - 1 )
    r  = math.floor( nY * 365.25 )
         + math.floor( ( mMY%12 + 4 )  *  30.6 )
         + r
        if at.legacy then
            r = r - 32205.5
        else
            r = r - math.floor( nY * 0.01 )     -- no leap day in century
                  + math.floor( nY * 0.0025 )   -- but every 400 years
                  - 32167.5
        end
    if at.hour then   -- divided by 24 hours per day
        r = r  +  at.hour * 0.0416666666666667
    else
        r = r + 0.5
    end
    if at.min then    -- divided by 1440 minutes per day
        r = r  +  at.min * 0.000694444444
    end
    if at.sec then    -- divided by 86400 seconds per day
        r = r  +  at.min * 0.00001157407407
    end
    if at.bc then
        r = 3442406 - r
        if at.legacy then
            r = r + 3
        end
    end
    if advanced then
        local slang = ( at.lang or World.slang )
        local o = mw.language.new( slang )
        r = o:formatNum( r )
    end
    return r
end -- Private.fixed()



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.format = function ( at, ask, adapt )
    -- Format object as string
    -- Parameter:
    --     at     -- 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 not
    local slang = at.lang or "en"
    local opts  = { lang = slang }
    local babel, r
    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 at.month then
            stamp = World.monthsLong.en[ at.month ]
            if at.year then
                stamp = string.format( "%s %04d", stamp, at.year )
            end
            if at.dom then
                stamp = string.format( "%d %s", at.dom, stamp )
            end
            if ask and ask:find( "Mon4", 1, true ) then
                local mon4 = World.months4[ opts.lang:lower() ]
                if mon4  and  mon4[ at.month ] then
                    limit4 = true
                end
            end
        elseif at.year then
            stamp = string.format( "%04d", at.year )
        end
        if at.hour then
            if stamp then
                stamp = stamp .. " "
            else
                stamp = ""
            end
            stamp = string.format( "%s%02d:", stamp, at.hour )
            if at.min then
                stamp = string.format( "%s%02d", stamp, at.min )
                if at.sec then
                    stamp = string.format( "%s:%02d",
                                           stamp, at.sec )
                    if at.msec then
                        stamp = string.format( "%s.%03d",
                                               stamp, at.msec )
                        if at.mysec then
                            stamp = string.format( "%s%03d",
                                                   stamp,
                                                   at.mysec )
                        end
                    end
                end
            else
                stamp = stamp .. "00"
            end
            if at.zone then
                stamp = stamp .. World.zones.formatter( at, "+-" )
            end
        end
        show, suffix = World.templates.formatter( at, 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( "&#160;$", "" )
        if at.year and at.year < 1000 then
            r = r:gsub( string.format( "%04d", at.year ),
                        tostring( at.year ) )
        end
        if at.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[ at.month ]
                    local ex
                    if x then
                        ex = x[ at.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
    return r
end -- Private.format()



Private.former = function ( at )
    -- Analyze whether Julian calendar
    -- Parameter:
    --     at  -- table, to be evaluated
    -- Returns:
    --     true, i
    local r
    if at.year then
        if at.year < 1582 then
            r = true
        elseif at.year == 1582 then
            if at.month then
                if at.month < 10 then
                    r = true
                elseif at.month == 10 then
                    r = ( at.dom <= 15 )
                end
            end
        end
    end
    return r
end -- Private.former()



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:
    --     self     -- table, or not, with DateTime object, unused
    --     atleast  -- string, with required version
    --                         or "wikidata" or "~" or "@" or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2020-08-17
    local since = atleast
    local last    = ( since == "~" )
    local linked  = ( since == "@" )
    local link    = ( since == "item" )
    local r
    if last  or  link  or  linked  or  since == "wikidata" then
        local item = Meta.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local suited = string.format( "Q%d", item )
            if linkedlink then
                r = suited
            else
                local entity = mw.wikibase.getEntity( suited )
                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
                                          Failsafe.serial ) then
                            r = false
                        elseif linked then
                            if mw.title.getCurrentTitle().prefixedText
                               ==  mw.wikibase.getSitelink( suited ) then
                                r = false
                            else
                                r = suited
                            end
                        else
                            r = vsn.value
                        end
                        if last then
                            r = false
                        else
                            r = vsn.value
                        end
                    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()
        if self.bc then
            defs.year.max = 999999
        end
        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
    --               table, with multiple formats
    --               string may contain multiple formats joined by "|||"
    --     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, or number for julian date
    local r
    if type( self ) == "table" then
        local s = type( ask )
        local poly
        if s == "string"  and  ask:find( "|||", 1, true ) then
            poly = mw.text.split( ask, "|||" )
        elseif s == "table" then
            poly = ask
        end
        if poly then
            r = ""
            for i = 1, #poly do
                r = r .. Private.field( self, poly[ i ], adapt )
            end -- for i
        else
            r = Private.field( self, ask, adapt )
        end
    end
    return r  or  false
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( "%.$", "" )
         :gsub( ":+$", "" )
    if self.bc then
        if self.year then
            r = "-" .. r
        else
            r = r .. " BC"
        end
    end
    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( "^&#160;", "" )
                    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( "^&#160;", "" )
                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
                elseif ask:find( "hh:mm", 1, true )  and
                       stamp  and
                       #stamp > 3 then
                    r1 = string.format( "%s H:i", r1 )
                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&#160;%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 = { }

p.test = function ( 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
        if args.errCat then
            local cats = mw.text.split( args.errCat, "%s*|%s*" )
            for i = 1, #cats do
                r = string.format( "%s[[Category:%s]]",  r,  cats[ i ] )
            end -- for i
        end
    end
    return r
end -- p.test



p.failsafe = function ( 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



p.format = function ( 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,
                errCat  = frame.args.errCat }
    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



p.lt = function ( frame )
    return Templates.flow( frame, "lt" )
end -- p.lt
p.le = function ( frame )
    return Templates.flow( frame, "le" )
end -- p.le
p.eq = function ( frame )
    return Templates.flow( frame, "eq" )
end -- p.eq
p.ne = function ( frame )
    return Templates.flow( frame, "ne" )
end -- p.ne
p.ge = function ( frame )
    return Templates.flow( frame, "ge" )
end -- p.ge
p.gt = function ( frame )
    return Templates.flow( frame, "gt" )
end -- p.gt



p.DateTime = function ()
    return DateTime
end -- p.DateTime

return p