Nicht angemeldeter Benutzer - Bearbeiten von Seiten ist nur als angemeldeter Benutzer möglich.

Modul:DateTime: Unterschied zwischen den Versionen

Aus imedwiki
Zur Navigation springen Zur Suche springen
K (1 Version importiert: Import der Vorlage Internetquelle)
K (1 Version importiert: Import der Vorlage Infobox Unternehmen aus der deutschsprachigen Wikipedia)
 
(2 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
local DateTime  = { serial = "2019-10-21",
+
local DateTime  = { serial = "2020-08-30",
 
                     suite  = "DateTime",
 
                     suite  = "DateTime",
 
                     item  = 20652535 }
 
                     item  = 20652535 }
Zeile 16: Zeile 16:
 
                     months4    = { } }
 
                     months4    = { } }
 
local MaxYear    = 2099
 
local MaxYear    = 2099
local Nbsp      = mw.ustring.char( 160 )
 
local Tab        = mw.ustring.char( 9 )
 
 
local Frame
 
local Frame
 +
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 120: Zeile 120:
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns whatever, probably table
 
     --    Returns whatever, probably table
     -- 2019-10-20
+
     -- 2020-01-01
 
     local storage = access
 
     local storage = access
 
     local fun, lucky, r
 
     local fun, lucky, r
Zeile 151: Zeile 151:
 
         end
 
         end
 
         if not lucky and alert then
 
         if not lucky and alert then
             error( "Missing or invalid page: " .. storage, 0 )
+
             error( "Missing or invalid page: " .. storage )
 
         end
 
         end
 
     end
 
     end
Zeile 316: Zeile 316:
 
end -- Meta.fiat()
 
end -- Meta.fiat()
 
setmetatable( DateTime, Meta.tableL )
 
setmetatable( DateTime, Meta.tableL )
DateTime.serial = nil
+
DateTime.serial   = nil
 
 
  
  
Zeile 526: 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
Zeile 1.255: Zeile 1.254:
 
                     :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( "%[%[", "" )
Zeile 1.305: Zeile 1.304:
  
  
Private.flow = function ( at1, at2 )
+
Private.field = function ( at, ask, adapt, atleast )
     -- Compare two objects
+
     -- Format object as string
 
     -- Parameter:
 
     -- Parameter:
     --    at1 -- DateTime
+
     --    at      -- DateTime
     --    at2 -- 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:
 
     -- Returns:
     --    -1, 0, 1 or nil if not comparable
+
     --    string, or false, if invalid, or number for julian date
     local r = 0
+
     local r, spec
    if at1.bc or at2.bc  and  at1.bc ~= at2.bc then
+
    if type( ask ) == "string" then
        if at1.bc then
+
        if ask:sub( 1, 1 ) == "$" then
             r = -1
+
            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
 
         else
             r = 1
+
             spec = ask
 
         end
 
         end
 
     else
 
     else
         local life  = false
+
         spec = false
         local s, v1, v2
+
    end
        for i = 2, 10 do
+
    if not r then
            s = Meta.order[ i ]
+
         r = Private.format( at, spec, adapt )
            v1 = at1[ s ]
+
    end
            v2 = at2[ s ]
+
    return r or atleast
            if v1 or v2 then
+
end -- Private.field()
                if v1 and v2 then
+
 
                    if v1 < v2 then
+
 
                        r = -1
+
 
                    elseif v1 > v2 then
+
Private.fixed = function ( at, advanced )
                        r = 1
+
    -- Compute julian date
                    end
+
    -- Parameter:
                elseif life then
+
    --    at        -- DateTime
                    if v2 then
+
    --                  .legacy -- true: at is in Julian calendar
                        r = -1
+
    --    advanced  -- true: format long number
                    else
+
    -- Returns:
                        r = 1
+
    --    number, or string
                    end
+
    local mM, mMY, mY, nY, r
                else
+
    if at.year then
                    r = nil
+
        mY = at.year * 12
                end
+
    else    -- actually invalid
                if r ~= 0 then
+
        mY = 0
                    if at1.bc and r then
+
    end
                        r = r * -1
+
    if at.month then
                    end
+
        mMY = at.month
                    break    -- for i
+
    else
                end
+
        mMY = 1
                life = true
+
    end
            end
+
    mMY = mMY + 57609
        end -- for i
+
    if at.dom then
 +
        r = at.dom
 +
    else
 +
        r = 1
 
     end
 
     end
     return r
+
     mM = ( mY + mMY )  *  0.08333333333    -- divided by 12 months
end -- Private.flow()
+
    nY = math.floor( mM - 1 )
 
+
    r  = math.floor( nY * 365.25 )
 
+
        + math.floor( ( mMY%12 + 4 )  *  30.6 )
 
+
        + r
Private.foreign = function ()
+
        if at.legacy then
    -- Retrieve localization submodule
+
            r = r - 32205.5
    if not Meta.localized then
+
         else
         local d = foreignModule( DateTime.suite,
+
            r = r - math.floor( nY * 0.01 )    -- no leap day in century
                                false,
+
                  + math.floor( nY * 0.0025 )  -- but every 400 years
                                "local",
+
                  - 32167.5
                                DateTime.item )
+
         end
         if d then
+
    if at.hour then   -- divided by 24 hours per day
            local wk
+
        r = r  +  at.hour * 0.0416666666666667
            if d.slang then
+
    else
                Meta.suite  = string.format( "%s %s",
+
        r = r + 0.5
                                            Meta.suite, d.slang )
