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
(2015-03-16)
(2015-07-15)
Zeile 1: Zeile 1:
--[=[ 2015-03-16
+
local DateTime  -- Date and time utilities
Date and time utilities
+
local Serial    = "2015-07-15"
]=]
+
local Calc      = { }
 
 
 
 
 
 
-- local globals
 
local DateTime
 
 
local Parser    = { }
 
local Parser    = { }
 
local Private    = { }
 
local Private    = { }
 
local Prototypes = { }
 
local Prototypes = { }
 +
local Templates  = { }
 
local World      = { slang      = "en",
 
local World      = { slang      = "en",
 
                     monthsLong  = { },
 
                     monthsLong  = { },
Zeile 17: Zeile 13:
 
local Nbsp      = mw.ustring.char( 160 )
 
local Nbsp      = mw.ustring.char( 160 )
 
local Tab        = mw.ustring.char( 9 )
 
local Tab        = mw.ustring.char( 9 )
 +
local Frame
 
World.era = { en = { "BC", "AD" } }
 
World.era = { en = { "BC", "AD" } }
 
World.monthsAbbr = {  en = { n = 3 }  }
 
World.monthsAbbr = {  en = { n = 3 }  }
Zeile 116: Zeile 113:
 
     return  mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) )
 
     return  mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) )
 
             .. mw.ustring.lower( mw.ustring.sub( a, 2 ) )
 
             .. mw.ustring.lower( mw.ustring.sub( a, 2 ) )
end -- fault()
+
end -- capitalize()
  
  
Zeile 131: Zeile 128:
  
  
DateTime = function ( assign, alien )
+
local function frame()
     -- Create metatable (constructor)
+
    if not Frame then
 +
        Frame = mw.getCurrentFrame()
 +
    end
 +
    return Frame
 +
end -- frame()
 +
 
 +
 
 +
 
 +
local Signature = "__datetime"
 +
local Metatable = {
 +
    __index    = function ( self, access )
 +
                    local r = self[ Signature ][ access ]
 +
                    if r == nil then
 +
                        if access == "serial" then
 +
                            r = Serial
 +
                        elseif access == "suite" then
 +
                            r = "DateTime"
 +
                        else
 +
                            for k, v in pairs( Prototypes ) do
 +
                                if k == access then
 +
                                    r = Prototypes[ k ]
 +
                                end
 +
                            end -- for k, v
 +
                        end
 +
                    end
 +
                    return r
 +
                end,
 +
    __newindex = function ( self, access, assign )
 +
                    if type( access ) == "string" then
 +
                        local data = self[ Signature ]
 +
                        if assign == nil then
 +
                            local val = data[ access ]
 +
                            data[ access ] = nil
 +
                            if not Prototypes.fair( data ) then
 +
                                data[ access ] = val
 +
                            end
 +
                        elseif Prototypes.fair( data,
 +
                                                access,
 +
                                                assign ) then
 +
                            data[ access ] = assign
 +
                        end
 +
                    end
 +
                    return
 +
                end,
 +
    __add      = function ( op1, op2 )
 +
                    return Prototypes.future( op1, op2 )
 +
                end,
 +
    __eq      = function ( op1, op2 )
 +
                    return Prototypes.flow( op1, op2, "eq" )
 +
                end,
 +
    __lt      = function ( op1, op2 )
 +
                    return Prototypes.flow( op1, op2, "lt" )
 +
                end,
 +
    __le      = function ( op1, op2 )
 +
                    return Prototypes.flow( op1, op2, "le" )
 +
                end
 +
}
 +
 
 +
 
 +
 
 +
DateTime = function ( assign, alien, add )
 +
     -- Create object (constructor)
 
     -- Parameter:
 
     -- Parameter:
 
     --    assign  -- string, with initial timestamp, or nil
 
     --    assign  -- string, with initial timestamp, or nil
Zeile 138: Zeile 196:
 
     --                false  -- empty object
 
     --                false  -- empty object
 
     --    alien  -- string, with language code, or nil
 
     --    alien  -- string, with language code, or nil
 +
    --    add    -- string, with interval (PHP strtotime), or nil
 
     -- Returns:
 
     -- Returns:
 
     --    table, as DateTime object
 
     --    table, as DateTime object
Zeile 143: Zeile 202:
 
     local r
 
     local r
 
     Private.foreign()
 
     Private.foreign()
     r = Private.factory( assign, alien )
+
     r = Private.factory( assign, alien, add )
 
     if type( r ) == "table" then
 
     if type( r ) == "table" then
        local meta = { }
+
         r = { [ Signature ] = r }
        local s    = "__datetime"
+
         setmetatable( r, Metatable )
        meta.__index    = function( self, access )
 
                              return self[ s ][ access ]
 
                          end
 
        meta.__newindex = function( self, access, assign )
 
                              if type( access ) == "string" then
 
                                  local data = self[ s ]
 
                                  if assign == nil then
 
                                      local val = data[ access ]
 
                                      data[ access ] = nil
 
                                      if not Prototypes.fair( data ) then
 
                                          data[ access ] = val
 
                                      end
 
                                  elseif Prototypes.fair( data,
 
                                                          access,
 
                                                          assign ) then
 
                                      data[ access ] = assign
 
                                  end
 
                              end
 
                              return
 
                          end
 
         r               = { [ s ] = r }
 
        r.fair          = function ( ... )
 
                              return Prototypes.fair( ... )
 
                          end
 
        r.figure        = function ( ... )
 
                              return Prototypes.figure( ... )
 
                          end
 
        r.first        = function ( ... )
 
                              return Prototypes.first( ... )
 
                          end
 
        r.format        = function ( ... )
 
                              return Prototypes.format( ... )
 
                          end
 
        r.full          = function ( ... )
 
                              return Prototypes.full( ... )
 
                          end
 
         setmetatable( r, meta )
 
 
     end
 
     end
 
     return r
 
     return r
Zeile 190: Zeile 212:
  
  
Parser.digitsHeading = function ( analyse, alone, amount, add )
+
Calc.months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
    -- String analysis, if digits only or at least 4 digits heading
+
 
 +
 
 +
 
 +
--  Calc.fast = function ( at )
 +
--      -- Quick scan of full ISO stamp
 +
--      -- Parameter:
 +
--      --    apply  -- string, ISO
 +
--      -- Returns:
 +
--      --    table, with numeric components
 +
--      local r = { }
 +
--      r.year  = tonumber( at:sub(  1, 4 ) )
 +
--      r.month = tonumber( at:sub(  6, 2 ) )
 +
--      r.dom  = tonumber( at:sub(  9, 2 ) )
 +
--      r.hour  = tonumber( at:sub( 12, 2 ) )
 +
--      r.min  = tonumber( at:sub( 14, 2 ) )
 +
--     r.sec  = tonumber( at:sub( 17, 2 ) )
 +
--      if at:sub( 19, 1 ) == "." then
 +
--          r.msec = tonumber( at:sub( 20, 3 ) )
 +
--          if #at > 22 then
 +
--              r.mysec = tonumber( at:sub( 23, 3 ) )
 +
--          end
 +
--      end
 +
--      return r
 +
--  end -- Calc.fast()
 +
 
 +
 
 +
 
 +
Calc.fair = function ( adjust )
 +
    -- Normalize numeric components
 
     -- Parameter:
 
     -- Parameter:
     --    analyse -- string to be scanned, starting with digit
+
     --    adjust -- table, with raw numbers
     --                digits only, else starting with exactly 4 digits
+
     local order  = { "mysec", "msec", "sec", "min", "hour",
     --     alone    -- true, if only digits
+
                    "dom", "month", "year" }
     --    amount  -- number of heading digits
