Modul:DateTime

Aus ÖsterreichWiki
Version vom 28. Oktober 2018, 22:06 Uhr von Agruwie (Diskussion | Beiträge) (1 Version importiert: Bei Wikipedia von Löschung bedroht)
Zur Navigation springen Zur Suche springen
Diese Vorlage(n) wurde(n) fast unverändert von der deutschsprachigen Wikipedia übernommen. Es wurden nur geringfügige technische, stilistische und organisatorische Anpassungen ans ÖsterreichWiki durchgeführt.

local DateTime   = { serial = "2018-01-24",
                     suite  = "DateTime",
                     item   = 20652535 }    -- Date and time objects
local Calc       = { }
local Meta       = { }
local Parser     = { }
local Private    = { }
local Prototypes = { }
local Templates  = { }
local World      = { slang       = "en",
                     monthsLong  = { },
                     monthsParse = { },
                     months4     = { } }
local MaxYear    = 2099
local Nbsp       = mw.ustring.char( 160 )
local Tab        = mw.ustring.char( 9 )
local Frame
World.era = { en = { "BC", "AD" } }
World.monthsAbbr = {  en = { n = 3 }  }
World.monthsLong.en = { "January",
                        "February",
                        "March",
                        "April",
                        "May",
                        "June",
                        "July",
                        "August",
                        "September",
                        "October",
                        "November",
                        "December"
                      }
World.monthsParse.en = { [ "Apr" ] =  4,
                         [ "Aug" ] =  8,
                         [ "Dec" ] = 12,
                         [ "Feb" ] =  2,
                         [ "Jan" ] =  1,
                         [ "Jul" ] =  7,
                         [ "Jun" ] =  6,
                         [ "Mar" ] =  3,
                         [ "May" ] =  5,
                         [ "Nov" ] = 11,
                         [ "Oct" ] = 10,
                         [ "Sep" ] =  9
                       }
World.months4.en = { [ 6 ] = true,
                     [ 7 ] = true }
World.templates = { [ "ISO" ] =
                        { spec = "Y-m-d",
                          lift = true },
                    [ "ISO-T" ] =
                        { spec = "c" },
                    [ "timestamp" ] =
                        { spec = "YmdHis" },
                    [ "default" ] =
                        { spec = "H:i, j M Y",
                          long = true },
                    [ "$dmy" ] =
                        { spec = "H:i, j M Y",
                          long = true },
                    [ "$ymd" ] =
                        { spec = "H:i, Y M j",
                          long = true  },
                    [ "$dmyt" ] =
                        { spec = "j M Y, H:i",
                          long = true  },
                    [ "$dmyts" ] =
                        { spec = "j M Y, H:i:s",
                          long = true  },
                    [ "data-sort-type:date" ] =
                        { spec = "j M Y" }
                  }
World.templates.en = { }
World.zones = {
    [ "!" ] = "YXWVUTSRQPONZABCDEFGHIKLM",
    UTC  =   0,
    GMT  =   0       -- Greenwich Mean Time
}
World.zones.en = {
    BST  =   100,    -- British Summer Time
    IST  =   100,    -- Irish Summer Time
    WET  =   0,      -- Western Europe Time
    WEST =   100,    -- Western Europe Summer Time
    CET  =   100,    -- Central Europe Time
    CEST =   200,    -- Central Europe Summer Time
    EET  =   200,    -- Eastern Europe Time
    EEST =   300,    -- Eastern Europe Summer Time
    MSK  =   300,    -- Moscow Time
    MSD  =   400,    -- Moscow Summer Time
    NST  =  -330,    -- Newfoundland Standard Time
    NDT  =  -230,    -- Newfoundland Daylight Time
    AST  =  -400,    -- Atlantic Standard Time
    ADT  =  -300,    -- Atlantic Daylight Time
    EST  =  -500,    -- Eastern Standard Time
    EDT  =  -400,    -- Eastern Daylight Saving Time
    CST  =  -600,    -- Central Standard Time
    CDT  =  -500,    -- Central Daylight Saving Time
    MST  =  -700,    -- Mountain Standard Time
    MDT  =  -600,    -- Mountain Daylight Saving Time
    PST  =  -800,    -- Pacific Standard Time
    PDT  =  -700,    -- Pacific Daylight Saving Time
    AKST =  -900,    -- Alaska Standard Time
    AKDT =  -800,    -- Alaska Standard Daylight Saving Time
    HST  = -1000     -- Hawaiian Standard Time
}