+
    end
                World.slang = d.slang
+
    if at.min then    -- divided by 1440 minutes per day
            end
+
        r = r  +  at.min * 0.000694444444
            for k, v in pairs( d ) do
+
    end
                wk = World[ k ]
+
    if at.sec then   -- divided by 86400 seconds per day
                if wk  and  wk.en then
+
        r = r  +  at.min * 0.00001157407407
                    for subk, subv in pairs( v ) do
+
    end
                        wk[ subk ] = subv
+
    if at.bc then
                    end -- for k, v%s %s
+
        r = 3442406 - r
                else
+
        if at.legacy then
                    World[ k ] = v
+
             r = r + 3
                end
 
             end -- for k, v
 
 
         end
 
         end
        Meta.localized = true
 
 
     end
 
     end
end -- Private.foreign()
+
    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.from = function ( attempt )
+
Private.flow = function ( at1, at2 )
     -- Create valid raw table from arbitrary table
+
     -- Compare two objects
 
     -- Parameter:
 
     -- Parameter:
     --    attempt -- table, to be evaluated
+
     --    at1 -- DateTime
 +
    --    at2  -- DateTime
 
     -- Returns:
 
     -- Returns:
     --    table, with valid components, or nil
+
     --    -1, 0, 1 or nil if not comparable
     local data  = { }
+
     local r = 0
     local r
+
     if at1.bc or at2.bc  and  at1.bc ~= at2.bc then
    for k, v in pairs( Meta.components ) do
+
         if at1.bc then
         if v then
+
             r = -1
             v = ( type( attempt[ k ] )  ==  v )
 
 
         else
 
         else
             v = true
+
             r = 1
 
         end
 
         end
         if v then
+
    else
             data[ k ] = attempt[ k ]
+
        local life  = false
        end
+
        local s, v1, v2
    end -- for k, v
+
         for i = 2, 10 do
    if Prototypes.fair( data ) then
+
            s  = Meta.order[ i ]
        r = data
+
             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
 
     end
 
     return r
 
     return r
end -- Private.from()
+
end -- Private.flow()
  
  
  
Private.future = function ( add )
+
Private.foreign = function ()
     -- Normalize move interval
+
     -- Retrieve localization submodule
    -- Parameter:
+
     if not Meta.localized then
    --    add  -- string or number, to be added
+
         local d = foreignModule( DateTime.suite,
    -- Returns:
+
                                false,
    --    table, with shift, or false/nil
+
                                "local",
    local r
+
                                DateTime.item )
     if add then
+
         if type( d ) == "table" then
         local s = type( add )
+
            local wk
        if s == "string"  and  add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then
+
             if d.slang then
            r = tonumber( add )
+
                 Meta.suite  = string.format( "%s %s",
            s = "number"
+
                                            Meta.suite, d.slang )
        end
+
                World.slang = d.slang
         if s == "number" then
+
             end
             if r == 0 then
+
            for k, v in pairs( d ) do
                 r = false
+
                wk = World[ k ]
            else
+
                if wk  and  wk.en then
                r = string.format( "%d second", r or add )
+
                    for subk, subv in pairs( v ) do
             end
+
                        wk[ subk ] = subv
        elseif s == "string" then
+
                    end -- for k, v
            r = add
+
                else
        else
+
                    World[ k ] = v
            r = false
+
                end
        end
+
             end -- for k, v
        if r then
 
             r = Calc.future( r )
 
 
         end
 
         end
 +
        Meta.localized = true
 
     end
 
     end
    return r
+
end -- Private.foreign()
end -- Private.future()
 
  
  
  
Prototypes.clone = function ( self )
+
Private.format = function ( at, ask, adapt )
     -- Clone object
+
     -- Format object as string
 
     -- Parameter:
 
     -- Parameter:
     --    self -- table, with object, to be cloned
+
     --    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:
 
     -- Returns:
     --    table, with object
+
     --    string, or not
     local r = { [ Meta.signature ] = self[ Meta.signature ] }
+
     local slang = at.lang or "en"
     setmetatable( r, Meta.tableI )
+
    local opts  = { lang = slang }
    return r
+
     local babel, r
end -- Prototypes.clone()
+
    if type( adapt ) == "table" then
 
+
        if type( adapt.lang ) == "string" then
 
+
            local i = adapt.lang:find( "-", 3, true )
 
+
            if i then
Prototypes.failsafe = function ( self, atleast )
+
                slang = adapt.lang:lower()
    -- Retrieve versioning and check for compliance
+
                opts.lang = slang:sub( 1, i - 1 )
    -- Precondition:
+
            else
    --    atleast  -- string, with required version or "wikidata" or "~"
+
                opts.lang = adapt.lang:lower()
     --                or false
+
            end
     -- Postcondition:
+
        end
     --    Returns  string  -- with queried version, also if problem
+
        opts.london = adapt.london
    --              false  -- if appropriate
+
        opts.lonely = adapt.lonely
    local last  = ( atleast == "~" )
+
     end
    local since = atleast
+
     babel = mw.language.new( opts.lang:lower() )
    local r
+
     if babel then
    if last  or  since == "wikidata" then
+
        local shift, show, stamp, suffix, limit4, locally
        local item = Meta.item
+
        if at.month then
        since = false
+
            stamp = World.monthsLong.en[ at.month ]
        if type( item ) == "number" and  item > 0 then
+
            if at.year then
             local entity = mw.wikibase.getEntity( string.format( "Q%d",
+
                stamp = string.format( "%s %04d", stamp, at.year )
                                                                item ) )