+
    local ranges = { year  = { min = -999,
     --    add     -- table, to be extended
+
                              max = 9999 },
 +
                    month = { min =  1,
 +
                              max = 12,
 +
                              mod = 12 },
 +
                    dom  = { min =  1,
 +
                              max = 28 },
 +
                    hour  = { mod = 24 },
 +
                    min  = { mod = 60 },
 +
                    sec  = { mod = 60 },
 +
                    msec  = { mod = 1000 },
 +
                    mysec = { mod = 1000 } }
 +
    local m, max, min, move, n, range
 +
    for k, v in pairs( order ) do
 +
        n = adjust[ v ]
 +
        if n or move then
 +
            range = ranges[ v ]
 +
            min  = range.min or 0
 +
            max  = range.max  or  ( range.mod - 1 )
 +
            if move then
 +
                n    = ( n or 0 )  +  move
 +
                move = false
 +
            end
 +
            if n < min  or  n > max then
 +
                if range.mod then
 +
                    m    = n % range.mod
 +
                    move = ( n - m )  /  range.mod
 +
                    n    = min + m
 +
                 else   -- dom
 +
                    if adjust.month and adjust.year  and  n > 1  and
 +
                      adjust.month >= 1  and
 +
                      adjust.month <= 12 and
 +
                      adjust.year > 1900 then
 +
                        max = Calc.months[ adjust.month ]
 +
                        if adjust.month == 2  and
 +
                          ( adjust.year % 4 ~= 0  or
 +
                            adjust.year % 400 == 0 ) then
 +
                            max = 28
 +
                        end
 +
                        if n <= max then
 +
                            max = false
 +
                        end
 +
                    end
 +
                    if max then
 +
                        m    = n % 30
 +
                        move = ( n - m )  /  30
 +
                        n    = 1 + m
 +
                    end
 +
                end
 +
            end
 +
            adjust[ v ] = n
 +
        end
 +
     end -- for k, v
 +
end -- Calc.fair()
 +
 
 +
 
 +
 
 +
Calc.future = function ( add )
 +
     -- Parse move interval
 +
     -- Parameter:
 +
     --    add   -- string, with GNU relative items
 
     -- Returns:
 
     -- Returns:
     --    table, extended if parsed
+
     --    table, with numeric components, or false
     --    false, if invalid text format
+
     local r, token
     local r = add
+
     local units = { year      = true,
     if alone then
+
                    month     = true,
        -- digits only
+
                    fortnight = { slot = "dom", mult = 14 },
        if amount <= 4 then
+
                    week      = { slot = "dom", mult = 7 },
            r.year = tonumber( analyse )
+
                    dom      = true,
        elseif n == 14 then
+
                    hour      = true,
            -- timestamp
+
                    min      = true,
            r.year  = tonumber( analyse:sub( 1, 4 ) )
+
                    sec      = true }
            r.month  = tonumber( analyse:sub( 5, 2 ) )
+
    local story = string.format( " %s ", add:lower() )
            r.dom    = tonumber( analyse:sub( 7, 2 ) )
+
                        :gsub( "%s+", " " )
            r.hour   = tonumber( analyse:sub( 9, 2 ) )
+
                        :gsub( " yesterday ", " -1 dom " )
            r.min    = tonumber( analyse:sub( 11, 2 ) )
+
                        :gsub( " tomorrow ",   " 1 dom " )
             r.sec    = tonumber( analyse:sub( 13, 2 ) )
+
                        :gsub( "(%l)s ", "%1 " )
 +
                        :gsub( " day ",    " dom " )
 +
                        :gsub( " minute ", " min " )
 +
                        :gsub( " second ", " sec " )
 +
    local feed  = function ()
 +
                      local slice
 +
                      token, slice = story:match( "^( (%S+)) " )
 +
                      return slice
 +
                  end
 +
    local fed   = function ()
 +
                      story = story:sub( #token + 1 )
 +
                  end
 +
    local m, n, s, u
 +
    while true do
 +
        s = feed()
 +
        if s then
 +
            n = 1
 +
            if s:match( "^[+-]?%d+$" ) then
 +
                n = tonumber( s )
 +
                fed()
 +
                s = feed()
 +
             end
 +
            if s then
 +
                u = units[ s ]
 +
            end
 +
            if s and u then
 +
                fed()
 +
                if u ~= true then
 +
                    s = u.slot
 +
                    n = n * u.mult
 +
                end
 +
                if feed() == "ago" then
 +
                    if n > 0 then
 +
                        n = - n
 +
                    end
 +
                    fed()
 +
                end
 +
                r      = r  or  { }
 +
                r[ s ] = ( r[ s ] or 0 ) +  n
 +
            else
 +
                r = false
 +
                break    -- while true
 +
            end
 
         else
 
         else
             r = false
+
             break    -- while true
 
         end
 
         end
     elseif amount == 4 then
+
     end    -- while true
        local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" )
+
    return r
        r.year = tonumber( s )
+
end -- Calc.future()
        if sep == "-" then
+
 
            -- ISO
+
 
            s, sep, sx = sx:match( "^(%d%d)(-?)(.*)$" )
+
 
            if s then
+
Parser.digitsHeading = function ( analyse, alone, amount, add )
                r.month  = tonumber( s )
+
    -- String analysis, if digits only or at least 4 digits heading
                r.month2 = true
+
    -- Parameter:
                if sep == "-" then
+
    --    analyse  -- string to be scanned, starting with digit
                    s, sep, sx = sx:match( "^(%d%d?)([ T]?)(.*)$" )
+
    --                 digits only, else starting with exactly 4 digits
                    if s then
+
    --    alone    -- true, if only digits
                        r.dom = tonumber( s )
+
    --    amount  -- number of heading digits
                        if sep == "T" then
+
    --    add      -- table, to be extended
                            r.month2 = nil
+
    -- Returns:
                        else
+
    --     table, extended if parsed
                            r.dom2 = ( #s == 2 )
+
    --    false, if invalid text format
                        end
+
    local r = add
                        if sep then
+
    if alone then
                            r = Parser.time( sx, r, sep == "T" )
+
        -- digits only
                        end
+
        if amount <= 4 then
                    else
+
            r.year = tonumber( analyse )
                        r = false
+
        elseif n == 14 then
                    end
+
            -- timestamp
                elseif sx and sx ~= "" then
+
            r.year  = tonumber( analyse:sub(  1, 4 ) )
                    r = false
+
            r.month  = tonumber( analyse:sub(  5, 2 ) )
                end
+
            r.dom    = tonumber( analyse:sub7, 2 ) )
             else
+
             r.hour  = tonumber( analyse:sub( 9, 2 ) )
                r = false
+
             r.min    = tonumber( analyse:sub( 11, 2 ) )
            end
+
            r.sec    = tonumber( analyse:sub( 13, 2 ) )
        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
 
         else
 
             r = false
 
             r = false
 
         end
 
         end
     elseif amount == 8 then
+
     elseif amount == 4 then
        -- ISO compact
+
         local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" )
         local s, sz = analyse:match( "^%d+T(%d+)([.+-]?%d*%a*)$" )
+
         r.year = tonumber( s )
         if s then
+
        if sep == "-" then
            local n = #s
+
            -- ISO
            if n == 2  or  n == 4  or  n == 6 then
+
            s, sep, sx = sx:match( "^(%d%d)(-?)(.*)$" )
                r.year = tonumber( analyse:sub(  1,  4 ) )
+
            if s then
                r.month = tonumber( analyse:sub(  5,  6 ) )
+
                r.month  = tonumber( s )
                r.dom  = tonumber( analyse:sub(  7,  8 ) )
+
                r.month2 = true
                r.hour  = tonumber( analyse:sub( 10, 11 ) )
+
                 if sep == "-" then
                if n > 2 then
+
                     s, sep, sx = sx:match( "^(%d%d?)([ T]?)(.*)$" )
                    r.min = tonumber( s:sub( 3, 4 ) )
+
                     if s then
                    if n == 6 then
+
                        r.dom = tonumber( s )
                        r.sec = tonumber( s:sub( 5, 6 ) )
+
                         if sep == "T" then
                    end
+
                             r.month2 = nil
                    n, s = sz:match( "^(%.%d+)([+-]?[%a%d]*)$" )
 
                    if n then
 
                        n      = n .. "00"
 
                        r.msec = tonumber( n:sub( 1, 3 ) )
 
                        sz    = s
 
                    end
 
                end
 
                 if sz ~= "" then
 
                     s, sz = sz:match( "^([+-]?)(%a*)$" )
 
                     if s == "" then
 
                         if sz:match( "^(%u)$" ) then
 
                             r.zone = sz
 
 
                         else
 
                         else
                             s = false
+
                             r.dom2 = ( #s == 2 )
 +
                        end
 +
                        if sep then
 +
                            r = Parser.time( sx,  r,  sep == "T" )
 
                         end
 
                         end
                    elseif #s == 1 then
 
                        r.zone = s .. sz
 
 
                     else
 
                     else
                         s = false
+
                         r = false
 
                     end
 
                     end
 +
                elseif sx and sx ~= "" then
 +
                    r = false
 
                 end
 
                 end
 
             else
 
             else
                 s = false
+
                 r = false
 
             end
 
             end
 +
        elseif sep:lower() == "w" then
 +
            if sx then
 +
                s = sx:match( "^(%d%d?)$" )
 +
                if s then
 +
                    r.week = tonumber( s )
 +
                    if r.week < 1  or  r.week > 53 then
 +
                        r = false
 +
                    end
 +
                else
 +
                    r = false
 +
                end
 +
            else
 +
                r = false
 +
            end
 +
        else
 +
            r = false
 +
        end
 +
        if r then
 +
            r.iso = true
 
         end
 
         end
 +
    elseif amount == 8 then
 +
        -- ISO compact
 +
        local s, sz = analyse:match( "^%d+T(%d+)([.+-]?%d*%a*)$" )
 
         if s then
 
         if s then
             r = false
+
             local n = #s
        end
+
            if n == 2  or  n == 4  or  n == 6 then
    end
+
                r.year  = tonumber( analyse:sub( 1,  4 ) )
    return r
+
                r.month = tonumber( analyse:sub(  5,  6 ) )
end -- Parser.digitsHeading()
+
                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 ) )