local function capitalize( a )
    -- Upcase first character, downcase anything else
    -- Parameter:
    --     a  -- string
    -- Returns:
    --     string
    return  mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) )
            .. mw.ustring.lower( mw.ustring.sub( a, 2 ) )
end -- capitalize()



local function fault( a )
    -- Format error message by class=error
    -- Parameter:
    --     a  -- string, error message
    -- Returns:
    --     string, HTML span
    return string.format( "<span class=\"error\">%s</span>", a )
end -- fault()



local function frame()
    if not Frame then
        Frame = mw.getCurrentFrame()
    end
    return Frame
end -- frame()



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  n > 1  and
                           adjust.month >= 1  and
                           adjust.month <= 12 and
                           adjust.year > 1900 then
                            max = Calc.months[ adjust.month ]
                            if adjust.month == 2  and
                               ( adjust.year % 4 ~= 0  or
                                 adjust.year % 400 == 0 ) then
                                max = 28
                            end
                            if n <= max then
                                max = false
                            end
                        end
                        if max then
                            m    = n % 30
                            move = ( n - m )  /  30
                            n    = 1 + m
                        end
                    end

                    end
                adjust[ s ] = n
            end
        end
    end -- for i
end -- Calc.fair()



Calc.future = function ( add )
    -- Parse move interval
    -- Parameter:
    --     add   -- string, with GNU relative items
    -- Returns:
    --     table, with numeric components, or false
    local r, token
    local units = { year      = true,
                    month     = true,
                    fortnight = { slot = "dom", mult = 14 },
                    week      = { slot = "dom", mult = 7 },
                    dom       = true,
                    hour      = true,
                    min       = true,
                    sec       = true }
    local story = string.format( " %s ", add:lower() )
                        :gsub( "%s+", " " )
                        :gsub( " yesterday ", " -1 dom " )
                        :gsub( " tomorrow ",   " 1 dom " )
                        :gsub( "(%l)s ", "%1 " )
                        :gsub( " day ",    " dom " )
                        :gsub( " minute ", " min " )
                        :gsub( " second ", " sec " )
    local feed  = function ()
                      local slice
                      token, slice = story:match( "^( (%S+)) " )
                      return slice
                  end
    local fed   = function ()
                      story = story:sub( #token + 1 )
                  end
    local m, n, s, u
    while true do
        s = feed()
        if s then
            n = 1
            if s:match( "^[+-]?%d+$" ) then
                n = tonumber( s )
                fed()
                s = feed()
            end
            if s then
                u = units[ s ]
            end
            if s and u then
                fed()
                if u ~= true then
                    s = u.slot
                    n = n * u.mult
                end
                if feed() == "ago" then
                    if n > 0 then
                        n = - n
                    end
                    fed()
                end
                r      = r  or  { }
                r[ s ] = ( r[ s ] or 0 )  +  n
            else
                r = false
                break    -- while true
            end
        else
            break    -- while true
        end
    end    -- while true
    return r
end -- Calc.future()



Parser.digitsHeading = function ( analyse, alone, amount, add )
    -- String analysis, if digits only or at least 4 digits heading
    -- Parameter:
    --     analyse  -- string to be scanned, starting with digit
    --                 digits only, else starting with exactly 4 digits
    --     alone    -- true, if only digits
    --     amount   -- number of heading digits
    --     add      -- table, to be extended
    -- Returns:
    --     table, extended if parsed
    --     false, if invalid text format
    local r = add
    if alone then
        -- digits only
        if amount <= 4 then
            r.year = tonumber( analyse )
        elseif n == 14 then
            -- timestamp
            r.year   = tonumber( analyse:sub(  1, 4 ) )
            r.month  = tonumber( analyse:sub(  5, 2 ) )
            r.dom    = tonumber( analyse:sub(  7, 2 ) )
            r.hour   = tonumber( analyse:sub(  9, 2 ) )
            r.min    = tonumber( analyse:sub( 11, 2 ) )
            r.sec    = tonumber( analyse:sub( 13, 2 ) )
        else
            r = false
        end
    elseif amount == 4 then
        local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" )
        r.year = tonumber( s )
        if sep == "-" then
            -- ISO
            s, sep, sx = sx:match( "^(%d%d)(-?)(.*)$" )
            if s then
                r.month  = tonumber( s )
                r.month2 = true
                if sep == "-" then
                    s, sep, sx = sx:match( "^(%d%d?)([ T]?)(.*)$" )
                    if s then
                        r.dom = tonumber( s )
                        if sep == "T" then
                            r.month2 = nil
                        else
                            r.dom2 = ( #s == 2 )
                        end
                        if sep then
                            r = Parser.time( sx,  r,  sep == "T" )
                        end
                    else
                        r = false
                    end
                elseif sx and sx ~= "" then
                    r = false
                end
            else
                r = false
            end
        elseif sep:lower() == "w" then
            if sx then
                s = sx:match( "^(%d%d?)$" )
                if s then
                    r.week = tonumber( s )
                    if r.week < 1  or  r.week > 53 then
                        r = false
                    end
                else
                    r = false
                end
            else
                r = false
            end
        else
            r = false
        end
        if r then
            r.iso = true
        end
    elseif amount == 8 then
        -- ISO compact
        local s, sz = analyse:match( "^%d+T(%d+)([.+-]?%d*%a*)$" )
        if s then
            local n = #s
            if n == 2  or  n == 4  or  n == 6 then
                r.year  = tonumber( analyse:sub(  1,  4 ) )
                r.month = tonumber( analyse:sub(  5,  6 ) )
                r.dom   = tonumber( analyse:sub(  7,  8 ) )
                r.hour  = tonumber( analyse:sub( 10, 11 ) )
                if n > 2 then
                    r.min = tonumber( s:sub( 3, 4 ) )
                    if n == 6 then
                        r.sec = tonumber( s:sub( 5, 6 ) )
                    end
                    n, s = sz:match( "^(%.%d+)([+-]?[%a%d]*)$" )
                    if n then
                        n      = n .. "00"
                        r.msec = tonumber( n:sub( 1, 3 ) )
                        if #n >= 6 then
                            r.mysec = tonumber( n:sub( 4, 6 ) )
                        end
                        sz = s
                    end
                end
                if sz ~= "" then
                    s, sz = sz:match( "^([+-]?)(%a*)$" )
                    if s == "" then
                        if sz:match( "^(%u)$" ) then
                            r.zone = sz
                        else
                            s = false
                        end
                    elseif #s == 1 then
                        r.zone = s .. sz
                    else
                        s = false
                    end
                end
            else
                s = false
            end
        end
        if s then
            r = false
        end
    end
    return r
end -- Parser.digitsHeading()



Parser.eraGermanEnglish = function ( analyse )
    -- String analysis, for German and English era
    -- v. Chr.   v. u. Z.  n. Chr.   AD BC   A.D. B.C. B.C.E.
    -- Parameter:
    --     analyse  -- string
    -- Returns:
    --     1  -- table, with boolean era, if any
    --     2  -- string, with era stripped off, if any
    local rO = { }
    local rS = analyse
    local s, switch = analyse:match( "^(.+) ([vn])%. ?Chr%.$" )
    if switch then
        rS    = s
        rO.bc = ( switch == "v" )
    elseif analyse:find( " v%. ?u%. ?Z%.$" ) then
        rS    = analyse:match( "^(.+) v%. ?u%. ?Z%.$" )
        rO.bc = true
    elseif analyse:find( " B%.? ?C%.? ?E?%.?$" ) then
        rS    = analyse:match( "^(.+) B%.? ?C%.? ?E?%.?$" )
        rO.bc = true
    elseif analyse:find( "^A%.? ?D%.? " ) then
        rS    = analyse:match( "^A%.? ?D%.? (.*)$" )
        rO.bc = false
    end
    return rO, rS
end -- Parser.eraGermanEnglish()



Parser.european = function ( ahead, adhere, analyse, assign )
    -- String analysis, retrieve date style: DOM MONTH YEAR
    -- Parameter:
    --     ahead    -- string, with first digits, not more than 2
    --     adhere   -- string, with first separator; not ":"
    --     analyse  -- string, remainder following adhere
    --     assign   -- table
    -- Returns:
    --     table, extended if parsed
    local r = assign
    local s, s2, sx
    if adhere == "."  or  adhere == ". " then
        -- 23.12.2013
        -- 23. Dezember 2013
        s, sx = analyse:match( "^(%d%d?)%.(.*)$" )
        if s then
            r = Parser.putDate( false, s, ahead, assign )
            r = Parser.yearTime( sx, r )
        else
            s, sx = mw.ustring.match( analyse,
                                      "^ ?([%a&;]+%.?) ?(.*)$" )
            if s then
                local n = Parser.monthNumber( s )
                if n then
                    r.month = n
                    r.dom   = tonumber( ahead )
                    r.dom2  = ( #ahead == 2 )
                    r       = Parser.yearTime( sx, r )
                else
                    r = false
                end
            else
                r = false
            end
        end
    elseif adhere == " " then
        -- 23 Dec 2013
        s, sx = mw.ustring.match( analyse,
                                  "^([%a&;]+%.?) ?(.*)$" )
        if s then
            local n = Parser.monthNumber( s )
            if n then
                r.month = n
                r.dom   = tonumber( ahead )
                r.dom2  = ( #ahead == 2 )
                r       = Parser.yearTime( sx, r )
            else
                r = false
            end
        else
            r = false
        end
    else
        r = false
    end
    return r
end -- Parser.european()



Parser.isoDate = function ( analyse, assign )
    -- String analysis, retrieve month heading ISO date
    -- Parameter:
    --     analyse  -- string, with heading hyphen
    --     assign   -- table
    -- Returns:
    --     1  -- table, extended if parsed
    --     2  -- stripped string, or false, if invalid text format
    local rO, rS
    if analyse:match( "^%-%-?[0-9]" ) then
        local n, s
        rO = assign
        rS = analyse:sub( 2 )
        s  = rS:match( "^([012][0-9])%-" )
        if s then
            n = tonumber( s )
            if n >= 1  and  n <= 12 then
                rO.month = n
                rS       = rS:sub( 3 )
            else
                rO = false
            end
        end
        if rO then
            if rS:byte( 1, 1 ) == 45 then
                local suffix
                s = rS:match( "^%-([012][0-9])" )
                if s then
                    n  = tonumber( s )
                    if n >= 1  and  n <= 31 then
                        rO.dom = n
                        rS     = rS:sub( 4 )
                    else
                        rO = false
                    end
                else
                    rS:sub( 2 )
                end
            else
                rO = false
            end
            if rO then
                if #rS > 0 then
                    if rO.dom then
                        n = rS:byte( 1, 1 )
                        if n == 32  or  n == 84 then
                            rS = rS:sub( 2 )
                        else
                            rO = false
                        end
                    else
                        rO = false
                    end
                end
            end
        end
    else
        rO = false
    end
    if rO then
        rO.iso = true
    else
        rS = false
    end
    return rO, rS
end -- Parser.isoDate()



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



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



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



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



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



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



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



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



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



Private.fetch = function ( analyse, alien, add )
    -- Retrieve object from string
    -- Parameter:
    --     analyse  -- string to be interpreted
    --     alien    -- string with language code, or nil
    --     add      -- string, with interval (PHP strtotime), or nil
    -- Returns:
    --     table, if parsed
    --     false, if invalid text format
    --     string, if serious error (args)
    local r
    if type( analyse ) == "string" then
        r =  analyse:gsub( "&nbsp;", " " )
                    :gsub( "&#160;", " " )
                    :gsub( "&#x[aA]0;", " " )
                    :gsub( "&#32;", " " )
                    :gsub( Nbsp, " " )
                    :gsub( Tab, " " )
                    :gsub( "  +", " " )
                    :gsub( "%[%[", "" )
                    :gsub( "%]%]", "" )
        r = mw.text.trim( r )
        if r == "" then
            r = { }
        else
            local slang = ( alien or "" )
            if slang == "" then
                slang = "en"
            else
                local s = slang:match( "^(%a+)%-" )
                if s then
                     slang = s
                end
            end
            slang = slang:lower()
            if slang == "en" or slang == "de" then
                local l
                l, r = pcall( Parser.GermanEnglish, r )
                if l and r then
                    if not Prototypes.fair( r ) then
                        r = false
                    elseif add then
                        r = Prototypes.future( r, add )
                    end
                end
            else
                r = "unknown language"
            end
        end
    else
        r = "bad type"
    end
    return r
end -- Private.fetch()



Private.flow = function ( at1, at2 )
    -- Compare two objects
    -- Parameter:
    --     at1  -- DateTime
    --     at2  -- DateTime
    -- Returns:
    --     -1, 0, 1 or nil if not comparable
    local r = 0
    if at1.bc or at2.bc  and  at1.bc ~= at2.bc then
        if at1.bc then
            r = -1
        else
            r = 1
        end
    else
        local 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 l, d = pcall( mw.loadData, "Module:DateTime/local" )
        if l 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%s %s
                else
                    World[ k ] = v
                end
            end -- for k, v
        end
        Meta.localized = true
    end
end -- Private.foreign()



Private.from = function ( attempt )
    -- Create valid raw table from arbitrary table
    -- Parameter:
    --     attempt  -- table, to be evaluated
    -- Returns:
    --     table, with valid components, or nil
    local data  = { }
    local r
    for k, v in pairs( Meta.components ) do
        if v then
            v = ( type( attempt[ k ] )  ==  v )
        else
            v = true
        end
        if v then
            data[ k ] = attempt[ k ]
        end
    end -- for k, v
    if Prototypes.fair( data ) then
        r = data
    end
    return r
end -- Private.from()



Private.future = function ( add )
    -- Normalize move interval
    -- Parameter:
    --     add   -- string or number, to be added
    -- Returns:
    --     string, with shift, or false/nil
    local r
    if add then
        local s = type( add )
        if s == "string"  and  add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then
            r = tonumber( add )
            s = "number"
        else
            r = add
        end
        if s == "number" then
            if r == 0 then
                r = false
            else
                r = string.format( "%d second", r )
            end
        elseif s ~= "string" then
            r = false
        end
        if r then
            r = Calc.future( r )
        end
    end
    return r
end -- Private.future()



Prototypes.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.fair = function ( self, access, assign )
    -- Check formal validity of table
    -- Parameter:
    --     self    -- table, to be checked
    --     access  -- string or nil, single item to be checked
    --     assign  -- single access value to be checked
    -- Returns:
    --     true, if valid;  false, if not
    local r = ( type( self ) == "table" )
    if r then
        local defs = { year  = { max = MaxYear },
                       month = { min =  1,
                                 max = 12 },
                       week  = { min =  1,
                                 max = 53 },
                       dom   = { min =  1,
                                 max = 31 },
                       hour  = { max = 23 },
                       min   = { max = 59 },
                       sec   = { max = 61 },
                       msec  = { max = 999 },
                       mysec = { max = 999 }
        }
        local fNum =
            function ( k, v )
                local ret = true
                local dk  = defs[ k ]
                if dk then
                    if type( dk.max ) == "number" then
                        ret = ( type( v ) == "number" )
                        if ret then
                            local min
                            if dk.min then
                                min = dk.min
                            else
                                min = 0
                            end
                            ret = ( v >= min  and  v <= dk.max
                                    and  math.floor( v ) == v )
                            if ret and dk.f then
                                ret = dk.f( v )
                            end
                        end
                    end
                end
                return ret
            end -- fNum()
        defs.dom.f =
            function ()
                local ret
                local d
                if access == "dom" then
                    d = assign
                else
                    d = self.dom
                end
                if d then
                    ret = ( d <= 28 )
                    if not ret then
                        local m
                        if access == "month" then
                            m = assign
                        else
                            m = self.month
                        end
                        if m then
                            ret = ( d <= Calc.months[ m ] )
                            if ret then
                                local y
                                if access == "year" then
                                    y = assign
                                else
                                    y = self.year
                                end
                                if d == 29  and  m == 2  and  y then
                                    if y % 4 ~= 0   or
                                       ( y % 100 == 0  and
                                         y % 400 ~= 0 ) then
                                        ret = false
                                    end
                                end
                            end
                        end
                    end
                else
                    ret = true
                end
                return ret
            end -- defs.dom.f()
        defs.sec.f =
            function ()
                local ret
                local second
                if access == "sec" then
                    second = assign
                else
                    second = self.sec
                end
                if second then
                    ret = ( second <= 59 )
                    if not ret and self.leap then
                        ret = true
                    end
                end
                return ret
            end -- defs.sec.f()
        if access or assign then
            r = ( type( access ) == "string" )
            if r then
                local def = defs[ access ]
                if def then
                    r = fNum( access, assign )
                    if r then
                        if def == "dom"  or
                           def == "month"  or
                           def == "year" then
                            r = defs.dom.f()
                        end
                    end
                elseif access == "lang" then
                    r = ( type( assign ) == "string" )
                    if r then
                        r = assign:match( "^%l%l%l?-?%a*$" )
                    end
                elseif access == "london" then
                    r = ( type( assign ) == "boolean" )
                end
            end
        else
            local life  = false
            local leak  = false
            local s, v
            for i = 1, 10 do
                s = Meta.order[ i ]
                v = self[ s ]
                if v then
                    if not life and leak then
                        -- gap detected
                        r = false
                        break
                    else
                        if not fNum( s, v ) then
                            r = false
                            break    -- for i
                        end
                        life = true
                        leak = true
                    end
                elseif i == 3 then
                    if not self.week then
                        life = false
                    end
                elseif i ~= 4 then
                    life = false
                end
            end -- for i
            if self.week  and  ( self.month or self.dom ) then
                r = false
            end
        end
    end
    return r
end -- Prototypes.fair()



Prototypes.figure = function ( self, assign )
    -- Assign month by name
    -- Parameter:
    --     self    -- table, to be filled
    --     assign  -- string, with month name
    -- Returns:
    --     number 1...12, if valid;  false, if not
    local r = false
    if type( self ) == "table"  and  type( assign ) == "string" then
        r = Parser.monthNumber( assign )
        if r then
            self.month = r
        end
    end
    return r
end -- Prototypes.figure()



Prototypes.first = function ( self )
    -- Retrieve abbreviated month name in current language
    -- Parameter:
    --     self  -- table, to be evaluated
    -- Returns:
    --     string, if defined;  false, if not
    local r
    if type( self ) == "table"  and  self.month then
        local slang = ( self.lang or World.slang )
        r = World.monthsLong[ slang ]
        if r then
            local brief = World.monthsAbbr[ slang ]
            r = r[ self.month ]
            if brief then
                local ex = brief[ self.month ]
                local s  = brief.suffix
                if ex then
                    r = ex[ 2 ]
                else
                    local n = brief.n or 3
                    r = mw.ustring.sub( r, 1, n )
                end
                if s then
                    r = r .. s
                end
            end
        end
    else
        r = false
    end
    return r
end -- Prototypes.first()



Prototypes.flow = function ( self, another, assert )
    -- Compare this object with another timestamp
    -- Parameter:
    --     self     -- table, with numbers etc.
    --     another  -- DateTime or string or nil (now)
    --     assert   -- nil, or string with operator
    --                       "lt", "le", "eq", "ne", "ge", "gt",
    --                       "<", "<=", "==", "~=", "<>", ">=", "=>", ">"
    -- Returns:
    --     if assert: true or false
    --     else: -1, 0, 1
    --     nil if invalid
    local base, other, r
    if type( self ) == "table" then
        base  = self
        other = another
    elseif type( another ) == "table" then
        base  = another
        other = self
    end
    if base then
        if type( other ) ~= "table" then
            other = Meta.fiat( other )
        end
        if type( other ) == "table" then
            r = Private.flow( base, other )
            if r  and  type( assert ) == "string" then
                local trsl = { lt     = "<",
                               ["<"]  = "<",
                               le     = "<=",
                               ["<="] = "<=",
                               eq     = "=",
                               ["=="] = "=",
                               ne     = "<>",
                               ["<>"] = "<>",
                               ["~="] = "<>",
                               ge     = ">=",
                               [">="] = ">=",
                               ["=>"] = ">=",
                               gt     = ">",
                               [">"]  = ">" }
                local same = trsl[ assert:lower() ]
                if same then
                    local s = "="
                    if r < 0 then
                        s = "<"
                    elseif r > 0 then
                        s = ">"
                    end
                    r = ( same:find( s, 1, true )  ~=  nil )
                else
                    r = nil
                end
            end
        end
    end
    return r
end -- Prototypes.flow()



Prototypes.format = function ( self, ask, adapt )
    -- Format object as string
    -- Parameter:
    --     self   -- table, with numbers etc.
    --     ask    -- string, format spec, or nil
    --     adapt  -- table, with options, or nil
    --               .lang    -- string, with particular language code
    --               .london  -- true: UTC output; default: local
    --               .lonely  -- true: permit lonely hour
    -- Returns:
    --     string, or false, if invalid
    local r = false
    if type( self ) == "table" then
        local opts  = { lang = self.lang }
        local babel, slang
        if type( adapt ) == "table" then
            if type( adapt.lang ) == "string" then
                local i = adapt.lang:find( "-", 3, true )
                if i then
                    slang = adapt.lang: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 )
        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
    return r
end -- Prototypes.format()



Prototypes.full = function ( self )
    -- Retrieve month name in current language
    -- Parameter:
    --     self  -- table, to be evaluated
    -- Returns:
    --     string, if defined;  false, if not
    local r
    if type( self ) == "table"  and  self.month then
        local slang = ( self.lang or World.slang )
        r = World.monthsLong[ slang ]
        if r then
            r = r[ self.month ]
        end
    else
        r = false
    end
    return r
end -- Prototypes.full()



Prototypes.future = function ( self, add, allocate )
    -- Relative move by interval
    -- Parameter:
    --     self      -- table, to be used as base
    --     add       -- string or number, to be added
    --     allocate  -- true, if a clone shall be returned
    -- Returns:
    --     table, with shift
    local r, raw, rel, shift
    if type( self ) == "table" then
        r     = self
        shift = add
    elseif type( add ) == "table" then
        r     = add
        shift = self
    end
    if r then
        raw = r[ Meta.signature ]
        rel = Private.future( shift )
    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, 4 do
        f( i )
    end -- for i
    r = s
    s = ""
    for i = 5, 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
    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"
    else
        local template = World.templates[ ask ]
        r1 = ask
        if not template then
            local slang = ( adapt.lang or assigned.lang or World.slang )
            local tmp   = World.templates[ slang ]
            if tmp then
                template = tmp[ ask ]
            end
        end
        if type( template ) == "table" then
            local low = ( ask == "ISO" or ask == "ISO-T" )
            r1 = template.spec
            if assigned.year then
                if not assigned.dom then
                    r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" )
                           :gsub( "^&#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" ) then
                    if stamp then
                        r1 = string.format( "%s %s", r1, stamp )
                    end
                end
                if stamp then
                    if low or template.long then
                        local scheme
                        if template.long then
                            scheme = mw.language.getContentLanguage()
                            scheme = scheme.code
                        end
                        r2 = World.zones.formatter( assigned, scheme )
                    end
                end
            end
            if type ( assigned.bc ) == "boolean" then
                local eras = World.era[ adapt.lang ]  or  World.era.en
                local i
                if not r2 then
                    r2 = ""
                end
                if assigned.bc then
                    i = 1
                else
                    i = 2
                end
                r2 = string.format( "%s&#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 )
                if move then
                    move          = ( move - 13 ) * 100
                    assigned.zone = move
                else
                    assigned.zone = false
                end
            else
                local code = World.zones[ s ]
                if not code then
                   local slang = ( assigned.lang or
                                   World.slang )
                   local tmp   = World.zones[ slang ]
                   if tmp then
                       code = tmp[ s ]
                   end
                end
                if code then
                    move          = code
                    assigned.zone = move
                end
            end
        elseif s == "number" then
            move = assigned.zone
        end
    end
    if move then
        local spec = "+-"
        if align then
            if align == "Z" then
                if move % 100 == 0 then
                    r = World.zones[ "!" ]:sub( move / 100 + 13,  1 )
                    spec = false
                end
            elseif align ~= "+-" then
                if move == 0 then
                    r    = " UTC"
                    spec = false
                else
                    local part = World.zones[ align ]
                    if part then
                        for k, v in pairs( part ) do
                            if v == move then
                                r    = string.format( " (%s)", k )
                                spec = false
                                break
                            end
                        end -- for k, v
                    end
                end
            end
        end
        if spec == "+-" then
            if move < 0 then
                spec = "%4.4d"
            else
                spec = "+%4.4d"
            end
            r = string.format( spec, move )
            r = string.format( "%s:%s",
                               r:sub( 1, 3), r:sub( 4 ) )
        end
    end
    return r
end -- World.zones.formatter()



-- Export
local p = { }

function p.test( args, alien )
    local slang = args.lang
    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 nicht erkannt" )
        else
            r = ""
        end
    end
    return r
end -- p.test



function p.failsafe( frame )
    local s = type( frame )
    local r, since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    if since == "wikidata" then
        local item = DateTime.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                 item ) )
            if type( entity ) == "table" then
                local vsn = entity:formatPropertyValues( "P348" )
                if type( vsn ) == "table"  and
                   type( vsn.value) == "string" and
                   vsn.value ~= "" then
                    r = vsn.value
                end
            end
        end
    end
    if since then
        if since > Meta.serial then
            r = ""
        else
            r = Meta.serial
        end
    elseif not r then
        r = Meta.serial
    end
    return r
end -- p.failsafe



function p.format( frame )
    --    1       -- stamp
    --    2       -- spec
    --    lang
    --    shift
    --    noerror
    local l, r
    local v = { frame.args[ 1 ],
                frame.args[ 2 ],
                shift   = frame.args.shift,
                noerror = frame.args.noerror }
    if not v[ 1 ]  or  v[ 1 ] == "now" then
        v[ 1 ]  = frame:callParserFunction( "#timel", "c", v.shift )
        v.shift = false
    end
    Frame  = frame
    l, r = pcall( p.test,  v,  frame.args[ 3 ] or frame.args.lang )
    if not l then
        r = fault( r )
    end
    return r
end -- p.format



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



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

return p