+
            end
             if type( entity ) == "table" then
+
            if at.dom then
                 local seek = Failsafe.serialProperty or "P348"
+
                stamp = string.format( "%d %s", at.dom, stamp )
                local vsn  = entity:formatPropertyValues( seek )
+
            end
                 if type( vsn ) == "table" and
+
            if ask and ask:find( "Mon4", 1, true ) then
                  type( vsn.value ) == "string" and
+
                local mon4 = World.months4[ opts.lang:lower() ]
                  vsn.value ~= "" then
+
                if mon4 and  mon4[ at.month ] then
                     if last  and
+
                    limit4 = true
                      vsn.value == ( Meta.serial or DateTime.serial ) then
+
                end
                        r = false
+
            end
                    else
+
        elseif at.year then
                         r = vsn.value
+
             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
 
                 end
 
                 end
 +
            else
 +
                stamp = stamp .. "00"
 +
            end
 +
            if at.zone then
 +
                stamp = stamp .. World.zones.formatter( at, "+-" )
 
             end
 
             end
 
         end
 
         end
    end
+
        show, suffix = World.templates.formatter( at, ask, opts )
    if type( r ) == "nil" then
+
        if limit4 then
        if not since  or  since <= Meta.serial then
+
            show = show:gsub( "M", "F" )
             r = Meta.serial
+
        end
 +
        if type( opts.london ) == "boolean" then
 +
             locally = not opts.london
 
         else
 
         else
             r = false
+
            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
 
     end
 
     end
 
     return r
 
     return r
end -- Prototypes.failsafe()
+
end -- Private.format()
  
  
  
Prototypes.fair = function ( self, access, assign )
+
Private.former = function ( at )
     -- Check formal validity of table
+
     -- Analyze whether Julian calendar
 
     -- Parameter:
 
     -- Parameter:
     --    self    -- table, to be checked
+
     --    at  -- table, to be evaluated
    --    access  -- string or nil, single item to be checked
 
    --    assign  -- single access value to be checked
 
 
     -- Returns:
 
     -- Returns:
     --    true, if valid;  false, if not
+
     --    true, i
     local r = ( type( self ) == "table" )
+
     local r
     if r then
+
     if at.year then
         local defs = { year = { max = MaxYear },
+
         if at.year < 1582 then
                      month = { min =  1,
+
            r = true
                                max = 12 },
+
        elseif at.year == 1582 then
                      week  = { min = 1,
+
            if at.month then
                                max = 53 },
+
                if at.month < 10 then
                      dom   = { min 1,
+
                    r = true
                                max = 31 },
+
                elseif at.month == 10 then
                      hour = { max = 23 },
+
                    r = ( at.dom <= 15 )
                      min  = { max = 59 },
+
                end
                      sec  = { max = 61 },
+
            end
                      msec = { max = 999 },
+
        end
                      mysec = { max = 999 }
+
    end
         }
+
    return r
         local fNum =
+
end -- Private.former()
            function ( k, v )
+
 
                local ret = true
+
 
                local dk  = defs[ k ]
+
 
                if dk then
+
Private.from = function ( attempt )
                    if type( dk.max ) == "number" then
+
    -- Create valid raw table from arbitrary table
                        ret = ( type( v ) == "number" )
+
    -- Parameter:
                        if ret then
+
    --    attempt -- table, to be evaluated
                            local min
+
    -- Returns:
                            if dk.min then
+
    --    table, with valid components, or nil
                                min = dk.min
+
    local data = { }
                            else
+
    local r
                                min = 0
+
    for k, v in pairs( Meta.components ) do
                            end
+
        if v then
                            ret = ( v >= min  and  v <= dk.max
+
            v = ( type( attempt[ k ] ) == v )
                                    and  math.floor( v ) == v )
+
        else
                            if ret and dk.f then
+
            v = true
                                ret = dk.f( v )
+
         end
                            end
+
         if v then
                        end
+
            data[ k ] = attempt[ k ]
                    end
+
        end
                end
+
    end -- for k, v
                return ret
+
    if Prototypes.fair( data ) then
            end -- fNum()
+
        r = data
        defs.dom.f =
+
    end
            function ()
+
    return r
                local ret
+
end -- Private.from()
                local d
+
 
                if access == "dom" then
+
 
                    d = assign
+
 
                else
+
Private.future = function ( add )
                    d = self.dom
+
    -- Normalize move interval
                end
+
    -- Parameter:
                if d then
+
    --    add  -- string or number, to be added
                    ret = ( d <= 28 )
+
    -- Returns:
                    if not ret then
+
    --    table, with shift, or false/nil
                         local m
+
    local r
                        if access == "month" then
+
    if add then
                            m = assign
+
        local s = type( add )
                        else
+
        if s == "string"  and  add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then
                            m = self.month
+
            r = tonumber( add )
                        end
+
            s = "number"
                        if m then
+
        end
                            ret = ( d <= Calc.months[ m ] )
+
        if s == "number" then
                            if ret then
+
            if r == 0 then
                                local y
+
                r = false
                                if access == "year" then
+
            else
                                    y = assign
+
                r = string.format( "%d second",  r or add )
                                else
+
            end
                                    y = self.year
+
        elseif s == "string" then
                                end
+
            r = add
                                if d == 29 and m == 2 and y then
+
        else
                                    if y % 4 ~= or
+
            r = false
                                      ( y % 100 == 0 and
+
        end
                                        y % 400 ~= 0 ) then