Parser.eraGermanEnglish = function ( analyse )
+
                    if n == 6 then
    -- String analysis, for German and English era
+
                        r.sec = tonumber( s:sub( 5, 6 ) )
    -- v. Chrv. u. Z.  n. Chr.  AD BC  A.D. B.C. B.C.E.
+
                    end
    -- Parameter:
+
                    n, s = sz:match( "^(%.%d+)([+-]?[%a%d]*)$" )
    --    analyse -- string
+
                    if n then
    -- Returns:
+
                        n      = n .. "00"
    --    1 -- table, with boolean era, if any
+
                        r.msec = tonumber( n:sub( 1, 3 ) )
    --    2 -- string, with era stripped off, if any
+
                        if #n >= 6 then
    local rO = { }
+
                            r.mysec = tonumber( n:sub( 4, 6 ) )
    local rS = analyse
+
                        end
    local s, switch = analyse:match( "^(.+) ([vn])%. ?Chr%.$" )
+
                        sz = s
    if switch then
+
                    end
        rS    = s
+
                end
        rO.bc = ( switch == "v" )
+
                if sz ~= "" then
    elseif analyse:find( " v%. ?u%. ?Z%.$" ) then
+
                    s, sz = sz:match( "^([+-]?)(%a*)$" )
        rS    = analyse:match( "^(.+) v%. ?u%. ?Z%.$" )
+
                    if s == "" then
        rO.bc = true
+
                        if sz:match( "^(%u)$" ) then
    elseif analyse:find( " B%.? ?C%.? ?E?%.?$" ) then
+
                            r.zone = sz
        rS    = analyse:match( "^(.+) B%.? ?C%.? ?E?%.?$" )
+
                        else
        rO.bc = true
+
                            s = false
    elseif analyse:find( "^A%.? ?D%.? " ) then
+
                        end
        rS    = analyse:match( "^A%.? ?D%.? (.*)$" )
+
                    elseif #s == 1 then
         rO.bc = false
+
                        r.zone = s .. sz
     end
+
                    else
     return rO, rS
+
                        s = false
end -- Parser.eraGermanEnglish()
+
                    end
 +
                end
 +
            else
 +
                s = false
 +
            end
 +
        end
 +
         if s then
 +
            r = false
 +
        end
 +
     end
 +
     return r
 +
end -- Parser.digitsHeading()
  
  
  
Parser.european = function ( ahead, adhere, analyse, assign )
+
Parser.eraGermanEnglish = function ( analyse )
     -- String analysis, retrieve date style: DOM MONTH YEAR
+
     -- String analysis, for German and English era
 +
    -- v. Chr.  v. u. Z.  n. Chr.  AD BC  A.D. B.C. B.C.E.
 
     -- Parameter:
 
     -- Parameter:
    --    ahead    -- string, with first digits, not more than 2
+
     --    analyse  -- string
    --    adhere  -- string, with first separator; not ":"
 
     --    analyse  -- string, remainder following adhere
 
    --    assign  -- table
 
 
     -- Returns:
 
     -- Returns:
     --    table, extended if parsed
+
     --    1  -- table, with boolean era, if any
     local r = assign
+
    --    2  -- string, with era stripped off, if any
     local s, s2, sx
+
    local rO = { }
     if adhere == "." or  adhere == ". " then
+
     local rS = analyse
         -- 23.12.2013
+
     local s, switch = analyse:match( "^(.+) ([vn])%. ?Chr%.$" )
         -- 23. Dezember 2013
+
     if switch then
         s, sx = analyse:match( "^(%d%d?)%.(.*)$" )
+
        rS    = s
         if s then
+
        rO.bc = ( switch == "v" )
            r = Parser.putDate( false, s, ahead, assign )
+
    elseif analyse:find( " v%. ?u%. ?Z%.$" ) then
            r = Parser.yearTime( sx, r )
+
         rS    = analyse:match( "^(.+) v%. ?u%. ?Z%.$" )
         else
+
         rO.bc = true
            s, sx = mw.ustring.match( analyse,
+
    elseif analyse:find( " B%.? ?C%.? ?E?%.?$" ) then
                                      "^ ?([%a&;]+%.?) ?(.*)$" )
+
         rS    = analyse:match( "^(.+) B%.? ?C%.? ?E?%.?$" )
            if s then
+
         rO.bc = true
                local n = Parser.monthNumber( s )
+
    elseif analyse:find( "^A%.? ?D%.? " ) then
                if n then
+
         rS    = analyse:match( "^A%.? ?D%.? (.*)$" )
                    r.month = n
+
        rO.bc = false
                    r.dom  = tonumber( ahead )