+
        if r then
                                        ret = false
+
            r = Calc.future( r )
                                    end
+
        end
                                 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
 +
                                          DateTime.serial ) then
 +
                            r = false
 +
                        elseif linked then
 +
                            if mw.title.getCurrentTitle().prefixedText
 +
                              ==  mw.wikibase.getSitelink( suited ) then
 +
                                r = false
 +
                            else
 +
                                 r = suited
 
                             end
 
                             end
 +
                        else
 +
                            r = vsn.value
 +
                        end
 +
                        if last then
 +
                            r = false
 +
                        else
 +
                            r = vsn.value
 
                         end
 
                         end
 
                     end
 
                     end
                else
 
                    ret = true
 
 
                 end
 
                 end
                return ret
+
             end
             end -- defs.dom.f()
+
         end
         defs.sec.f =
+
    end
            function ()
+
    if type( r ) == "nil" then
                local ret
+
        if not since  or  since <= Meta.serial then
                local second
+
            r = Meta.serial
                if access == "sec" then
+
        else
                    second = assign
+
            r = false
                else
+
        end
                    second = self.sec
+
    end
                end
+
    return r
                if second then
+
end -- Prototypes.failsafe()
                    ret = ( second <= 59 )
+
 
                    if not ret and self.leap then
+
 
                        ret = true
+
 
                    end
+
Prototypes.fair = function ( self, access, assign )
                end
+
    -- Check formal validity of table
                return ret
+
    -- Parameter:
            end -- defs.sec.f()
+
    --    self    -- table, to be checked
        if access or assign then
+
    --    access -- string or nil, single item to be checked
            r = ( type( access ) == "string" )
+
    --    assign -- single access value to be checked
            if r then
+
    -- Returns:
                 local def = defs[ access ]
+
    --    true, if valid;  false, if not
                 if def then
+
    local r = ( type( self ) == "table" )
                     r = fNum( access, assign )
+
    if r then
                    if r then
+
        local defs = { year  = { max = MaxYear },
                        if def == "dom"  or
+
                      month = { min =  1,
                          def == "month" or
+
                                max = 12 },
                          def == "year" then
+
                      week  = { min =  1,
                            r = defs.dom.f()
+
                                max = 53 },
                         end
+
                      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
                elseif access == "lang" then
 
                    r = ( type( assign ) == "string" )
 
                    if r then
 
                        r = assign:match( "^%l%l%l?-?%a*$" )
 
                    end
 
                elseif access == "london" then
 
                    r = ( type( assign ) == "boolean" )
 
 
                 end
 
                 end
             end
+
                return ret
         else
+
             end -- fNum()
             local life  = false
+
         if self.bc then
             local leak  = false
+
             defs.year.max = 999999
            local s, v
+
        end
            for i = 1, 10 do
+
        defs.dom.f =
                 s = Meta.order[ i ]
+
             function ()
                 v = self[ s ]
+
                local ret
                 if v then
+
                local d
                     if not life and leak then
+
                if access == "dom" then
                         -- gap detected
+
                    d = assign
                         r = false
+
                 else
                         break
+
                    d = self.dom
                    else
+
                 end
                        if not fNum( s, v ) then
+
                 if d then
                            r = false
+
                    ret = ( d <= 28 )
                             break    -- for i
+
                     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
                        life = true
 
                        leak = true
 
 
                     end
 
                     end
                 elseif i == 3 then
+
                 else
                     if not self.week then
+
                    ret = true
                         life = false
+
                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
                elseif i ~= 4 then
 
                    life = false
 
 
                 end
 
                 end
             end -- for i
+
                return ret
             if self.week  and  ( self.month or self.dom ) then
+
            end -- defs.sec.f()
                 r = false
+
        if access or assign then
             end
+
            r = ( type( access ) == "string" )
         end
+
            if r then
     end
+
                local def = defs[ access ]
     return r
+
                if def then
end -- Prototypes.fair()
+
                    r = fNum( access, assign )
 
+
                    if r then
 
+
                        if def == "dom"  or
 
+
                          def == "month"  or
Prototypes.figure = function ( self, assign )
+
                          def == "year" then
     -- Assign month by name
+
                            r = defs.dom.f()
     -- Parameter:
+
                        end
     --    self    -- table, to be filled
+
                    end
     --    assign  -- string, with month name
+
                elseif access == "lang" then
     -- Returns:
+
                    r = ( type( assign ) == "string" )
     --    number 1...12, if valid;  false, if not
+
                    if r then
     local r = false
+
                        r = assign:match( "^%l%l%l?-?%a*$" )
     if type( self ) == "table"  and  type( assign ) == "string" then
+
                    end
         r = Parser.monthNumber( assign )
+
                elseif access == "london" then
         if r then
+
                    r = ( type( assign ) == "boolean" )
             self.month = r
+
                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
 
     end
 
     end
Zeile 1.804: 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
Zeile 1.809: Zeile 2.112:
 
     --              .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, slang
+
         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, "|||" )
                local i = adapt.lang:find( "-", 3, true )
+
        elseif s == "table" then
                if i then
+
            poly = ask
                    slang = adapt.lang:lower()
+
        end
                    opts.lang = slang:sub( 1,  i - 1 )
+
        if poly then
                else
+
             r = ""
                    opts.lang = adapt.lang:lower()
+
             for i = 1, #poly do
                end
+
                 r = r .. Private.field( self, poly[ i ], adapt )
             end
+
            end -- for i
            opts.london = adapt.london
+
        else
            opts.lonely = adapt.lonely
+
            r = Private.field( self, ask, adapt )
        end
 
        babel = mw.language.new( opts.lang )
 
        if babel then
 
            local shift, show, stamp, suffix, limit4, locally
 
            if self.month then
 
                stamp = World.monthsLong.en[ self.month ]
 
                if self.year then
 
                    stamp = string.format( "%s %04d", stamp, self.year )
 
                end
 
                if self.dom then
 
                    stamp = string.format( "%d %s", self.dom, stamp )
 
                end
 
                if ask and ask:find( "Mon4" ) then
 
                    local mon4 = World.months4[ opts.lang ]
 
                    if mon4 then
 
                        if mon4[ self.month ] then
 
                            limit4 = true
 
                        end
 
                    end
 
                end
 
            elseif self.year then
 
                stamp = string.format( "%04d", self.year )
 
            end
 
            if self.hour then
 
                stamp = string.format( "%s %02d:", stamp, self.hour )
 
                if self.min then
 
                    stamp = string.format( "%s%02d", stamp, self.min )
 
                    if self.sec then
 
                        stamp = string.format( "%s:%02d",
 
                                              stamp, self.sec )
 
                        if self.msec then
 
                            stamp = string.format( "%s.%03d",
 
                                                  stamp, self.msec )
 
                            if self.mysec then
 
                                stamp = string.format( "%s%03d",
 
                                                      stamp,
 
                                                      self.mysec )
 
                            end
 
                        end
 
                    end
 
                else
 
                    stamp = stamp .. "00"
 
                end
 
                if self.zone then
 
                    stamp = stamp .. World.zones.formatter( self, "+-" )
 
                end
 
             end
 
            show, suffix = World.templates.formatter( self, ask, opts )
 
            if limit4 then
 
                show = show:gsub( "M", "F" )
 
             end
 
            if type( opts.london ) == "boolean" then
 
                locally = not opts.london
 
            else
 
                locally = true
 
            end
 
            r = babel:formatDate( show, stamp, locally )
 
            r = r:gsub( "&#160;$", "" )
 
            if self.year and self.year < 1000 then
 
                 r = r:gsub( string.format( "%04d", self.year ),
 
                            tostring( self.year ) )
 
            end
 
            if self.month then
 
                local bucket, m, suite, x
 
                if show:find( "F" ) then
 
                    suite = "monthsLong"
 
                elseif show:find( "M" ) then
 
                    suite = "monthsAbbr"
 
                end
 
                bucket = World[ suite ]
 
                if bucket then
 
                    m = bucket[ opts.lang ]
 
                    if slang then
 
                        x = bucket[ slang ]
 
                    end
 
                    if m then
 
                        local base = m[ self.month ]
 
                        local ex
 
                        if x then
 
                            ex = x[ self.month ]
 
                        end
 
                        if suite == "monthsAbbr" then
 
                            local stop
 
                            if ex then
 
                                stop = x.suffix
 
                                base = ex
 
                            else
 
                                stop = m.suffix
 
                            end
 
                            if base and stop then
 
                                local shift, std
 
                                std  = string.format( "%s%%%s",
 
                                                      base[ 1 ], stop )
 
                                shift = string.format( "%s%s",
 
                                                      base[ 2 ], stop )
 
                                r = mw.ustring.gsub( r, std, shift )
 
                            end
 
                        elseif suite == "monthsLong" then
 
                            if base and ex then
 
                                r = mw.ustring.gsub( r, base, ex )
 
                            end
 
                        end
 
                    end
 
                end
 
            end
 
            if suffix then
 
                r = r .. suffix
 
            end
 
 
         end
 
         end
 
     end
 
     end
     return r
+
     return r or  false
 
end -- Prototypes.format()
 
end -- Prototypes.format()
  
Zeile 2.012: Zeile 2.207:
 
     -- Returns:
 
     -- Returns:
 
     --    string
 
     --    string
     local dels = { false, "", "-", "-", "", ":", ":", ".", "", "" }
+
     local dels = { false, "", "-", "-", "", "", ":", ":", ".", "" }
     local wids = { false, 4,  2,  2,  2,  2,   2,  2,  3,  3  }
+
     local wids = { false, 4,  2,  2,  2,  2, 2,  2,  3,  3  }
 
     local s    = ""
 
     local s    = ""
 
     local n, r, spec
 
     local n, r, spec
Zeile 2.039: Zeile 2.234:
 
         else
 
         else
 
             r = string.format( "%sT%s", r, s )
 
             r = string.format( "%sT%s", r, s )
 +
        end
 +
    end
 +
    r = r:gsub( "%.$", "" )
 +
    if self.bc then
 +
        if self.year then
 +
            r = "-" .. r
 +
        else
 +
            r = r .. " BC"
 
         end
 
         end
 
     end
 
     end
Zeile 2.110: Zeile 2.313:
 
     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 2.118: Zeile 2.341:
 
             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
Zeile 2.139: Zeile 2.372:
 
             end
 
             end
 
             if template.lift and
 
             if template.lift and
               (assigned.dom or
+
               ( assigned.dom or
                not (assigned.month or assigned.year or assigned.bc)
+
                not ( assigned.month or assigned.year or assigned.bc )
 
               ) then
 
               ) then
 
                 local stamp = false
 
                 local stamp = false
Zeile 2.163: Zeile 2.396:
 
                     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