+
    end
                    r.dom2  = ( #ahead == 2 )
+
    return rO, rS
                    r      = Parser.yearTime( sx, r )
+
end -- Parser.eraGermanEnglish()
                else
+
 
                    r = false
+
 
                end
+
 
            else
+
Parser.european = function ( ahead, adhere, analyse, assign )
                r = false
+
    -- String analysis, retrieve date style: DOM MONTH YEAR
            end
+
    -- Parameter:
        end
+
    --    ahead   -- string, with first digits, not more than 2
     elseif adhere == " " then
+
    --    adhere  -- string, with first separator; not ":"
         -- 23 Dec 2013
+
    --    analyse  -- string, remainder following adhere
         s, sx = mw.ustring.match( analyse,
+
    --    assign  -- table
                                  "^([%a&;]+%.?) ?(.*)$" )
+
    -- Returns:
 +
    --    table, extended if parsed
 +
    local r = assign
 +
    local s, s2, sx
 +
     if adhere == "."  or  adhere == ". " then
 +
         -- 23.12.2013
 +
        -- 23. Dezember 2013
 +
         s, sx = analyse:match( "^(%d%d?)%.(.*)$" )
 
         if s then
 
         if s then
             local n = Parser.monthNumber( s )
+
             r = Parser.putDate( false, s, ahead, assign )
             if n then
+
             r = Parser.yearTime( sx, r )
                r.month = n
 
                r.dom  = tonumber( ahead )
 
                r.dom2  = ( #ahead == 2 )
 
                r      = Parser.yearTime( sx, r )
 
            else
 
                r = false
 
            end
 
 
         else
 
         else
             r = false
+
             s, sx = mw.ustring.match( analyse,
 +
                                      "^ ?([%a&;]+%.?) ?(.*)$" )
 +
            if s then
 +
                local n = Parser.monthNumber( s )
 +
                if n then
 +
                    r.month = n
 +
                    r.dom  = tonumber( ahead )
 +
                    r.dom2  = ( #ahead == 2 )
 +
                    r      = Parser.yearTime( sx, r )
 +
                else
 +
                    r = false
 +
                end
 +
            else
 +
                r = false
 +
            end
 
         end
 
         end
     else
+
    elseif adhere == " " then
         r = false
+
        -- 23 Dec 2013
     end
+
        s, sx = mw.ustring.match( analyse,
     return r
+
                                  "^([%a&;]+%.?) ?(.*)$" )
end -- Parser.european()
+
        if s then
 
+
            local n = Parser.monthNumber( s )
 +
            if n then
 +
                r.month = n
 +
                r.dom  = tonumber( ahead )
 +
                r.dom2  = ( #ahead == 2 )
 +
                r      = Parser.yearTime( sx, r )
 +
            else
 +
                r = false
 +
            end
 +
        else
 +
            r = false
 +
        end
 +
     else
 +
         r = false
 +
     end
 +
     return r
 +
end -- Parser.european()
 +
 
  
  
Zeile 461: Zeile 651:
 
         rO = false
 
         rO = false
 
     end
 
     end
     if not rO then
+
     if rO then
 +
        rO.iso = true
 +
    else
 
         rS = false
 
         rS = false
 
     end
 
     end
Zeile 640: Zeile 832:
 
                 s = sx:match( "^%.(%d+)$" )
 
                 s = sx:match( "^%.(%d+)$" )
 
                 if s then
 
                 if s then
                     r.msec = tonumber( s )
+
                    s  = s .. "00"
 +
                     r.msec = tonumber( s:sub( 1, 3 ) )
 +
                    if #s >= 6 then
 +
                        r.mysec = tonumber( s:sub( 4, 6 ) )
 +
                    end
 
                 else
 
                 else
 
                     r = false
 
                     r = false
Zeile 861: Zeile 1.057:
  
  
Private.factory = function ( assign, alien )
+
Private.factory = function ( assign, alien, add )
 
     -- Create DateTime table (constructor)
 
     -- Create DateTime table (constructor)
 
     -- Parameter:
 
     -- Parameter:
Zeile 868: Zeile 1.064:
 
     --                false  -- empty object
 
     --                false  -- empty object
 
     --    alien  -- string, with language code, or nil
 
     --    alien  -- string, with language code, or nil
 +
    --    add    -- string, with interval (PHP strtotime), or nil
 
     -- Returns:
 
     -- Returns:
 
     --    table, for DateTime object
 
     --    table, for DateTime object
 
     --    string or false, if failed
 
     --    string or false, if failed
 
     local l    = true
 
     local l    = true
    local r    = false
 
 
     local slang = mw.text.trim( alien or World.slang or "en" )
 
     local slang = mw.text.trim( alien or World.slang or "en" )
 +
    local r
 
     if assign == false then
 
     if assign == false then
 
         r = { }
 
         r = { }
 
     else
 
     else
 
         local stamp = ( assign or "now" )
 
         local stamp = ( assign or "now" )
 +
        local shift
 +
        if add then
 +
            shift = Private.future( add )
 +
        end
 +
        r = false
 
         if stamp == "now" then
 
         if stamp == "now" then
             stamp = mw.getCurrentFrame():callParserFunction( "#timel",
+
             stamp = frame():callParserFunction( "#timel", "c", shift )
                                                            "c" )
+
            shift = false
 
         else
 
         else
 
             local seconds = stamp:match( "^#(%d+)$" )
 
             local seconds = stamp:match( "^#(%d+)$" )
Zeile 888: Zeile 1.090:
 
             end
 
             end
 
         end
 
         end
         l, r = pcall( Private.fetch, stamp, slang )
+
         l, r = pcall( Private.fetch, stamp, slang, shift )
 
     end
 
     end
 
     if l  and  type( r ) == "table" then
 
     if l  and  type( r ) == "table" then
Zeile 900: Zeile 1.102:
  
  
Private.fetch = function ( analyse, alien )
+
Private.fetch = function ( analyse, alien, add )
 
     -- Retrieve object from string
 
     -- Retrieve object from string
 
     -- Parameter:
 
     -- Parameter:
 
     --    analyse  -- string to be interpreted
 
     --    analyse  -- string to be interpreted
 
     --    alien    -- string with language code, or nil
 
     --    alien    -- string with language code, or nil
 +
    --    add      -- string, with interval (PHP strtotime), or nil
 
     -- Returns:
 
     -- Returns:
 
     --    table, if parsed
 
     --    table, if parsed
Zeile 940: Zeile 1.143:
 
                     if not Prototypes.fair( r ) then
 
                     if not Prototypes.fair( r ) then
 
                         r = false
 
                         r = false
 +
                    elseif add then
 +
                        r = Prototypes.future( r, add )
 
                     end
 
                     end
 
                 end
 
                 end
Zeile 954: Zeile 1.159:
  
  
Private.foreign = function ()
+
Private.flow = function ( at1, at2 )
     -- Retrieve localization submodule
+
    -- Compare two objects
     if not World.localization then
+
    -- Parameter:
 +
    --    at1  -- DateTime
 +
    --    at2  -- DateTime
 +
    -- Returns:
 +
    --    -1, 0, 1 or nil if not comparable
 +
    local r = 0
 +
    if at1.bc or at2.bc  and  at1.bc ~= at2.bc then
 +
        if at1.bc then
 +
            r = -1
 +
        else
 +
            r = 1
 +
        end
 +
    else
 +
        local order = { "year", "month", "week", "dom",
 +
                        "hour", "min", "sec", "msec", "mysec" }
 +
        local life  = false
 +
        local s, v1, v2
 +
        for i = 1, 9 do
 +
            s  = order[ i ]
 +
            v1 = at1[ s ]
 +
            v2 = at2[ s ]
 +
            if v1 or v2 then
 +
                if v1 and v2 then
 +
                    if v1 < v2 then
 +
                        r = -1
 +
                    elseif v1 > v2 then
 +
                        r = 1
 +
                    end
 +
                elseif life then
 +
                    if v2 then
 +
                        r = -1
 +
                    else
 +
                        r = 1
 +
                    end
 +
                else
 +
                    r = nil
 +
                end
 +
                if r ~= 0 then
 +
                    if at1.bc and r then
 +
                        r = r * -1
 +
                    end
 +
                    break    -- for i
 +
                end
 +
                life = true
 +
            end
 +
        end -- for i
 +
    end
 +
    return r
 +
end -- Private.flow()
 +
 
 +
 
 +
 
 +
Private.foreign = function ()
 +
     -- Retrieve localization submodule
 +
     if not World.localization then
 
         local l, d = pcall( mw.loadData, "Module:DateTime/local" )
 
         local l, d = pcall( mw.loadData, "Module:DateTime/local" )
 
         if l then
 
         if l then
Zeile 980: Zeile 1.239:
  
  
Prototypes.fair = function ( self, access, assign )
+
Private.future = function ( add )
     -- Check formal validity of table
+
     -- Normalize move interval
 
     -- Parameter:
 
     -- Parameter:
     --    self    -- table, to be checked
+
     --    add  -- string or number, to be added
    --    access  -- string or nil, single item to be checked
 
    --    assign  -- single access value to be checked
 
 
     -- Returns:
 
     -- Returns:
     --    true, if valid;  false, if not
+
    --    string, with shift, or false/nil
     local r = ( type( self ) == "table" )
+
    local r
 +
    if add then
 +
        local s = type( add )
 +
        if s == "string"  and  add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then
 +
            r = tonumber( add )
 +
            s = "number"
 +
        else
 +
            r = add
 +
        end
 +
        if s == "number" then
 +
            if r == 0 then
 +
                r = false
 +
            else
 +
                r = string.format( "%d second", r )
 +
            end
 +
        elseif s ~= "string" then
 +
            r = false
 +
        end
 +
        if r then
 +
            r = Calc.future( r )
 +
        end
 +
    end
 +
    return r
 +
end -- Private.future()
 +
 
 +
 
 +
 
 +
Prototypes.fair = function ( self, access, assign )
 +
    -- Check formal validity of table
 +
    -- Parameter:
 +
    --    self    -- table, to be checked
 +
    --    access  -- string or nil, single item to be checked
 +
    --    assign  -- single access value to be checked
 +
    -- Returns:
 +
     --    true, if valid;  false, if not
 +
     local r = ( type( self ) == "table" )
 
     if r then
 
     if r then
 
         local defs = { year  = { max = MaxYear },
 
         local defs = { year  = { max = MaxYear },
 
                       month = { min =  1,
 
                       month = { min =  1,
 
                                 max = 12 },
 
                                 max = 12 },
 +
                      week  = { min =  1,
 +
                                max = 53 },
 
                       dom  = { min =  1,
 
                       dom  = { min =  1,
 
                                 max = 31 },
 
                                 max = 31 },
Zeile 998: Zeile 1.292:
 
                       min  = { max = 59 },
 
                       min  = { max = 59 },
 
                       sec  = { max = 61 },
 
                       sec  = { max = 61 },
                       msec  = { max = 1000 }
+
                       msec  = { max = 999 },
 +
                      mysec = { max = 999 }
 
         }
 
         }
        local months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
 
 
         local fNum =
 
         local fNum =
 
             function ( k, v )
 
             function ( k, v )
Zeile 1.044: Zeile 1.338:
 
                         end
 
                         end
 
                         if m then
 
                         if m then
                             ret = ( d <= months[ m ] )
+
                             ret = ( d <= Calc.months[ m ] )
 
                             if ret then
 
                             if ret then
 
                                 local y
 
                                 local y
Zeile 1.105: Zeile 1.399:
 
             end
 
             end
 
         else
 
         else
             local order = { "bc", "year", "month", "dom",
+
             local order = { "bc", "year", "month", "week", "dom",
                             "hour", "min", "sec", "msec" }
+
                             "hour", "min", "sec", "msec", "mysec" }
             local life = false
+
             local life = false
             local leak = false
+
             local leak = false
 
             local s, v
 
             local s, v
             for i = 1, 8 do
+
             for i = 1, 10 do
 
                 s = order[ i ]
 
                 s = order[ i ]
 
                 v = self[ s ]
 
                 v = self[ s ]
Zeile 1.121: Zeile 1.415:
 
                         if not fNum( s, v ) then
 
                         if not fNum( s, v ) then
 
                             r = false
 
                             r = false
                             break
+
                             break   -- for i
 
                         end
 
                         end
 
                         life = true
 
                         life = true
 
                         leak = true
 
                         leak = true
 
                     end
 
                     end
                 else
+
                 elseif i == 3 then
 +
                    if not self.week then
 +
                        life = false
 +
                    end
 +
                elseif i ~= 4 then
 
                     life = false
 
                     life = false
 
                 end
 
                 end
 
             end -- for i
 
             end -- for i
 +
            if self.week  and  ( self.month or self.dom ) then
 +
                r = false
 +
            end
 
         end
 
         end
 
     end
 
     end
Zeile 1.191: Zeile 1.492:
  
  
Prototypes.format = function ( self, ask, adapt )
+
Prototypes.flow = function ( self, another, assert )
     -- Format object as string
+
    -- Compare this object with another timestamp
 +
    -- Parameter:
 +
    --    self    -- table, with numbers etc.
 +
    --    another  -- DateTime or string or nil (now)
 +
    --    assert  -- nil, or string with operator
 +
    --                      "lt", "le", "eq", "ne", "ge", "gt",
 +
    --                      "<", "<=", "==", "~=", "<>", ">=", "=>", ">"
 +
    -- Returns:
 +
    --    if assert: true or false
 +
    --    else: -1, 0, 1
 +
    --    nil if invalid
 +
    local r
 +
    if type( self ) == "table" then
 +
        local d2
 +
        if type( another ) == "table" then
 +
            d2 = another
 +
        else
 +
            d2 = DateTime( another )
 +
        end
 +
        if type( d2 ) == "table" then
 +
            r = Private.flow( self, d2 )
 +
            if r  and  type( assert ) == "string" then
 +
                local trsl = { lt    = "<",
 +
                              ["<"]  = "<",
 +
                              le    = "<=",
 +
                              ["<="] = "<=",
 +
                              eq    = "=",
 +
                              ["=="] = "=",
 +
                              ne    = "<>",
 +
                              ["<>"] = "<>",
 +
                              ["~="] = "<>",
 +
                              ge    = ">=",
 +
                              [">="] = ">=",
 +
                              ["=>"] = ">=",
 +
                              gt    = ">",
 +
                              [">"]  = ">" }
 +
                local same = trsl[ assert:lower() ]
 +
                if same then
 +
                    local s = "="
 +
                    if r < 0 then
 +
                        s = "<"
 +
                    elseif r > 0 then
 +
                        s = ">"
 +
                    end
 +
                    r = ( same:find( s, 1, true )  ~=  nil )
 +
                else
 +
                    r = nil
 +
                end
 +
            end
 +
        end
 +
    end
 +
    return r
 +
end -- Prototypes.flow()
 +
 
 +
 
 +
 
 +
Prototypes.format = function ( self, ask, adapt )
 +
     -- Format object as string
 
     -- Parameter:
 
     -- Parameter:
 
     --    self  -- table, with numbers etc.
 
     --    self  -- table, with numbers etc.
Zeile 1.198: Zeile 1.556:
 
     --    adapt  -- table, with options, or nil
 
     --    adapt  -- table, with options, or nil
 
     --              .lang    -- string, with particular language code
 
     --              .lang    -- string, with particular language code
 +
    --              .london  -- true: UTC output; default: local
 
     --              .lonely  -- true: permit lonely hour
 
     --              .lonely  -- true: permit lonely hour
 
     -- Returns:
 
     -- Returns:
Zeile 1.249: Zeile 1.608:
 
                                               stamp, self.sec )
 
                                               stamp, self.sec )
 
                         if self.msec then
 
                         if self.msec then
                             stamp = string.format( "%s.%d",
+
                             stamp = string.format( "%s.%03d",
 
                                                   stamp, self.msec )
 
                                                   stamp, self.msec )
 +
                            if self.mysec then
 +
                                stamp = string.format( "%s%03d",
 +
                                                      stamp,
 +
                                                      self.mysec )
 +
                            end
 
                         end
 
                         end
 
                     end
 
                     end
Zeile 1.349: Zeile 1.713:
  
  
World.templates.formatter = function ( assigned, ask, adapt )
+
Prototypes.future = function ( self, add )
     -- Retrieve format specification string
+
     -- Relative move by interval
 
     -- Parameter:
 
     -- Parameter:
     --    assigned -- table, with numbers etc.
+
     --    self -- table, to be used as base
     --    ask      -- string, format spec, or nil
+
     --    add  -- string or number, to be added
    --    adapt    -- table, with options
 
    --                  .lang    -- string, with particular language code
 
    --                  .lonely  -- true: permit lonely hour
 
 
     -- Returns:
 
     -- Returns:
     --    1  -- string
+
     --    table, with shift
     --     2  -- string or nil; append suffix (zone)
+
     local r, raw, rel, shift
     local r1, r2
+
     if type( self ) == "table" then
     if not ask  or  ask == "" then
+
        r     = self
         r1 = "c"
+
        shift = add       
     else
+
     elseif type( add ) == "table" then
         local template = World.templates[ ask ]
+
         r    = add
         r1 = ask
+
        shift = self       
        if not template then
+
    end
             local slang = ( adapt.lang or assigned.lang or World.slang )
+
     if r then
            local tmp  = World.templates[ slang ]
+
         raw = r[ Signature ]
            if tmp then
+
         rel = Private.future( shift )
                template = tmp[ ask ]
+
    end
             end
+
    if raw and rel then
 +
        for k, v in pairs( rel ) do
 +
             raw[ k ] = ( raw[ k ] or 0 )  +  v
 +
        end -- for k, v
 +
        Calc.fair( raw )
 +
    end
 +
    return r
 +
end -- Prototypes.future()
 +
 
 +
 
 +
 
 +
Templates.flow = function ( frame, action )
 +
    -- Comparison invokation
 +
    -- Parameter:
 +
    --    frame  -- object
 +
    -- Returns:
 +
    --    string, either "" or "1"
 +
    local r
 +
    local s1 = frame.args[ 1 ]
 +
    local s2 = frame.args[ 2 ]
 +
    if s1 then
 +
        s1 = mw.text.trim( s1 )
 +
        if s1 == "" then
 +
             s1 = false
 
         end
 
         end
         if type( template ) == "table" then
+
    end
             local low = ( ask == "ISO" or ask == "ISO-T" )
+
    if s2 then
            r1 = template.spec
+
         s2 = mw.text.trim( s2 )
            if assigned.year then
+
        if s2 == "" then
                if not assigned.dom then
+
             s2 = false
                    r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" )
+
        end
                          :gsub( "^&#160;", "" )
+
    end
                    if not assigned.month then
+
    if s1 or s2 then
                        r1 = r1:gsub( "[ .%-]?[FmMnt][ .%-]*", "" )
+
        local l
                    end
+
        Frame = frame
                end
+
        l, r = pcall( Prototypes.flow,
             else
+
                      DateTime( s1 ), s2, action )
                r1 = r1:gsub( " ?[yY] ?", "" )
+
        if r == true then
 +
            r = "1"
 +
        end
 +
    end
 +
    return r or ""
 +
end -- Templates.flow()
 +
 
 +
 
 +
 
 +
World.templates.formatter = function ( assigned, ask, adapt )
 +
    -- Retrieve format specification string
 +
    -- Parameter:
 +
    --    assigned  -- table, with numbers etc.
 +
    --    ask      -- string, format spec, or nil
 +
    --    adapt    -- table, with options
 +
    --                  .lang    -- string, with particular language code
 +
    --                  .lonely  -- true: permit lonely hour
 +
    -- Returns:
 +
    --    1  -- string
 +
    --    2  -- string or nil; append suffix (zone)
 +
    local r1, r2
 +
    if not ask  or  ask == "" then
 +
        r1 = "c"
 +
    else
 +
        local template = World.templates[ ask ]
 +
        r1 = ask
 +
        if not template then
 +
            local slang = ( adapt.lang or assigned.lang or World.slang )
 +
            local tmp  = World.templates[ slang ]
 +
            if tmp then
 +
                template = tmp[ ask ]
 +
            end
 +
        end
 +
        if type( template ) == "table" then
 +
             local low = ( ask == "ISO" or ask == "ISO-T" )
 +
            r1 = template.spec
 +
            if assigned.year then
 
                 if not assigned.dom then
 
                 if not assigned.dom then
                     r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" )
+
                    r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" )
                             :gsub( "^&#160;", "" )
+
                          :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
 
             end
 
             end
Zeile 1.402: Zeile 1.831:
 
                             stamp = "H:i:s"
 
                             stamp = "H:i:s"
 
                             if assigned.msec then
 
                             if assigned.msec then
                                 stamp = string.format( "%s.%d",
+
                                 stamp = string.format( "%s.%03d",
 
                                                       stamp,
 
                                                       stamp,
 
                                                       assigned.msec )
 
                                                       assigned.msec )
 +
                                if assigned.mysec then
 +
                                    stamp = string.format( "%s.%03d",
 +
                                                          stamp,
 +
                                                        assigned.mysec )
 +
                                end
 
                             end
 
                             end
 
                         end
 
                         end
Zeile 1.539: Zeile 1.973:
  
 
function p.test( args )
 
function p.test( args )
 +
    local slang = args[ 3 ]
 +
    local obj  = DateTime( args[ 1 ],  slang or "de",  args[ 4 ] )
 
     local r
 
     local r
    local obj = DateTime( args[ 1 ], "de" )
 
 
     if type( obj ) == "table" then
 
     if type( obj ) == "table" then
 +
        local spec  = args[ 2 ]
 
         local opt
 
         local opt
        local spec  = args[ 2 ]
 
        local slang = args[ 3 ]
 
 
         if spec then
 
         if spec then
 
             spec = mw.text.trim( spec )
 
             spec = mw.text.trim( spec )
 
         end
 
         end
 
         if slang then
 
         if slang then
             opt = { lang = mw.text.trim( slang ) }
+
             opt = { lang = mw.text.trim( slang ) }
         end
+
         end
         r = obj:format( spec, opt )
+
         r = obj:format( spec, opt )
     else
+
     else
         r = ( args.noerror or "0" )
+
         r = ( args.noerror or "0" )
         if r == "0" then
+
         if r == "0" then
             r = fault( "Format nicht erkannt" )
+
             r = fault( "Format nicht erkannt" )
         else
+
         else
             r = ""
+
             r = ""
         end
+
         end
     end
+
     end
     return r
+
     return r
end -- p.test
+
end -- p.test
 +
 
 +
 
 +
 
 +
function p.failsafe( frame )
 +
    local since = frame.args[ 1 ]
 +
    local r
 +
    if since then
 +
        since = mw.text.trim( since )
 +
        if since == "" then
 +
          since = false
 +
        end
 +
    end
 +
    if since then
 +
        if since > Serial then
 +
            r = ""
 +
        else
 +
            r = Serial
 +
        end
 +
    else
 +
        r = Serial
 +
    end
 +
    return r
 +
end -- p.failsafe
 +
 
 +
 
 +
 
 +
function p.format( frame )
 +
    --    1  -- stamp
 +
    --    2  -- spec
 +
    --    3  -- slang
 +
    --    4  -- shift
 +
    local l, r
 +
    local v = { frame.args[ 1 ],
 +
                frame.args[ 2 ],
 +
                frame.args[ 3 ],
 +
                frame.args[ 4 ],
 +
                noerror = frame.args.noerror }
 +
    if not v[ 1 ]  or  v[ 1 ] == "now" then
 +
        v[ 1 ] = frame:callParserFunction( "#timel", "c", v[ 4 ] )
 +
        v[ 4 ] = false
 +
    end
 +
    Frame = frame
 +
    l, r = pcall( p.test, v )
 +
    if not l then
 +
        r = fault( r )
 +
    end
 +
    return r
 +
end -- p.format
  
  
  
function p.format( frame )
+
function p.lt( frame )
     local l, r
+
     return Templates.flow( frame, "lt" )
    local v = { frame.args[ 1 ],
+
end -- p.lt
                frame.args[ 2 ],
+
function p.le( frame )
                frame.args[ 3 ],
+
    return Templates.flow( frame, "le" )
                noerror = frame.args.noerror }
+
end -- p.le
     if not v[ 1 ]  or  v[ 1 ] == "now" then
+
function p.eq( frame )
        v[ 1 ] = frame:callParserFunction( "#timel", "c" )
+
     return Templates.flow( frame, "eq" )
    end
+
end -- p.eq
    l, r = pcall( p.test, v )
+
function p.ne( frame )
     if not l then
+
    return Templates.flow( frame, "ne" )
        r = fault( r )
+
end -- p.ne
    end
+
function p.ge( frame )
     return r
+
     return Templates.flow( frame, "ge" )
end -- p.format
+
end -- p.ge
 +
function p.gt( frame )
 +
     return Templates.flow( frame, "gt" )
 +
end -- p.gt
  
  

Version vom 15. Juli 2015, 10:36 Uhr

Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch English

Modul: Dokumentation
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
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   -- Date and time utilities
local Serial     = "2015-07-15"
local Calc       = { }
local Parser     = { }
local Private    = { }
local Prototypes = { }
local Templates  = { }
local World      = { slang       = "en",
                     monthsLong  = { },
                     monthsParse = { },
                     months4     = { } }
local MaxYear    = 2099
local Nbsp       = mw.ustring.char( 160 )
local Tab        = mw.ustring.char( 9 )
local Frame
World.era = { en = { "BC", "AD" } }
World.monthsAbbr = {  en = { n = 3 }  }
World.monthsLong.en = { "January",
                        "February",
                        "March",
                        "April",
                        "May",
                        "June",
                        "July",
                        "August",
                        "September",
                        "October",
                        "November",
                        "December"
                      }
World.monthsParse.en = { [ "Apr" ] =  4,
                         [ "Aug" ] =  8,
                         [ "Dec" ] = 12,
                         [ "Feb" ] =  2,
                         [ "Jan" ] =  1,
                         [ "Jul" ] =  7,
                         [ "Jun" ] =  6,
                         [ "Mar" ] =  3,
                         [ "May" ] =  5,
                         [ "Nov" ] = 11,
                         [ "Oct" ] = 10,
                         [ "Sep" ] =  9
                       }
World.months4.en = { [ 6 ] = true,
                     [ 7 ] = true }
World.templates = { [ "ISO" ] =
                        { spec = "Y-m-d",
                          lift = true },
                    [ "ISO-T" ] =
                        { spec = "c" },
                    [ "timestamp" ] =
                        { spec = "YmdHis" },
                    [ "default" ] =
                        { spec = "H:i, j&#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 function capitalize( a )
    -- Upcase first character, downcase anything else
    -- Parameter:
    --     a  -- string
    -- Returns:
    --     string
    return  mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) )
            .. mw.ustring.lower( mw.ustring.sub( a, 2 ) )
end -- capitalize()



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



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



local Signature = "__datetime"
local Metatable = {
    __index    = function ( self, access )
                     local r = self[ Signature ][ access ]
                     if r == nil then
                         if access == "serial" then
                             r = Serial
                         elseif access == "suite" then
                             r = "DateTime"
                         else
                             for k, v in pairs( Prototypes ) do
                                 if k == access then
                                     r = Prototypes[ k ]
                                 end
                             end -- for k, v
                         end
                     end
                     return r
                 end,
    __newindex = function ( self, access, assign )
                     if type( access ) == "string" then
                         local data = self[ Signature ]
                         if assign == nil then
                             local val = data[ access ]
                             data[ access ] = nil
                             if not Prototypes.fair( data ) then
                                 data[ access ] = val
                             end
                         elseif Prototypes.fair( data,
                                                 access,
                                                 assign ) then
                             data[ access ] = assign
                         end
                     end
                     return
                 end,
    __add      = function ( op1, op2 )
                     return Prototypes.future( op1, op2 )
                 end,
    __eq       = function ( op1, op2 )
                     return Prototypes.flow( op1, op2, "eq" )
                 end,
    __lt       = function ( op1, op2 )
                     return Prototypes.flow( op1, op2, "lt" )
                 end,
    __le       = function ( op1, op2 )
                     return Prototypes.flow( op1, op2, "le" )
                 end
}



DateTime = function ( assign, alien, add )
    -- Create object (constructor)
    -- Parameter:
    --     assign  -- string, with initial timestamp, or nil
    --                nil    -- now
    --                false  -- empty object
    --     alien   -- string, with language code, or nil
    --     add     -- string, with interval (PHP strtotime), or nil
    -- Returns:
    --     table, as DateTime object
    --     string or false, if failed
    local r
    Private.foreign()
    r = Private.factory( assign, alien, add )
    if type( r ) == "table" then
        r = { [ Signature ] = r }
        setmetatable( r, Metatable )
    end
    return r
end -- DateTime()



Calc.months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }



--  Calc.fast = function ( at )
--      -- Quick scan of full ISO stamp
--      -- Parameter:
--      --     apply  -- string, ISO
--      -- Returns:
--      --     table, with numeric components
--      local r = { }
--      r.year  = tonumber( at:sub(  1, 4 ) )
--      r.month = tonumber( at:sub(  6, 2 ) )
--      r.dom   = tonumber( at:sub(  9, 2 ) )
--      r.hour  = tonumber( at:sub( 12, 2 ) )
--      r.min   = tonumber( at:sub( 14, 2 ) )
--      r.sec   = tonumber( at:sub( 17, 2 ) )
--      if at:sub( 19, 1 ) == "." then
--          r.msec = tonumber( at:sub( 20, 3 ) )
--          if #at > 22 then
--              r.mysec = tonumber( at:sub( 23, 3 ) )
--          end
--      end
--      return r
--  end -- Calc.fast()



Calc.fair = function ( adjust )
    -- Normalize numeric components
    -- Parameter:
    --     adjust  -- table, with raw numbers
    local order  = { "mysec", "msec", "sec", "min", "hour",
                     "dom", "month", "year" }
    local ranges = { year  = { min = -999,
                               max = 9999 },
                     month = { min =  1,
                               max = 12,
                               mod = 12 },
                     dom   = { min =  1,
                               max = 28 },
                     hour  = { mod = 24 },
                     min   = { mod = 60 },
                     sec   = { mod = 60 },
                     msec  = { mod = 1000 },
                     mysec = { mod = 1000 } }
    local m, max, min, move, n, range
    for k, v in pairs( order ) do
        n = adjust[ v ]
        if n or move then
            range = ranges[ v ]
            min   = range.min or 0
            max   = range.max  or  ( range.mod - 1 )
            if move then
                n    = ( n or 0 )  +  move
                move = false
            end
            if n < min  or  n > max then
                if range.mod then
                    m    = n % range.mod
                    move = ( n - m )  /  range.mod
                    n    = min + m
                else    -- dom
                    if adjust.month and adjust.year  and  n > 1  and
                       adjust.month >= 1  and
                       adjust.month <= 12 and
                       adjust.year > 1900 then
                        max = Calc.months[ adjust.month ]
                        if adjust.month == 2  and
                           ( adjust.year % 4 ~= 0  or
                             adjust.year % 400 == 0 ) then
                            max = 28
                        end
                        if n <= max then
                            max = false
                        end
                    end
                    if max then
                        m    = n % 30
                        move = ( n - m )  /  30
                        n    = 1 + m
                    end
                end
            end
            adjust[ v ] = n
        end
    end -- for k, v
end -- Calc.fair()



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



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



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



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



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



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



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



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



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



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



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



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



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



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



Private.fetch = function ( analyse, alien, add )
    -- Retrieve object from string
    -- Parameter:
    --     analyse  -- string to be interpreted
    --     alien    -- string with language code, or nil
    --     add      -- string, with interval (PHP strtotime), or nil
    -- Returns:
    --     table, if parsed
    --     false, if invalid text format
    --     string, if serious error (args)
    local r
    if type( analyse ) == "string" then
        r =  analyse:gsub( "&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 order = { "year", "month", "week", "dom",
                        "hour", "min", "sec", "msec", "mysec" }
        local life  = false
        local s, v1, v2
        for i = 1, 9 do
            s  = order[ i ]
            v1 = at1[ s ]
            v2 = at2[ s ]
            if v1 or v2 then
                if v1 and v2 then
                    if v1 < v2 then
                        r = -1
                    elseif v1 > v2 then
                        r = 1
                    end
                elseif life then
                    if v2 then
                        r = -1
                    else
                        r = 1
                    end
                else
                    r = nil
                end
                if r ~= 0 then
                    if at1.bc and r then
                        r = r * -1
                    end
                    break    -- for i
                end
                life = true
            end
        end -- for i
    end
    return r
end -- Private.flow()



Private.foreign = function ()
    -- Retrieve localization submodule
    if not World.localization then
        local l, d = pcall( mw.loadData, "Module:DateTime/local" )
        if l then
            local wk
            if d.slang then
                World.slang = d.slang
            end
            for k, v in pairs( d ) do
                wk = World[ k ]
                if wk  and  wk.en then
                    for subk, subv in pairs( v ) do
                        wk[ subk ] = subv
                    end -- for k, v
                else
                    World[ k ] = v
                end
            end -- for k, v
        end
        World.localization = true
    end
end -- Private.foreign()



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



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



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



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



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



Prototypes.format = function ( self, ask, adapt )
    -- Format object as string
    -- Parameter:
    --     self   -- table, with numbers etc.
    --     ask    -- string, format spec, or nil
    --     adapt  -- table, with options, or nil
    --               .lang    -- string, with particular language code
    --               .london  -- true: UTC output; default: local
    --               .lonely  -- true: permit lonely hour
    -- Returns:
    --     string, or false, if invalid
    local r = false
    if type( self ) == "table" then
        local opts  = { lang = self.lang }
        local babel, slang
        if type( adapt ) == "table" then
            if type( adapt.lang ) == "string" then
                local i = adapt.lang:find( "-", 3, true )
                if i then
                    slang = adapt.lang
                    opts.lang = slang:sub( 1,  i - 1 )
                else
                    opts.lang = adapt.lang
                end
            end
            opts.lang   = opts.lang:lower()
            opts.london = adapt.london
            opts.lonely = adapt.lonely
        end
        babel = mw.language.new( opts.lang )
        if babel then
            local shift, show, stamp, suffix, limit4, locally
            if self.month then
                stamp = World.monthsLong.en[ self.month ]
                if self.year then
                    stamp = string.format( "%s %04d", stamp, self.year )
                end
                if self.dom then
                    stamp = string.format( "%d %s", self.dom, stamp )
                end
                if ask and ask:find( "Mon4" ) then
                    local mon4 = World.months4[ opts.lang ]
                    if mon4 then
                        if mon4[ self.month ] then
                            limit4 = true
                        end
                    end
                end
            elseif self.year then
                stamp = string.format( "%04d", self.year )
            end
            if self.hour then
                stamp = string.format( "%s %02d:", stamp, self.hour )
                if self.min then
                    stamp = string.format( "%s%02d", stamp, self.min )
                    if self.sec then
                        stamp = string.format( "%s:%02d",
                                               stamp, self.sec )
                        if self.msec then
                            stamp = string.format( "%s.%03d",
                                                   stamp, self.msec )
                            if self.mysec then
                                stamp = string.format( "%s%03d",
                                                       stamp,
                                                       self.mysec )
                            end
                        end
                    end
                else
                    stamp = stamp .. "00"
                end
                if self.zone then
                    stamp = stamp .. World.zones.formatter( self, "+-" )
                end
            end
            show, suffix = World.templates.formatter( self, ask, opts )
            if limit4 then
                show = show:gsub( "M", "F" )
            end
            if type( opts.london ) == "boolean" then
                locally = not opts.london
            else
                locally = true
            end
            r = babel:formatDate( show, stamp, locally )
            r = r:gsub( "&#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 )
    -- Relative move by interval
    -- Parameter:
    --     self  -- table, to be used as base
    --     add   -- string or number, to be added
    -- Returns:
    --     table, with shift
    local r, raw, rel, shift
    if type( self ) == "table" then
        r     = self
        shift = add        
    elseif type( add ) == "table" then
        r     = add
        shift = self        
    end
    if r then
        raw = r[ Signature ]
        rel = Private.future( shift )
    end
    if raw and rel then
        for k, v in pairs( rel ) do
            raw[ k ] = ( raw[ k ] or 0 )  +  v
        end -- for k, v
        Calc.fair( raw )
    end
    return r
end -- Prototypes.future()



Templates.flow = function ( frame, action )
    -- Comparison invokation
    -- Parameter:
    --     frame  -- object
    -- Returns:
    --     string, either "" or "1"
    local r
    local s1 = frame.args[ 1 ]
    local s2 = frame.args[ 2 ]
    if s1 then
        s1 = mw.text.trim( s1 )
        if s1 == "" then
            s1 = false
        end
    end
    if s2 then
        s2 = mw.text.trim( s2 )
        if s2 == "" then
            s2 = false
        end
    end
    if s1 or s2 then
        local l
        Frame = frame
        l, r = pcall( Prototypes.flow,
                      DateTime( s1 ), s2, action )
        if r == true then
            r = "1"
        end
    end
    return r or ""
end -- Templates.flow()



World.templates.formatter = function ( assigned, ask, adapt )
    -- Retrieve format specification string
    -- Parameter:
    --     assigned  -- table, with numbers etc.
    --     ask       -- string, format spec, or nil
    --     adapt     -- table, with options
    --                  .lang    -- string, with particular language code
    --                  .lonely  -- true: permit lonely hour
    -- Returns:
    --     1  -- string
    --     2  -- string or nil; append suffix (zone)
    local r1, r2
    if not ask  or  ask == "" then
        r1 = "c"
    else
        local template = World.templates[ ask ]
        r1 = ask
        if not template then
            local slang = ( adapt.lang or assigned.lang or World.slang )
            local tmp   = World.templates[ slang ]
            if tmp then
                template = tmp[ ask ]
            end
        end
        if type( template ) == "table" then
            local low = ( ask == "ISO" or ask == "ISO-T" )
            r1 = template.spec
            if assigned.year then
                if not assigned.dom then
                    r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" )
                           :gsub( "^&#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 )
    local slang = args[ 3 ]
    local obj   = DateTime( args[ 1 ],  slang or "de",  args[ 4 ] )
    local r
    if type( obj ) == "table" then
        local spec  = args[ 2 ]
        local opt
        if spec then
            spec = mw.text.trim( spec )
        end
        if slang then
            opt = { lang = mw.text.trim( slang ) }
        end
        r = obj:format( spec, opt )
    else
        r = ( args.noerror or "0" )
        if r == "0" then
            r = fault( "Format nicht erkannt" )
        else
            r = ""
        end
    end
    return r
end -- p.test



function p.failsafe( frame )
    local since = frame.args[ 1 ]
    local r
    if since then
        since = mw.text.trim( since )
        if since == "" then
           since = false
        end
    end
    if since then
        if since > Serial then
            r = ""
        else
            r = Serial
        end
    else
        r = Serial
    end
    return r
end -- p.failsafe



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



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



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

return p