Zeile 2.219: Zeile 2.456:
 
             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 2.295: Zeile 2.532:
 
local p = { }
 
local p = { }
  
function p.test( args, alien )
+
p.test = function ( args, alien )
 
     local slang = args.lang or alien
 
     local slang = args.lang or alien
 
     local obj  = Meta.fiat( args[ 1 ], false, args.shift )
 
     local obj  = Meta.fiat( args[ 1 ], false, args.shift )
Zeile 2.315: Zeile 2.552:
 
         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 2.322: Zeile 2.565:
  
  
function p.failsafe( frame )
+
p.failsafe = function ( frame )
 
     local s = type( frame )
 
     local s = type( frame )
 
     local r, since
 
     local r, since
Zeile 2.338: Zeile 2.581:
  
  
function p.format( frame )
+
p.format = function ( frame )
 
     --    1      -- stamp
 
     --    1      -- stamp
 
     --    2      -- spec
 
     --    2      -- spec
Zeile 2.348: Zeile 2.591:
 
                 frame.args[ 2 ],
 
                 frame.args[ 2 ],
 
                 shift  = frame.args.shift,
 
                 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.shift )
 
         v[ 1 ]  = frame:callParserFunction( "#timel", "c", v.shift )
Zeile 2.363: Zeile 2.607:
  
  
function p.lt( frame )
+
p.lt = function ( frame )
 
     return Templates.flow( frame, "lt" )
 
     return Templates.flow( frame, "lt" )
 
end -- p.lt
 
end -- p.lt
function p.le( frame )
+
p.le = function ( frame )
 
     return Templates.flow( frame, "le" )
 
     return Templates.flow( frame, "le" )
 
end -- p.le
 
end -- p.le
function p.eq( frame )
+
p.eq = function ( frame )
 
     return Templates.flow( frame, "eq" )
 
     return Templates.flow( frame, "eq" )
 
end -- p.eq
 
end -- p.eq
function p.ne( frame )
+
p.ne = function ( frame )
 
     return Templates.flow( frame, "ne" )
 
     return Templates.flow( frame, "ne" )
 
end -- p.ne
 
end -- p.ne
function p.ge( frame )
+
p.ge = function ( frame )
 
     return Templates.flow( frame, "ge" )
 
     return Templates.flow( frame, "ge" )
 
end -- p.ge
 
end -- p.ge
function p.gt( frame )
+
p.gt = function ( frame )
 
     return Templates.flow( frame, "gt" )
 
     return Templates.flow( frame, "gt" )
 
end -- p.gt
 
end -- p.gt

Aktuelle Version vom 18. September 2020, 11:45 Uhr

Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch English

Modul: Dokumentation
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
Wikipedia-logo-v2.svg
Dieses Modul (und die Dokumentation) basieren (teilweise) auf Modul:DateTime aus der freien Enzyklopädie Wikipedia in der Fassung 127005311 vom 14. December 2019 und steht unter der GNU Lizenz für freie Dokumentation und der Creative Commons Attribution/Share Alike. Auf Wikipedia ist eine Liste der Autoren verfügbar. Veränderungen seither in Imedwiki. Veränderungen seither in Wikipedia.Weiteres zum Import aus Wikipedia siehe Seite Imedwiki:Import aus Wikipedia.

local DateTime   = { serial = "2020-08-30",
                     suite  = "DateTime",
                     item   = 20652535 }
-- Date and time objects
local Failsafe   = DateTime
local GlobalMod  = DateTime
local Calc       = { }
local Meta       = { }
local Parser     = { }
local Private    = { }
local Prototypes = { }
local Templates  = { }
local World      = { slang       = "en",
                     monthsLong  = { },
                     monthsParse = { },
                     months4     = { } }
local MaxYear    = 2099
local 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( "^%-([012][0-9])" )
                if s then
                    n  = tonumber( s )
                    if n >= 1  and  n <= 31 then
                        rO.dom = n
                        rS     = rS:sub( 4 )
                    else
                        rO = false
                    end
                else
                    rS:sub( 2 )
                end
            else
                rO = false
            end
            if rO then
                if #rS > 0 then
                    if rO.dom then
                        n = rS:byte( 1, 1 )
                        if n == 32  or  n == 84 then
                            rS = rS:sub( 2 )
                        else
                            rO = false
                        end
                    else
                        rO = false
                    end
                end
            end
        end
    else
        rO = false
    end
    if rO then
        rO.iso = true
    else
        rS = false
    end
    return rO, rS
end -- Parser.isoDate()



Parser.monthHeading = function ( analyse, assign )
    -- String analysis, retrieve month heading date (US only)
    -- Parameter:
    --     analyse  -- string, with heading word
    --     assign   -- table
    -- Returns:
    --     1  -- table, extended if parsed
    --     2  -- stripped string, or false, if invalid text format
    local rO = assign
    local rS = analyse
    local s, sep = mw.ustring.match( analyse, "^([%a&;]+%.?)([^%a%.]?)" )
    if s then
        -- might begin with month name   "December 23, 2013"
        local n = Parser.monthNumber( s )
        if n then
            rO.month = n
            if sep == "" then
                rS = ""
            else
                local s2, s3
                n = mw.ustring.len( s )  +  1
                s = mw.ustring.sub( analyse, n )
                s2 = s:match( "^ (%d%d%d?%d?)$" )
                if s2 then
                    rO.year = tonumber( s2 )
                    rS = ""
                else
                    s2, s3, rS = s:match( "^ (%d+), (%d+)( ?.*)$" )
                    if s2 and s3 then
                        n = #s2
                        if n <= 2  and  #s3 == 4 then
                            rO.dom  = tonumber( s2 )
                            rO.year = tonumber( s3 )
                            rO.dom2 = ( n == 2 )
                        else
                            rO = false
                        end
                    else
                        rO = false
                    end
                end
            end
        else
            rO = false
        end
    else
        rO = false
    end
    if not rO then
        rS = false
    end
    return rO, rS
end -- Parser.monthHeading()



Parser.monthNumber = function ( analyse )
    -- String analysis, retrieve month number
    -- Parameter:
    --     analyse  -- string, with month name including any period
    -- Returns:
    --     number, 1...12 if found
    --     false or nil, if not detected
    local r = false
    local s = mw.ustring.match( analyse, "^([%a&;]+)%.?$" )
    if s then
        local given
        s = capitalize( s )
        for k, v in pairs( World.monthsLong ) do
            given = World.monthsParse[ k ]
            if given then
                r = given[ s ]
            end
            if not r then
                given = World.monthsLong[ k ]
                for i = 1, 12 do
                    if given[ i ] == s then
                        r = i
                        break
                    end
                end -- for i
            end
            if r then
                break
            end
        end -- for k, v
    end
    return r
end -- Parser.monthNumber()



Parser.putDate = function ( aYear, aMonth, aDom, assign )
    -- Store date strings
    -- Parameter:
    --     aYear   -- string, with year, or false
    --     aMonth  -- string, with numeric month
    --     aDom    -- string, with day of month
    --     assign  -- table
    -- Returns:
    --     table, extended
    local r = assign
    if aYear then
        r.year   = tonumber( aYear )
    end
    r.month  = tonumber( aMonth )
    r.dom    = tonumber( aDom )
    r.month2 = ( #aMonth == 2 )
    r.dom2   = ( #aDom == 2 )
    return r
end -- Parser.putDate()



Parser.time = function ( analyse, assign, adjusted )
    -- String analysis, retrieve time components
    -- Parameter:
    --     analyse   -- string, with time part
    --     assign    -- table
    --     adjusted  -- true: fixed length of 2 digits expected
    -- Returns:
    --     table, extended if parsed
    --     false, if invalid text format
    local r = assign
    if analyse ~= "" then
        local s, sx = analyse:match( "^(%d+)(:?.*)$" )
        if s then
            local n = #s
            if n <= 2 then
                r.hour  = tonumber( s )
                if not adjusted then
                    r.hour2 = ( n == 2 )
                end
            else
                sx = false
                r  = false
            end
            if sx then
                s, sx = sx:match( "^:(%d+)(:?(.*))$"  )
                if s then
                    if #s == 2 then
                        r.min = tonumber( s )
                        if sx == "" then
                            sx = false
                        end
                    else
                        sx = false
                        r  = false
                    end
                    if sx then
                        local sep
                        local scan = "^([:,] ?)(%d+)(.*)$"
                        sep, s, sx = sx:match( scan )
                        if sep == ":" then
                            if #s == 2 then
                                r.sec = tonumber( s )
                            end
                        elseif sep == ", " then
                            r = Parser.wikiDate( s .. sx,  r )
                            sx = false
                        else
                            r = false
                        end
                    end
                else
                    r = false
                end
            end
            if sx  and  sx ~= "" then
                s = sx:match( "^%.(%d+)$" )
                if s then
                    s  = s .. "00"
                    r.msec = tonumber( s:sub( 1, 3 ) )
                    if #s >= 6 then
                        r.mysec = tonumber( s:sub( 4, 6 ) )
                    end
                else
                    r = false
                end
            end
        else
            r = false
        end
    end
    return r
end -- Parser.time()



Parser.wikiDate = function ( analyse, assign )
    -- String analysis, for date after wiki ~~~~~ signature time
    --     dmy    10:28, 30. Dez. 2013
    --     ymd    10:28, 2013 Dez. 30
    -- Parameter:
    --     analyse  -- string
    --     assign   -- table
    -- Returns:
    --     table, extended if parsed
    --     false, if invalid text format
    local r
    local s = analyse:match( "^(2%d%d%d) " )
    local sx
    if s then
        -- ymd    "10:28, 2013 Dez. 30"
        local n = false
        r = assign
        r.year = tonumber( s )
        s = analyse:sub( 6 )
        s, sx = mw.ustring.match( analyse:sub( 6 ),
                                  "^([%a&;]+)%.? (%d%d?)$" )
        if s then
            n = Parser.monthNumber( s )
            if n then
               r.month = n
            end
        end
        if n then
            r.dom  = tonumber( sx )
            r.dom2 = ( #sx == 2 )
        else
            r = false
        end
    else
        -- dmy    "10:28, 30. Dez. 2013"
        local sep
        s, sep, sx = analyse:match( "^(%d%d?)(%.? ?)(%a.+)$" )
        if s then
            r = Parser.european( s, sep, sx, assign )
        else
            r = false
        end
    end
    return r
end -- Parser.wikiDate()



Parser.yearTime = function ( analyse, assign )
    -- String analysis, for possible year and possible time
    -- Parameter:
    --     analyse  -- string, starting with year
    --     assign   -- table
    -- Returns:
    --     table, extended if parsed
    --     false, if invalid text format
    local r = assign
    local n = #analyse
    if n > 0 then
        local s, sx
        if n == 4 then
            if analyse:match( "^%d%d%d%d$" ) then
                s  = analyse
                sx = false
            end
        else
            s = analyse:match( "^(%d%d%d%d)[ ,]" )
            if s then
                sx = analyse:sub( 5 )
            else
                local suffix
                s, sx, suffix = analyse:match( "^(%d+)([ ,]?)(.*)$" )
                if s then
                    local j = #sx
                    n = #s
                    if n < 4  and  ( j == 1 or #suffix == 0 ) then
                        sx = analyse:sub( n + j )
                    else
                        s = false
                    end
                end
            end
        end
        if s then
            r.year = tonumber( s )
            if sx then
                s, sx = sx:match( "^(,? ?)(%d.*)$" )
                if #s >= 1 then
                    r = Parser.time( sx, r )
                end
            end
        else
            r = false
        end
    end
    return r
end -- Parser.yearTime()



Parser.zone = function ( analyse, assign )
    -- String analysis, for time zone
    -- +/-nn +/-nnnn (AAAa)
    -- Parameter:
    --     analyse  -- string
    --     assign   -- table
    -- Returns:
    --     1  -- table, with number or string zone, if any, or false
    --     2  -- string, with zone stripped off, if any
    local rO = assign
    local rS = analyse
    local s, sign, shift, sub
    s = "^(.+)([+-])([01]%d):?(%d?%d?)$"
    s, sign, shift, sub = analyse:match( s )
    if sign then
        if s:find( ":%d%d *$" ) then
            if sub then
                if #sub == 2 then
                    rO.zone = tonumber( shift .. sub )
                else
                    rO = false
                end
            else
                rO.zone = tonumber( shift ) * 100
            end
            if rO then
                if sign == "-" then
                    rO.zone = - rO.zone
                end
                rS = mw.text.trim( s )
            end
        end
    elseif analyse:find( "%(.*%)$" ) then
        s, shift = analyse:match( "^(.+)%((%a%a%a%a?)%)$" )
        if shift then
            rO.zone = shift:upper()
            rS      = mw.text.trim( s )
        else
            rO = false
        end
    else
        s, shift = analyse:match( "^(.+%d) ?(%a+)$" )
        if shift then
            local n = #shift
            if n == 1 then
                rO.zone = shift:upper()
            elseif n == 3 then
                if shift == "UTC"  or  shift == "GMT" then
                    rO.zone = 0
                end
            end
            if rO.zone then
                rS = s
            end
        end
    end
    return rO, rS
end -- Parser.zone()



Parser.GermanEnglish = function ( analyse )
    -- String analysis, for German and English formats
    -- Parameter:
    --     analyse  -- string, with date or time or parts of it
    -- Returns:
    --     table, if parsed
    --     false, if invalid text format
    local r, s = Parser.eraGermanEnglish( analyse )
    r, s = Parser.zone( s, r )
    if r then
        local start, sep, sx = s:match( "^(%d+)([ %-%.:WwT]?)(.*)$" )
        if start then
            -- begins with one or more digits (ASCII)
            local n    = #start
            local lazy = ( start == s   and
                           ( n >=4  or  type( r.bc == "boolean" ) ) )
            if n == 4  or  n == 8  or  lazy then
                r = Parser.digitsHeading( s, lazy, n, r )
            elseif n <= 2 then
                if sep == ":" then
                    r, s = Parser.time( s, r )
                elseif sep == "" then
                    r = false
                else
                    r = Parser.european( start, sep, sx, r )
                end
            else
                r = false
            end
        else
            local rM, sM = Parser.monthHeading( s, r )
            if rM then
                r = rM
            else
                r, sM = Parser.isoDate( s, r )
            end
            if r and sM ~= "" then
                r = Parser.time( sM, r )
            end
        end
    end
    return r
end -- Parser.GermanEnglish()



Private.factory = function ( assign, alien, add )
    -- Create DateTime table (constructor)
    -- Parameter:
    --     assign  -- string, with initial timestamp, or nil
    --                nil    -- now
    --                false  -- empty object
    --     alien   -- string, with language code, or nil
    --     add     -- string, with interval (PHP strtotime), or nil
    -- Returns:
    --     table, for DateTime object
    --     string or false, if failed
    local l     = true
    local slang = mw.text.trim( alien or World.slang or "en" )
    local r
    if assign == false then
        r = { }
    else
        local stamp = ( assign or "now" )
        local shift
        if add then
            shift = Private.future( add )
        end
        r = false
        if stamp == "now" then
            stamp = frame():callParserFunction( "#timel", "c", shift )
            shift = false
        else
            local seconds = stamp:match( "^#(%d+)$" )
            if seconds then
                stamp = os.date( "!%Y-%m-%dT%H:%M:%S",
                                 tonumber( seconds ) )
            end
        end
        l, r = pcall( Private.fetch, stamp, slang, shift )
    end
    if l  and  type( r ) == "table" then
        if slang ~= "" then
            r.lang = slang
        end
    end
    return r
end -- Private.factory()



Private.fetch = function ( analyse, alien, add )
    -- Retrieve object from string
    -- Parameter:
    --     analyse  -- string to be interpreted
    --     alien    -- string with language code, or nil
    --     add      -- table, with interval, or nil
    -- Returns:
    --     table, if parsed
    --     false, if invalid text format
    --     string, if serious error (args)
    local r
    if type( analyse ) == "string" then
        local strip = mw.ustring.char( 0x5B, 0x200E, 0x200F, 0x5D )
        r =  analyse:gsub( "&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
                                          DateTime.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( "%.$", "" )
    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