Modul:DateTime: Unterschied zwischen den Versionen

Aus Fahrradmonteur
Zur Navigation springenZur Suche springen
K (1 Version importiert: Import aus Wikipedia)
 
(2)
Markierung: Geleert
Zeile 1: Zeile 1:
local DateTime  = { serial = "2017-04-29",
 
                    suite  = "DateTime" }    -- Date and time objects
 
local Calc      = { }
 
local Meta      = { }
 
local Parser    = { }
 
local Private    = { }
 
local Prototypes = { }
 
local Templates  = { }
 
local World      = { slang      = "en",
 
                    monthsLong  = { },
 
                    monthsParse = { },
 
                    months4    = { } }
 
local MaxYear    = 2099
 
local Nbsp      = mw.ustring.char( 160 )
 
local Tab        = mw.ustring.char( 9 )
 
local Frame
 
World.era = { en = { "BC", "AD" } }
 
World.monthsAbbr = {  en = { n = 3 }  }
 
World.monthsLong.en = { "January",
 
                        "February",
 
                        "March",
 
                        "April",
 
                        "May",
 
                        "June",
 
                        "July",
 
                        "August",
 
                        "September",
 
                        "October",
 
                        "November",
 
                        "December"
 
                      }
 
World.monthsParse.en = { [ "Apr" ] =  4,
 
                        [ "Aug" ] =  8,
 
                        [ "Dec" ] = 12,
 
                        [ "Feb" ] =  2,
 
                        [ "Jan" ] =  1,
 
                        [ "Jul" ] =  7,
 
                        [ "Jun" ] =  6,
 
                        [ "Mar" ] =  3,
 
                        [ "May" ] =  5,
 
                        [ "Nov" ] = 11,
 
                        [ "Oct" ] = 10,
 
                        [ "Sep" ] =  9
 
                      }
 
World.months4.en = { [ 6 ] = true,
 
                    [ 7 ] = true }
 
World.templates = { [ "ISO" ] =
 
                        { spec = "Y-m-d",
 
                          lift = true },
 
                    [ "ISO-T" ] =
 
                        { spec = "c" },
 
                    [ "timestamp" ] =
 
                        { spec = "YmdHis" },
 
                    [ "default" ] =
 
                        { spec = "H:i, j M Y",
 
                          long = true },
 
                    [ "$dmy" ] =
 
                        { spec = "H:i, j M Y",
 
                          long = true },
 
                    [ "$ymd" ] =
 
                        { spec = "H:i, Y M j",
 
                          long = true  },
 
                    [ "$dmyt" ] =
 
                        { spec = "j M Y, H:i",
 
                          long = true  },
 
                    [ "$dmyts" ] =
 
                        { spec = "j M Y, H:i:s",
 
                          long = true  },
 
                    [ "data-sort-type:date" ] =
 
                        { spec = "j M Y" }
 
                  }
 
World.templates.en = { }
 
World.zones = {
 
    [ "!" ] = "YXWVUTSRQPONZABCDEFGHIKLM",
 
    UTC  =  0,
 
    GMT  =  0      -- Greenwich Mean Time
 
}
 
World.zones.en = {
 
    BST  =  100,    -- British Summer Time
 
    IST  =  100,    -- Irish Summer Time
 
    WET  =  0,      -- Western Europe Time
 
    WEST =  100,    -- Western Europe Summer Time
 
    CET  =  100,    -- Central Europe Time
 
    CEST =  200,    -- Central Europe Summer Time
 
    EET  =  200,    -- Eastern Europe Time
 
    EEST =  300,    -- Eastern Europe Summer Time
 
    MSK  =  300,    -- Moscow Time
 
    MSD  =  400,    -- Moscow Summer Time
 
    NST  =  -330,    -- Newfoundland Standard Time
 
    NDT  =  -230,    -- Newfoundland Daylight Time
 
    AST  =  -400,    -- Atlantic Standard Time
 
    ADT  =  -300,    -- Atlantic Daylight Time
 
    EST  =  -500,    -- Eastern Standard Time
 
    EDT  =  -400,    -- Eastern Daylight Saving Time
 
    CST  =  -600,    -- Central Standard Time
 
    CDT  =  -500,    -- Central Daylight Saving Time
 
    MST  =  -700,    -- Mountain Standard Time
 
    MDT  =  -600,    -- Mountain Daylight Saving Time
 
    PST  =  -800,    -- Pacific Standard Time
 
    PDT  =  -700,    -- Pacific Daylight Saving Time
 
    AKST =  -900,    -- Alaska Standard Time
 
    AKDT =  -800,    -- Alaska Standard Daylight Saving Time
 
    HST  = -1000    -- Hawaiian Standard Time
 
}
 
  
 
 
local function capitalize( a )
 
    -- Upcase first character, downcase anything else
 
    -- Parameter:
 
    --    a  -- string
 
    -- Returns:
 
    --    string
 
    return  mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) )
 
            .. mw.ustring.lower( mw.ustring.sub( a, 2 ) )
 
end -- capitalize()
 
 
 
 
local function fault( a )
 
    -- Format error message by class=error
 
    -- Parameter:
 
    --    a  -- string, error message
 
    -- Returns:
 
    --    string, HTML span
 
    return string.format( "<span class=\"error\">%s</span>", a )
 
end -- fault()
 
 
 
 
local function frame()
 
    if not Frame then
 
        Frame = mw.getCurrentFrame()
 
    end
 
    return Frame
 
end -- frame()
 
 
 
 
Meta.localized  = false
 
Meta.serial    = DateTime.serial
 
Meta.signature  = "__datetime"
 
Meta.suite      = "{DateTime}"
 
Meta.components = { lang  = "string",
 
                    bc    = "boolean",
 
                    year  = "number",
 
                    month = "number",
 
                    week  = "number",
 
                    dom  = "number",
 
                    hour  = "number",
 
                    min  = "number",
 
                    sec  = "number",
 
                    msec  = "number",
 
                    mysec = "number",
 
                    zone  = false,
 
                    leap  = "boolean",
 
                    jul  = "boolean" }
 
Meta.order      = { "bc", "year", "month", "week", "dom",
 
                    "hour", "min", "sec", "msec", "mysec" }
 
Meta.tableI    = {    -- instance metatable
 
    __index    = function ( self, access )
 
                    local r = self[ Meta.signature ][ access ]
 
                    if r == nil then
 
                        if access == "serial" then
 
                            r = Meta.serial
 
                        elseif access == "suite" then
 
                            r = "DateTime"
 
                        else
 
                            r = Prototypes[ access ]
 
                        end
 
                    end
 
                    return r
 
                end,
 
    __newindex = function ( self, access, assign )
 
                    if type( access ) == "string" then
 
                        local data = self[ Meta.signature ]
 
                        if assign == nil then
 
                            local val = data[ access ]
 
                            data[ access ] = nil
 
                            if not Prototypes.fair( data ) then
 
                                data[ access ] = val
 
                            end
 
                        elseif Prototypes.fair( data,
 
                                                access,
 
                                                assign ) then
 
                            data[ access ] = assign
 
                        end
 
                    end
 
                    return
 
                end,
 
    __add      = function ( op1, op2 )
 
                    return Prototypes.future( op1, op2, true )
 
                end,
 
    __eq      = function ( op1, op2 )
 
                    return Prototypes.flow( op1, op2, "eq" )
 
                end,
 
    __lt      = function ( op1, op2 )
 
                    return Prototypes.flow( op1, op2, "lt" )
 
                end,
 
    __le      = function ( op1, op2 )
 
                    return Prototypes.flow( op1, op2, "le" )
 
                end,
 
    __tostring = function ( e )
 
                    return Prototypes.tostring( e )
 
                end,
 
    __call    = function ( func, ... )
 
                    return Meta.fiat( ... )
 
                end
 
} -- Meta.tableI
 
Meta.tableL    = {    -- library metatable
 
    __index    = function ( self, access )
 
                    local r
 
                    if access == "serial" then
 
                        r = Meta.serial
 
                    elseif access == "suite" then
 
                        r = Meta.suite
 
                    end
 
                    return r
 
                end,
 
    __newindex = function ()
 
                    return
 
                end,
 
    __tostring = function ()
 
                    return Meta.suite
 
                end,
 
    __call    = function ( func, ... )
 
                    return Meta.fiat( ... )
 
                end
 
} -- Meta.tableL
 
Meta.fiat = function ( assign, alien, add )
 
    -- Create instance object (constructor)
 
    -- Parameter:
 
    --    assign  -- string, with initial timestamp, or nil
 
    --                nil    -- now
 
    --                false  -- empty object
 
    --                table  -- clone this object, or copy from raw
 
    --                          ignore remaining parameters
 
    --    alien  -- string, with language code, or nil
 
    --    add    -- string, with interval (PHP strtotime), or nil
 
    -- Returns:
 
    --    table, as DateTime object
 
    --    string or false, if failed
 
    local r
 
    Private.foreign()
 
    if type( assign ) == "table" then
 
        if assign.suite == Meta.suite  and
 
          getmetatable( assign ) == Meta.tableI then
 
            r = assign[ Meta.signature ]
 
        else
 
            r = Private.from( assign )
 
        end
 
    else
 
        r = Private.factory( assign, alien, add )
 
    end
 
    if type( r ) == "table" then
 
        r = { [ Meta.signature ] = r }
 
        setmetatable( r, Meta.tableI )
 
    end
 
    return r
 
end -- Meta.fiat()
 
setmetatable( DateTime, Meta.tableL )
 
DateTime.serial = nil
 
 
 
 
Calc.months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
 
 
 
 
--  Calc.fast = function ( at )
 
--      -- Quick scan of full ISO stamp
 
--      -- Parameter:
 
--      --    apply  -- string, ISO
 
--      -- Returns:
 
--      --    table, with numeric components
 
--      local r = { }
 
--      r.year  = tonumber( at:sub(  1, 4 ) )
 
--      r.month = tonumber( at:sub(  6, 2 ) )
 
--      r.dom  = tonumber( at:sub(  9, 2 ) )
 
--      r.hour  = tonumber( at:sub( 12, 2 ) )
 
--      r.min  = tonumber( at:sub( 14, 2 ) )
 
--      r.sec  = tonumber( at:sub( 17, 2 ) )
 
--      if at:sub( 19, 1 ) == "." then
 
--          r.msec = tonumber( at:sub( 20, 3 ) )
 
--          if #at > 22 then
 
--              r.mysec = tonumber( at:sub( 23, 3 ) )
 
--          end
 
--      end
 
--      return r
 
--  end -- Calc.fast()
 
 
 
 
Calc.fair = function ( adjust )
 
    -- Normalize numeric components
 
    -- Parameter:
 
    --    adjust  -- table, with raw numbers
 
    local ranges = { year  = { min = -999,
 
                              max = 9999 },
 
                    month = { min =  1,
 
                              max = 12,
 
                              mod = 12 },
 
                    dom  = { min =  1,
 
                              max = 28 },
 
                    hour  = { mod = 24 },
 
                    min  = { mod = 60 },
 
                    sec  = { mod = 60 },
 
                    msec  = { mod = 1000 },
 
                    mysec = { mod = 1000 } }
 
    local m, max, min, move, n, range, s
 
    for i = 10, 2, -1 do
 
        s = Meta.order[ i ]
 
        n = adjust[ s ]
 
        if n or move then
 
            range = ranges[ s ]
 
            if range then
 
                min = range.min or 0
 
                max = range.max  or  ( range.mod - 1 )
 
                if move then
 
                    n    = ( n or 0 )  +  move
 
                    move = false
 
                end
 
                if n < min  or  n > max then
 
                    if range.mod then
 
                        m    = n % range.mod
 
                        move = ( n - m )  /  range.mod
 
                        n    = min + m
 
                    else    -- dom
 
                        if adjust.month and adjust.year  and  n > 1  and
 
                          adjust.month >= 1  and
 
                          adjust.month <= 12 and
 
                          adjust.year > 1900 then
 
                            max = Calc.months[ adjust.month ]
 
                            if adjust.month == 2  and
 
                              ( adjust.year % 4 ~= 0  or
 
                                adjust.year % 400 == 0 ) then
 
                                max = 28
 
                            end
 
                            if n <= max then
 
                                max = false
 
                            end
 
                        end
 
                        if max then
 
                            m    = n % 30
 
                            move = ( n - m )  /  30
 
                            n    = 1 + m
 
                        end
 
                    end
 
 
                    end
 
                adjust[ s ] = n
 
            end
 
        end
 
    end -- for i
 
end -- Calc.fair()
 
 
 
 
Calc.future = function ( add )
 
    -- Parse move interval
 
    -- Parameter:
 
    --    add  -- string, with GNU relative items
 
    -- Returns:
 
    --    table, with numeric components, or false
 
    local r, token
 
    local units = { year      = true,
 
                    month    = true,
 
                    fortnight = { slot = "dom", mult = 14 },
 
                    week      = { slot = "dom", mult = 7 },
 
                    dom      = true,
 
                    hour      = true,
 
                    min      = true,
 
                    sec      = true }
 
    local story = string.format( " %s ", add:lower() )
 
                        :gsub( "%s+", " " )
 
                        :gsub( " yesterday ", " -1 dom " )
 
                        :gsub( " tomorrow ",  " 1 dom " )
 
                        :gsub( "(%l)s ", "%1 " )
 
                        :gsub( " day ",    " dom " )
 
                        :gsub( " minute ", " min " )
 
                        :gsub( " second ", " sec " )
 
    local feed  = function ()
 
                      local slice
 
                      token, slice = story:match( "^( (%S+)) " )
 
                      return slice
 
                  end
 
    local fed  = function ()
 
                      story = story:sub( #token + 1 )
 
                  end
 
    local m, n, s, u
 
    while true do
 
        s = feed()
 
        if s then
 
            n = 1
 
            if s:match( "^[+-]?%d+$" ) then
 
                n = tonumber( s )
 
                fed()
 
                s = feed()
 
            end
 
            if s then
 
                u = units[ s ]
 
            end
 
            if s and u then
 
                fed()
 
                if u ~= true then
 
                    s = u.slot
 
                    n = n * u.mult
 
                end
 
                if feed() == "ago" then
 
                    if n > 0 then
 
                        n = - n
 
                    end
 
                    fed()
 
                end
 
                r      = r  or  { }
 
                r[ s ] = ( r[ s ] or 0 )  +  n
 
            else
 
                r = false
 
                break    -- while true
 
            end
 
        else
 
            break    -- while true
 
        end
 
    end    -- while true
 
    return r
 
end -- Calc.future()
 
 
 
 
Parser.digitsHeading = function ( analyse, alone, amount, add )
 
    -- String analysis, if digits only or at least 4 digits heading
 
    -- Parameter:
 
    --    analyse  -- string to be scanned, starting with digit
 
    --                digits only, else starting with exactly 4 digits
 
    --    alone    -- true, if only digits
 
    --    amount  -- number of heading digits
 
    --    add      -- table, to be extended
 
    -- Returns:
 
    --    table, extended if parsed
 
    --    false, if invalid text format
 
    local r = add
 
    if alone then
 
        -- digits only
 
        if amount <= 4 then
 
            r.year = tonumber( analyse )
 
        elseif n == 14 then
 
            -- timestamp
 
            r.year  = tonumber( analyse:sub(  1, 4 ) )
 
            r.month  = tonumber( analyse:sub(  5, 2 ) )
 
            r.dom    = tonumber( analyse:sub(  7, 2 ) )
 
            r.hour  = tonumber( analyse:sub(  9, 2 ) )
 
            r.min    = tonumber( analyse:sub( 11, 2 ) )
 
            r.sec    = tonumber( analyse:sub( 13, 2 ) )
 
        else
 
            r = false
 
        end
 
    elseif amount == 4 then
 
        local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" )
 
        r.year = tonumber( s )
 
        if sep == "-" then
 
            -- ISO
 
            s, sep, sx = sx:match( "^(%d%d)(-?)(.*)$" )
 
            if s then
 
                r.month  = tonumber( s )
 
                r.month2 = true
 
                if sep == "-" then
 
                    s, sep, sx = sx:match( "^(%d%d?)([ T]?)(.*)$" )
 
                    if s then
 
                        r.dom = tonumber( s )
 
                        if sep == "T" then
 
                            r.month2 = nil
 
                        else
 
                            r.dom2 = ( #s == 2 )
 
                        end
 
                        if sep then
 
                            r = Parser.time( sx,  r,  sep == "T" )
 
                        end
 
                    else
 
                        r = false
 
                    end
 
                elseif sx and sx ~= "" then
 
                    r = false
 
                end
 
            else
 
                r = false
 
            end
 
        elseif sep:lower() == "w" then
 
            if sx then
 
                s = sx:match( "^(%d%d?)$" )
 
                if s then
 
                    r.week = tonumber( s )
 
                    if r.week < 1  or  r.week > 53 then
 
                        r = false
 
                    end
 
                else
 
                    r = false
 
                end
 
            else
 
                r = false
 
            end
 
        else
 
            r = false
 
        end
 
        if r then
 
            r.iso = true
 
        end
 
    elseif amount == 8 then
 
        -- ISO compact
 
        local s, sz = analyse:match( "^%d+T(%d+)([.+-]?%d*%a*)$" )
 
        if s then
 
            local n = #s
 
            if n == 2  or  n == 4  or  n == 6 then
 
                r.year  = tonumber( analyse:sub(  1,  4 ) )
 
                r.month = tonumber( analyse:sub(  5,  6 ) )
 
                r.dom  = tonumber( analyse:sub(  7,  8 ) )
 
                r.hour  = tonumber( analyse:sub( 10, 11 ) )
 
                if n > 2 then
 
                    r.min = tonumber( s:sub( 3, 4 ) )
 
                    if n == 6 then
 
                        r.sec = tonumber( s:sub( 5, 6 ) )
 
                    end
 
                    n, s = sz:match( "^(%.%d+)([+-]?[%a%d]*)$" )
 
                    if n then
 
                        n      = n .. "00"
 
                        r.msec = tonumber( n:sub( 1, 3 ) )
 
                        if #n >= 6 then
 
                            r.mysec = tonumber( n:sub( 4, 6 ) )
 
                        end
 
                        sz = s
 
                    end
 
                end
 
                if sz ~= "" then
 
                    s, sz = sz:match( "^([+-]?)(%a*)$" )
 
                    if s == "" then
 
                        if sz:match( "^(%u)$" ) then
 
                            r.zone = sz
 
                        else
 
                            s = false
 
                        end
 
                    elseif #s == 1 then
 
                        r.zone = s .. sz
 
                    else
 
                        s = false
 
                    end
 
                end
 
            else
 
                s = false
 
            end
 
        end
 
        if s then
 
            r = false
 
        end
 
    end
 
    return r
 
end -- Parser.digitsHeading()
 
 
 
 
Parser.eraGermanEnglish = function ( analyse )
 
    -- String analysis, for German and English era
 
    -- v. Chr.  v. u. Z.  n. Chr.  AD BC  A.D. B.C. B.C.E.
 
    -- Parameter:
 
    --    analyse  -- string
 
    -- Returns:
 
    --    1  -- table, with boolean era, if any
 
    --    2  -- string, with era stripped off, if any
 
    local rO = { }
 
    local rS = analyse
 
    local s, switch = analyse:match( "^(.+) ([vn])%. ?Chr%.$" )
 
    if switch then
 
        rS    = s
 
        rO.bc = ( switch == "v" )
 
    elseif analyse:find( " v%. ?u%. ?Z%.$" ) then
 
        rS    = analyse:match( "^(.+) v%. ?u%. ?Z%.$" )
 
        rO.bc = true
 
    elseif analyse:find( " B%.? ?C%.? ?E?%.?$" ) then
 
        rS    = analyse:match( "^(.+) B%.? ?C%.? ?E?%.?$" )
 
        rO.bc = true
 
    elseif analyse:find( "^A%.? ?D%.? " ) then
 
        rS    = analyse:match( "^A%.? ?D%.? (.*)$" )
 
        rO.bc = false
 
    end
 
    return rO, rS
 
end -- Parser.eraGermanEnglish()
 
 
 
 
Parser.european = function ( ahead, adhere, analyse, assign )
 
    -- String analysis, retrieve date style: DOM MONTH YEAR
 
    -- Parameter:
 
    --    ahead    -- string, with first digits, not more than 2
 
    --    adhere  -- string, with first separator; not ":"
 
    --    analyse  -- string, remainder following adhere
 
    --    assign  -- table
 
    -- Returns:
 
    --    table, extended if parsed
 
    local r = assign
 
    local s, s2, sx
 
    if adhere == "."  or  adhere == ". " then
 
        -- 23.12.2013
 
        -- 23. Dezember 2013
 
        s, sx = analyse:match( "^(%d%d?)%.(.*)$" )
 
        if s then
 
            r = Parser.putDate( false, s, ahead, assign )
 
            r = Parser.yearTime( sx, r )
 
        else
 
            s, sx = mw.ustring.match( analyse,
 
                                      "^ ?([%a&;]+%.?) ?(.*)$" )
 
            if s then
 
                local n = Parser.monthNumber( s )
 
                if n then
 
                    r.month = n
 
                    r.dom  = tonumber( ahead )
 
                    r.dom2  = ( #ahead == 2 )
 
                    r      = Parser.yearTime( sx, r )
 
                else
 
                    r = false
 
                end
 
            else
 
                r = false
 
            end
 
        end
 
    elseif adhere == " " then
 
        -- 23 Dec 2013
 
        s, sx = mw.ustring.match( analyse,
 
                                  "^([%a&;]+%.?) ?(.*)$" )
 
        if s then
 
            local n = Parser.monthNumber( s )
 
            if n then
 
                r.month = n
 
                r.dom  = tonumber( ahead )
 
                r.dom2  = ( #ahead == 2 )
 
                r      = Parser.yearTime( sx, r )
 
            else
 
                r = false
 
            end
 
        else
 
            r = false
 
        end
 
    else
 
        r = false
 
    end
 
    return r
 
end -- Parser.european()
 
 
 
 
Parser.isoDate = function ( analyse, assign )
 
    -- String analysis, retrieve month heading ISO date
 
    -- Parameter:
 
    --    analyse  -- string, with heading hyphen
 
    --    assign  -- table
 
    -- Returns:
 
    --    1  -- table, extended if parsed
 
    --    2  -- stripped string, or false, if invalid text format
 
    local rO, rS
 
    if analyse:match( "^%-%-?[0-9]" ) then
 
        local n, s
 
        rO = assign
 
        rS = analyse:sub( 2 )
 
        s  = rS:match( "^([012][0-9])%-" )
 
        if s then
 
            n = tonumber( s )
 
            if n >= 1  and  n <= 12 then
 
                rO.month = n
 
                rS      = rS:sub( 3 )
 
            else
 
                rO = false
 
            end
 
        end
 
        if rO then
 
            if rS:byte( 1, 1 ) == 45 then
 
                local suffix
 
                s = rS:match( "^%-([012][0-9])" )
 
                if s then
 
                    n  = tonumber( s )
 
                    if n >= 1  and  n <= 31 then
 
                        rO.dom = n
 
                        rS    = rS:sub( 4 )
 
                    else
 
                        rO = false
 
                    end
 
                else
 
                    rS:sub( 2 )
 
                end
 
            else
 
                rO = false
 
            end
 
            if rO then
 
                if #rS > 0 then
 
                    if rO.dom then
 
                        n = rS:byte( 1, 1 )
 
                        if n == 32  or  n == 84 then
 
                            rS = rS:sub( 2 )
 
                        else
 
                            rO = false
 
                        end
 
                    else
 
                        rO = false
 
                    end
 
                end
 
            end
 
        end
 
    else
 
        rO = false
 
    end
 
    if rO then
 
        rO.iso = true
 
    else
 
        rS = false
 
    end
 
    return rO, rS
 
end -- Parser.isoDate()
 
 
 
 
Parser.monthHeading = function ( analyse, assign )
 
    -- String analysis, retrieve month heading date (US only)
 
    -- Parameter:
 
    --    analyse  -- string, with heading word
 
    --    assign  -- table
 
    -- Returns:
 
    --    1  -- table, extended if parsed
 
    --    2  -- stripped string, or false, if invalid text format
 
    local rO = assign
 
    local rS = analyse
 
    local s, sep = mw.ustring.match( analyse, "^([%a&;]+%.?)([^%a%.]?)" )
 
    if s then
 
        -- might begin with month name  "December 23, 2013"
 
        local n = Parser.monthNumber( s )
 
        if n then
 
            rO.month = n
 
            if sep == "" then
 
                rS = ""
 
            else
 
                local s2, s3
 
                n = mw.ustring.len( s )  +  1
 
                s = mw.ustring.sub( analyse, n )
 
                s2 = s:match( "^ (%d%d%d?%d?)$" )
 
                if s2 then
 
                    rO.year = tonumber( s2 )
 
                    rS = ""
 
                else
 
                    s2, s3, rS = s:match( "^ (%d+), (%d+)( ?.*)$" )
 
                    if s2 and s3 then
 
                        n = #s2
 
                        if n <= 2  and  #s3 == 4 then
 
                            rO.dom  = tonumber( n )
 
                            rO.year = tonumber( s3 )
 
                            rO.dom2 = ( n == 2 )
 
                        else
 
                            rO = false
 
                        end
 
                    else
 
                        rO = false
 
                    end
 
                end
 
            end
 
        else
 
            rO = false
 
        end
 
    else
 
        rO = false
 
    end
 
    if not rO then
 
        rS = false
 
    end
 
    return rO, rS
 
end -- Parser.monthHeading()
 
 
 
 
Parser.monthNumber = function ( analyse )
 
    -- String analysis, retrieve month number
 
    -- Parameter:
 
    --    analyse  -- string, with month name including any period
 
    -- Returns:
 
    --    number, 1...12 if found
 
    --    false or nil, if not detected
 
    local r = false
 
    local s = mw.ustring.match( analyse, "^([%a&;]+)%.?$" )
 
    if s then
 
        local given
 
        s = capitalize( s )
 
        for k, v in pairs( World.monthsLong ) do
 
            given = World.monthsParse[ k ]
 
            if given then
 
                r = given[ s ]
 
            end
 
            if not r then
 
                given = World.monthsLong[ k ]
 
                for i = 1, 12 do
 
                    if given[ i ] == s then
 
                        r = i
 
                        break
 
                    end
 
                end -- for i
 
            end
 
            if r then
 
                break
 
            end
 
        end -- for k, v
 
    end
 
    return r
 
end -- Parser.monthNumber()
 
 
 
 
Parser.putDate = function ( aYear, aMonth, aDom, assign )
 
    -- Store date strings
 
    -- Parameter:
 
    --    aYear  -- string, with year, or false
 
    --    aMonth  -- string, with numeric month
 
    --    aDom    -- string, with day of month
 
    --    assign  -- table
 
    -- Returns:
 
    --    table, extended
 
    local r = assign
 
    if aYear then
 
        r.year  = tonumber( aYear )
 
    end
 
    r.month  = tonumber( aMonth )
 
    r.dom    = tonumber( aDom )
 
    r.month2 = ( #aMonth == 2 )
 
    r.dom2  = ( #aDom == 2 )
 
    return r
 
end -- Parser.putDate()
 
 
 
 
Parser.time = function ( analyse, assign, adjusted )
 
    -- String analysis, retrieve time components
 
    -- Parameter:
 
    --    analyse  -- string, with time part
 
    --    assign    -- table
 
    --    adjusted  -- true: fixed length of 2 digits expected
 
    -- Returns:
 
    --    table, extended if parsed
 
    --    false, if invalid text format
 
    local r = assign
 
    if analyse ~= "" then
 
        local s, sx = analyse:match( "^(%d+)(:?.*)$" )
 
        if s then
 
            local n = #s
 
            if n <= 2 then
 
                r.hour  = tonumber( s )
 
                if not adjusted then
 
                    r.hour2 = ( n == 2 )
 
                end
 
            else
 
                sx = false
 
                r  = false
 
            end
 
            if sx then
 
                s, sx = sx:match( "^:(%d+)(:?(.*))$"  )
 
                if s then
 
                    if #s == 2 then
 
                        r.min = tonumber( s )
 
                        if sx == "" then
 
                            sx = false
 
                        end
 
                    else
 
                        sx = false
 
                        r  = false
 
                    end
 
                    if sx then
 
                        local sep
 
                        local scan = "^([:,] ?)(%d+)(.*)$"
 
                        sep, s, sx = sx:match( scan )
 
                        if sep == ":" then
 
                            if #s == 2 then
 
                                r.sec = tonumber( s )
 
                            end
 
                        elseif sep == ", " then
 
                            r = Parser.wikiDate( s .. sx,  r )
 
                            sx = false
 
                        else
 
                            r = false
 
                        end
 
                    end
 
                else
 
                    r = false
 
                end
 
            end
 
            if sx  and  sx ~= "" then
 
                s = sx:match( "^%.(%d+)$" )
 
                if s then
 
                    s  = s .. "00"
 
                    r.msec = tonumber( s:sub( 1, 3 ) )
 
                    if #s >= 6 then
 
                        r.mysec = tonumber( s:sub( 4, 6 ) )
 
                    end
 
                else
 
                    r = false
 
                end
 
            end
 
        else
 
            r = false
 
        end
 
    end
 
    return r
 
end -- Parser.time()
 
 
 
 
Parser.wikiDate = function ( analyse, assign )
 
    -- String analysis, for date after wiki ~~~~~ signature time
 
    --    dmy    10:28, 30. Dez. 2013
 
    --    ymd    10:28, 2013 Dez. 30
 
    -- Parameter:
 
    --    analyse  -- string
 
    --    assign  -- table
 
    -- Returns:
 
    --    table, extended if parsed
 
    --    false, if invalid text format
 
    local r
 
    local s = analyse:match( "^(2%d%d%d) " )
 
    local sx
 
    if s then
 
        -- ymd    "10:28, 2013 Dez. 30"
 
        local n = false
 
        r = assign
 
        r.year = tonumber( s )
 
        s = analyse:sub( 6 )
 
        s, sx = mw.ustring.match( analyse:sub( 6 ),
 
                                  "^([%a&;]+)%.? (%d%d?)$" )
 
        if s then
 
            n = Parser.monthNumber( s )
 
            if n then
 
              r.month = n
 
            end
 
        end
 
        if n then
 
            r.dom  = tonumber( sx )
 
            r.dom2 = ( #sx == 2 )
 
        else
 
            r = false
 
        end
 
    else
 
        -- dmy    "10:28, 30. Dez. 2013"
 
        local sep
 
        s, sep, sx = analyse:match( "^(%d%d?)(%.? ?)(%a.+)$" )
 
        if s then
 
            r = Parser.european( s, sep, sx, assign )
 
        else
 
            r = false
 
        end
 
    end
 
    return r
 
end -- Parser.wikiDate()
 
 
 
 
Parser.yearTime = function ( analyse, assign )
 
    -- String analysis, for possible year and possible time
 
    -- Parameter:
 
    --    analyse  -- string, starting with year
 
    --    assign  -- table
 
    -- Returns:
 
    --    table, extended if parsed
 
    --    false, if invalid text format
 
    local r = assign
 
    local n = #analyse
 
    if n > 0 then
 
        local s, sx
 
        if n == 4 then
 
            if analyse:match( "^%d%d%d%d$" ) then
 
                s  = analyse
 
                sx = false
 
            end
 
        else
 
            s = analyse:match( "^(%d%d%d%d)[ ,]" )
 
            if s then
 
                sx = analyse:sub( 5 )
 
            else
 
                local suffix
 
                s, sx, suffix = analyse:match( "^(%d+)([ ,]?)(.*)$" )
 
                if s then
 
                    local j = #sx
 
                    n = #s
 
                    if n < 4  and  ( j == 1 or #suffix == 0 ) then
 
                        sx = analyse:sub( n + j )
 
                    else
 
                        s = false
 
                    end
 
                end
 
            end
 
        end
 
        if s then
 
            r.year = tonumber( s )
 
            if sx then
 
                s, sx = sx:match( "^(,? ?)(%d.*)$" )
 
                if #s >= 1 then
 
                    r = Parser.time( sx, r )
 
                end
 
            end
 
        else
 
            r = false
 
        end
 
    end
 
    return r
 
end -- Parser.yearTime()
 
 
 
 
Parser.zone = function ( analyse, assign )
 
    -- String analysis, for time zone
 
    -- +/-nn +/-nnnn (AAAa)
 
    -- Parameter:
 
    --    analyse  -- string
 
    --    assign  -- table
 
    -- Returns:
 
    --    1  -- table, with number or string zone, if any, or false
 
    --    2  -- string, with zone stripped off, if any
 
    local rO = assign
 
    local rS = analyse
 
    local s, sign, shift, sub
 
    s = "^(.+)([+-])([01]%d):?(%d?%d?)$"
 
    s, sign, shift, sub = analyse:match( s )
 
    if sign then
 
        if s:find( ":%d%d *$" ) then
 
            if sub then
 
                if #sub == 2 then
 
                    rO.zone = tonumber( shift .. sub )
 
                else
 
                    rO = false
 
                end
 
            else
 
                rO.zone = tonumber( shift ) * 100
 
            end
 
            if rO then
 
                if sign == "-" then
 
                    rO.zone = - rO.zone
 
                end
 
                rS = mw.text.trim( s )
 
            end
 
        end
 
    elseif analyse:find( "%(.*%)$" ) then
 
        s, shift = analyse:match( "^(.+)%((%a%a%a%a?)%)$" )
 
        if shift then
 
            rO.zone = shift:upper()
 
            rS      = mw.text.trim( s )
 
        else
 
            rO = false
 
        end
 
    else
 
        s, shift = analyse:match( "^(.+%d) ?(%a+)$" )
 
        if shift then
 
            local n = #shift
 
            if n == 1 then
 
                rO.zone = shift:upper()
 
            elseif n == 3 then
 
                if shift == "UTC"  or  shift == "GMT" then
 
                    rO.zone = 0
 
                end
 
            end
 
            if rO.zone then
 
                rS = s
 
            end
 
        end
 
    end
 
    return rO, rS
 
end -- Parser.zone()
 
 
 
 
Parser.GermanEnglish = function ( analyse )
 
    -- String analysis, for German and English formats
 
    -- Parameter:
 
    --    analyse  -- string, with date or time or parts of it
 
    -- Returns:
 
    --    table, if parsed
 
    --    false, if invalid text format
 
    local r, s = Parser.eraGermanEnglish( analyse )
 
    r, s = Parser.zone( s, r )
 
    if r then
 
        local start, sep, sx = s:match( "^(%d+)([ %-%.:WwT]?)(.*)$" )
 
        if start then
 
            -- begins with one or more digits (ASCII)
 
            local n    = #start
 
            local lazy = ( start == s  and
 
                          ( n >=4  or  type( r.bc == "boolean" ) ) )
 
            if n == 4  or  n == 8  or  lazy then
 
                r = Parser.digitsHeading( s, lazy, n, r )
 
            elseif n <= 2 then
 
                if sep == ":" then
 
                    r, s = Parser.time( s, r )
 
                elseif sep == "" then
 
                    r = false
 
                else
 
                    r = Parser.european( start, sep, sx, r )
 
                end
 
            else
 
                r = false
 
            end
 
        else
 
            local rM, sM = Parser.monthHeading( s, r )
 
            if rM then
 
                r = rM
 
            else
 
                r, sM = Parser.isoDate( s, r )
 
            end
 
            if r and sM ~= "" then
 
                r = Parser.time( sM, r )
 
            end
 
        end
 
    end
 
    return r
 
end -- Parser.GermanEnglish()
 
 
 
 
Private.factory = function ( assign, alien, add )
 
    -- Create DateTime table (constructor)
 
    -- Parameter:
 
    --    assign  -- string, with initial timestamp, or nil
 
    --                nil    -- now
 
    --                false  -- empty object
 
    --    alien  -- string, with language code, or nil
 
    --    add    -- string, with interval (PHP strtotime), or nil
 
    -- Returns:
 
    --    table, for DateTime object
 
    --    string or false, if failed
 
    local l    = true
 
    local slang = mw.text.trim( alien or World.slang or "en" )
 
    local r
 
    if assign == false then
 
        r = { }
 
    else
 
        local stamp = ( assign or "now" )
 
        local shift
 
        if add then
 
            shift = Private.future( add )
 
        end
 
        r = false
 
        if stamp == "now" then
 
            stamp = frame():callParserFunction( "#timel", "c", shift )
 
            shift = false
 
        else
 
            local seconds = stamp:match( "^#(%d+)$" )
 
            if seconds then
 
                stamp = os.date( "!%Y-%m-%dT%H:%M:%S",
 
                                tonumber( seconds ) )
 
            end
 
        end
 
        l, r = pcall( Private.fetch, stamp, slang, shift )
 
    end
 
    if l  and  type( r ) == "table" then
 
        if slang ~= "" then
 
            r.lang = slang
 
        end
 
    end
 
    return r
 
end -- Private.factory()
 
 
 
 
Private.fetch = function ( analyse, alien, add )
 
    -- Retrieve object from string
 
    -- Parameter:
 
    --    analyse  -- string to be interpreted
 
    --    alien    -- string with language code, or nil
 
    --    add      -- string, with interval (PHP strtotime), or nil
 
    -- Returns:
 
    --    table, if parsed
 
    --    false, if invalid text format
 
    --    string, if serious error (args)
 
    local r
 
    if type( analyse ) == "string" then
 
        r =  analyse:gsub( "&nbsp;", " " )
 
                    :gsub( "&#160;", " " )
 
                    :gsub( "&#x[aA]0;", " " )
 
                    :gsub( "&#32;", " " )
 
                    :gsub( Nbsp, " " )
 
                    :gsub( Tab, " " )
 
                    :gsub( "  +", " " )
 
                    :gsub( "%[%[", "" )
 
                    :gsub( "%]%]", "" )
 
        r = mw.text.trim( r )
 
        if r == "" then
 
            r = { }
 
        else
 
            local slang = ( alien or "" )
 
            if slang == "" then
 
                slang = "en"
 
            else
 
                local s = slang:match( "^(%a+)%-" )
 
                if s then
 
                    slang = s
 
                end
 
            end
 
            slang = slang:lower()
 
            if slang == "en" or slang == "de" then
 
                local l
 
                l, r = pcall( Parser.GermanEnglish, r )
 
                if l and r then
 
                    if not Prototypes.fair( r ) then
 
                        r = false
 
                    elseif add then
 
                        r = Prototypes.future( r, add )
 
                    end
 
                end
 
            else
 
                r = "unknown language"
 
            end
 
        end
 
    else
 
        r = "bad type"
 
    end
 
    return r
 
end -- Private.fetch()
 
 
 
 
Private.flow = function ( at1, at2 )
 
    -- Compare two objects
 
    -- Parameter:
 
    --    at1  -- DateTime
 
    --    at2  -- DateTime
 
    -- Returns:
 
    --    -1, 0, 1 or nil if not comparable
 
    local r = 0
 
    if at1.bc or at2.bc  and  at1.bc ~= at2.bc then
 
        if at1.bc then
 
            r = -1
 
        else
 
            r = 1
 
        end
 
    else
 
        local life  = false
 
        local s, v1, v2
 
        for i = 2, 10 do
 
            s  = Meta.order[ i ]
 
            v1 = at1[ s ]
 
            v2 = at2[ s ]
 
            if v1 or v2 then
 
                if v1 and v2 then
 
                    if v1 < v2 then
 
                        r = -1
 
                    elseif v1 > v2 then
 
                        r = 1
 
                    end
 
                elseif life then
 
                    if v2 then
 
                        r = -1
 
                    else
 
                        r = 1
 
                    end
 
                else
 
                    r = nil
 
                end
 
                if r ~= 0 then
 
                    if at1.bc and r then
 
                        r = r * -1
 
                    end
 
                    break    -- for i
 
                end
 
                life = true
 
            end
 
        end -- for i
 
    end
 
    return r
 
end -- Private.flow()
 
 
 
 
Private.foreign = function ()
 
    -- Retrieve localization submodule
 
    if not Meta.localized then
 
        local l, d = pcall( mw.loadData, "Module:DateTime/local" )
 
        if l then
 
            local wk
 
            if d.slang then
 
                Meta.suite  = string.format( "%s %s",
 
                                            Meta.suite, d.slang )
 
                World.slang = d.slang
 
            end
 
            for k, v in pairs( d ) do
 
                wk = World[ k ]
 
                if wk  and  wk.en then
 
                    for subk, subv in pairs( v ) do
 
                        wk[ subk ] = subv
 
                    end -- for k, v%s %s
 
                else
 
                    World[ k ] = v
 
                end
 
            end -- for k, v
 
        end
 
        Meta.localized = true
 
    end
 
end -- Private.foreign()
 
 
 
 
Private.from = function ( attempt )
 
    -- Create valid raw table from arbitrary table
 
    -- Parameter:
 
    --    attempt  -- table, to be evaluated
 
    -- Returns:
 
    --    table, with valid components, or nil
 
    local data  = { }
 
    local r
 
    for k, v in pairs( Meta.components ) do
 
        if v then
 
            v = ( type( attempt[ k ] )  ==  v )
 
        else
 
            v = true
 
        end
 
        if v then
 
            data[ k ] = attempt[ k ]
 
        end
 
    end -- for k, v
 
    if Prototypes.fair( data ) then
 
        r = data
 
    end
 
    return r
 
end -- Private.from()
 
 
 
 
Private.future = function ( add )
 
    -- Normalize move interval
 
    -- Parameter:
 
    --    add  -- string or number, to be added
 
    -- Returns:
 
    --    string, with shift, or false/nil
 
    local r
 
    if add then
 
        local s = type( add )
 
        if s == "string"  and  add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then
 
            r = tonumber( add )
 
            s = "number"
 
        else
 
            r = add
 
        end
 
        if s == "number" then
 
            if r == 0 then
 
                r = false
 
            else
 
                r = string.format( "%d second", r )
 
            end
 
        elseif s ~= "string" then
 
            r = false
 
        end
 
        if r then
 
            r = Calc.future( r )
 
        end
 
    end
 
    return r
 
end -- Private.future()
 
 
 
 
Prototypes.clone = function ( self )
 
    -- Clone object
 
    -- Parameter:
 
    --    self  -- table, with object, to be cloned
 
    -- Returns:
 
    --    table, with object
 
    local r = { [ Meta.signature ] = self[ Meta.signature ] }
 
    setmetatable( r, Meta.tableI )
 
    return r
 
end -- Prototypes.clone()
 
 
 
 
Prototypes.fair = function ( self, access, assign )
 
    -- Check formal validity of table
 
    -- Parameter:
 
    --    self    -- table, to be checked
 
    --    access  -- string or nil, single item to be checked
 
    --    assign  -- single access value to be checked
 
    -- Returns:
 
    --    true, if valid;  false, if not
 
    local r = ( type( self ) == "table" )
 
    if r then
 
        local defs = { year  = { max = MaxYear },
 
                      month = { min =  1,
 
                                max = 12 },
 
                      week  = { min =  1,
 
                                max = 53 },
 
                      dom  = { min =  1,
 
                                max = 31 },
 
                      hour  = { max = 23 },
 
                      min  = { max = 59 },
 
                      sec  = { max = 61 },
 
                      msec  = { max = 999 },
 
                      mysec = { max = 999 }
 
        }
 
        local fNum =
 
            function ( k, v )
 
                local ret = true
 
                local dk  = defs[ k ]
 
                if dk then
 
                    if type( dk.max ) == "number" then
 
                        ret = ( type( v ) == "number" )
 
                        if ret then
 
                            local min
 
                            if dk.min then
 
                                min = dk.min
 
                            else
 
                                min = 0
 
                            end
 
                            ret = ( v >= min  and  v <= dk.max
 
                                    and  math.floor( v ) == v )
 
                            if ret and dk.f then
 
                                ret = dk.f( v )
 
                            end
 
                        end
 
                    end
 
                end
 
                return ret
 
            end -- fNum()
 
        defs.dom.f =
 
            function ()
 
                local ret
 
                local d
 
                if access == "dom" then
 
                    d = assign
 
                else
 
                    d = self.dom
 
                end
 
                if d then
 
                    ret = ( d <= 28 )
 
                    if not ret then
 
                        local m
 
                        if access == "month" then
 
                            m = assign
 
                        else
 
                            m = self.month
 
                        end
 
                        if m then
 
                            ret = ( d <= Calc.months[ m ] )
 
                            if ret then
 
                                local y
 
                                if access == "year" then
 
                                    y = assign
 
                                else
 
                                    y = self.year
 
                                end
 
                                if d == 29  and  m == 2  and  y then
 
                                    if y % 4 ~= 0  or  y % 400 == 0 then
 
                                        ret = false
 
                                    end
 
                                end
 
                            end
 
                        end
 
                    end
 
                else
 
                    ret = true
 
                end
 
                return ret
 
            end -- defs.dom.f()
 
        defs.sec.f =
 
            function ()
 
                local ret
 
                local second
 
                if access == "sec" then
 
                    second = assign
 
                else
 
                    second = self.sec
 
                end
 
                if second then
 
                    ret = ( second <= 59 )
 
                    if not ret and self.leap then
 
                        ret = true
 
                    end
 
                end
 
                return ret
 
            end -- defs.sec.f()
 
        if access or assign then
 
            r = ( type( access ) == "string" )
 
            if r then
 
                local def = defs[ access ]
 
                if def then
 
                    r = fNum( access, assign )
 
                    if r then
 
                        if def == "dom"  or
 
                          def == "month"  or
 
                          def == "year" then
 
                            r = defs.dom.f()
 
                        end
 
                    end
 
                elseif access == "lang" then
 
                    r = ( type( assign ) == "string" )
 
                    if r then
 
                        r = assign:match( "^%l%l%l?-?%a*$" )
 
                    end
 
                elseif access == "london" then
 
                    r = ( type( assign ) == "boolean" )
 
                end
 
            end
 
        else
 
            local life  = false
 
            local leak  = false
 
            local s, v
 
            for i = 1, 10 do
 
                s = Meta.order[ i ]
 
                v = self[ s ]
 
                if v then
 
                    if not life and leak then
 
                        -- gap detected
 
                        r = false
 
                        break
 
                    else
 
                        if not fNum( s, v ) then
 
                            r = false
 
                            break    -- for i
 
                        end
 
                        life = true
 
                        leak = true
 
                    end
 
                elseif i == 3 then
 
                    if not self.week then
 
                        life = false
 
                    end
 
                elseif i ~= 4 then
 
                    life = false
 
                end
 
            end -- for i
 
            if self.week  and  ( self.month or self.dom ) then
 
                r = false
 
            end
 
        end
 
    end
 
    return r
 
end -- Prototypes.fair()
 
 
 
 
Prototypes.figure = function ( self, assign )
 
    -- Assign month by name
 
    -- Parameter:
 
    --    self    -- table, to be filled
 
    --    assign  -- string, with month name
 
    -- Returns:
 
    --    number 1...12, if valid;  false, if not
 
    local r = false
 
    if type( self ) == "table"  and  type( assign ) == "string" then
 
        r = Parser.monthNumber( assign )
 
        if r then
 
            self.month = r
 
        end
 
    end
 
    return r
 
end -- Prototypes.figure()
 
 
 
 
Prototypes.first = function ( self )
 
    -- Retrieve abbreviated month name in current language
 
    -- Parameter:
 
    --    self  -- table, to be evaluated
 
    -- Returns:
 
    --    string, if defined;  false, if not
 
    local r
 
    if type( self ) == "table"  and  self.month then
 
        local slang = ( self.lang or World.slang )
 
        r = World.monthsLong[ slang ]
 
        if r then
 
            local brief = World.monthsAbbr[ slang ]
 
            r = r[ self.month ]
 
            if brief then
 
                local ex = brief[ self.month ]
 
                local s  = brief.suffix
 
                if ex then
 
                    r = ex[ 2 ]
 
                else
 
                    local n = brief.n or 3
 
                    r = mw.ustring.sub( r, 1, n )
 
                end
 
                if s then
 
                    r = r .. s
 
                end
 
            end
 
        end
 
    else
 
        r = false
 
    end
 
    return r
 
end -- Prototypes.first()
 
 
 
 
Prototypes.flow = function ( self, another, assert )
 
    -- Compare this object with another timestamp
 
    -- Parameter:
 
    --    self    -- table, with numbers etc.
 
    --    another  -- DateTime or string or nil (now)
 
    --    assert  -- nil, or string with operator
 
    --                      "lt", "le", "eq", "ne", "ge", "gt",
 
    --                      "<", "<=", "==", "~=", "<>", ">=", "=>", ">"
 
    -- Returns:
 
    --    if assert: true or false
 
    --    else: -1, 0, 1
 
    --    nil if invalid
 
    local base, other, r
 
    if type( self ) == "table" then
 
        base  = self
 
        other = another
 
    elseif type( another ) == "table" then
 
        base  = another
 
        other = self
 
    end
 
    if base then
 
        if type( other ) ~= "table" then
 
            other = Meta.fiat( other )
 
        end
 
        if type( other ) == "table" then
 
            r = Private.flow( base, other )
 
            if r  and  type( assert ) == "string" then
 
                local trsl = { lt    = "<",
 
                              ["<"]  = "<",
 
                              le    = "<=",
 
                              ["<="] = "<=",
 
                              eq    = "=",
 
                              ["=="] = "=",
 
                              ne    = "<>",
 
                              ["<>"] = "<>",
 
                              ["~="] = "<>",
 
                              ge    = ">=",
 
                              [">="] = ">=",
 
                              ["=>"] = ">=",
 
                              gt    = ">",
 
                              [">"]  = ">" }
 
                local same = trsl[ assert:lower() ]
 
                if same then
 
                    local s = "="
 
                    if r < 0 then
 
                        s = "<"
 
                    elseif r > 0 then
 
                        s = ">"
 
                    end
 
                    r = ( same:find( s, 1, true )  ~=  nil )
 
                else
 
                    r = nil
 
                end
 
            end
 
        end
 
    end
 
    return r
 
end -- Prototypes.flow()
 
 
 
 
Prototypes.format = function ( self, ask, adapt )
 
    -- Format object as string
 
    -- Parameter:
 
    --    self  -- table, with numbers etc.
 
    --    ask    -- string, format spec, or nil
 
    --    adapt  -- table, with options, or nil
 
    --              .lang    -- string, with particular language code
 
    --              .london  -- true: UTC output; default: local
 
    --              .lonely  -- true: permit lonely hour
 
    -- Returns:
 
    --    string, or false, if invalid
 
    local r = false
 
    if type( self ) == "table" then
 
        local opts  = { lang = self.lang }
 
        local babel, slang
 
        if type( adapt ) == "table" then
 
            if type( adapt.lang ) == "string" then
 
                local i = adapt.lang:find( "-", 3, true )
 
                if i then
 
                    slang = adapt.lang:lower()
 
                    opts.lang = slang:sub( 1,  i - 1 )
 
                else
 
                    opts.lang = adapt.lang:lower()
 
                end
 
            end
 
            opts.lang  = opts.lang
 
            opts.london = adapt.london
 
            opts.lonely = adapt.lonely
 
        end
 
        babel = mw.language.new( opts.lang )
 
        if babel then
 
            local shift, show, stamp, suffix, limit4, locally
 
            if self.month then
 
                stamp = World.monthsLong.en[ self.month ]
 
                if self.year then
 
                    stamp = string.format( "%s %04d", stamp, self.year )
 
                end
 
                if self.dom then
 
                    stamp = string.format( "%d %s", self.dom, stamp )
 
                end
 
                if ask and ask:find( "Mon4" ) then
 
                    local mon4 = World.months4[ opts.lang ]
 
                    if mon4 then
 
                        if mon4[ self.month ] then
 
                            limit4 = true
 
                        end
 
                    end
 
                end
 
            elseif self.year then
 
                stamp = string.format( "%04d", self.year )
 
            end
 
            if self.hour then
 
                stamp = string.format( "%s %02d:", stamp, self.hour )
 
                if self.min then
 
                    stamp = string.format( "%s%02d", stamp, self.min )
 
                    if self.sec then
 
                        stamp = string.format( "%s:%02d",
 
                                              stamp, self.sec )
 
                        if self.msec then
 
                            stamp = string.format( "%s.%03d",
 
                                                  stamp, self.msec )
 
                            if self.mysec then
 
                                stamp = string.format( "%s%03d",
 
                                                      stamp,
 
                                                      self.mysec )
 
                            end
 
                        end
 
                    end
 
                else
 
                    stamp = stamp .. "00"
 
                end
 
                if self.zone then
 
                    stamp = stamp .. World.zones.formatter( self, "+-" )
 
                end
 
            end
 
            show, suffix = World.templates.formatter( self, ask, opts )
 
            if limit4 then
 
                show = show:gsub( "M", "F" )
 
            end
 
            if type( opts.london ) == "boolean" then
 
                locally = not opts.london
 
            else
 
                locally = true
 
            end
 
            r = babel:formatDate( show, stamp, locally )
 
            r = r:gsub( "&#160;$", "" )
 
            if self.year and self.year < 1000 then
 
                r = r:gsub( string.format( "%04d", self.year ),
 
                            tostring( self.year ) )
 
            end
 
            if self.month then
 
                local bucket, m, suite, x
 
                if show:find( "F" ) then
 
                    suite = "monthsLong"
 
                elseif show:find( "M" ) then
 
                    suite = "monthsAbbr"
 
                end
 
                bucket = World[ suite ]
 
                if bucket then
 
                    m = bucket[ opts.lang ]
 
                    if slang then
 
                        x = bucket[ slang ]
 
                    end
 
                    if m then
 
                        local base = m[ self.month ]
 
                        local ex
 
                        if x then
 
                            ex = x[ self.month ]
 
                        end
 
                        if suite == "monthsAbbr" then
 
                            local stop
 
                            if ex then
 
                                stop = x.suffix
 
                                base = ex
 
                            else
 
                                stop = m.suffix
 
                            end
 
                            if base and stop then
 
                                local shift, std
 
                                std  = string.format( "%s%%%s",
 
                                                      base[ 1 ], stop )
 
                                shift = string.format( "%s%s",
 
                                                      base[ 2 ], stop )
 
                                r = mw.ustring.gsub( r, std, shift )
 
                            end
 
                        elseif suite == "monthsLong" then
 
                            if base and ex then
 
                                r = mw.ustring.gsub( r, base, ex )
 
                            end
 
                        end
 
                    end
 
                end
 
            end
 
            if suffix then
 
                r = r .. suffix
 
            end
 
        end
 
    end
 
    return r
 
end -- Prototypes.format()
 
 
 
 
Prototypes.full = function ( self )
 
    -- Retrieve month name in current language
 
    -- Parameter:
 
    --    self  -- table, to be evaluated
 
    -- Returns:
 
    --    string, if defined;  false, if not
 
    local r
 
    if type( self ) == "table"  and  self.month then
 
        local slang = ( self.lang or World.slang )
 
        r = World.monthsLong[ slang ]
 
        if r then
 
            r = r[ self.month ]
 
        end
 
    else
 
        r = false
 
    end
 
    return r
 
end -- Prototypes.full()
 
 
 
 
Prototypes.future = function ( self, add, allocate )
 
    -- Relative move by interval
 
    -- Parameter:
 
    --    self      -- table, to be used as base
 
    --    add      -- string or number, to be added
 
    --    allocate  -- true, if a clone shall be returned
 
    -- Returns:
 
    --    table, with shift
 
    local r, raw, rel, shift
 
    if type( self ) == "table" then
 
        r    = self
 
        shift = add
 
    elseif type( add ) == "table" then
 
        r    = add
 
        shift = self
 
    end
 
    if r then
 
        raw = r[ Meta.signature ]
 
        rel = Private.future( shift )
 
    end
 
    if raw and rel then
 
        if allocate then
 
            r  = Prototypes.clone( r )
 
            raw = r[ Meta.signature ]
 
        end
 
        for k, v in pairs( rel ) do
 
            raw[ k ] = ( raw[ k ] or 0 )  +  v
 
        end -- for k, v
 
        Calc.fair( raw )
 
        r[ Meta.signature ] = raw
 
    end
 
    return r
 
end -- Prototypes.future()
 
 
 
 
Prototypes.tostring = function ( self )
 
    -- Stringify yourself
 
    -- Parameter:
 
    --    self  -- table, to be stringified
 
    -- Returns:
 
    --    string
 
    local dels = { false, "", "-", "-", "", ":", ":", ".", "", "" }
 
    local wids = { false, 4,  2,  2,  2,  2,  2,  2,  3,  3  }
 
    local s    = ""
 
    local n, r, spec
 
    local f = function ( a )
 
                  n = self[ Meta.order[ a ] ]
 
                  s = s .. dels[ a ]
 
                  if n then
 
                      spec = string.format( "%%s%%0%dd", wids[ a ] )
 
                      s    = string.format( spec, s, n )
 
                  end
 
              end -- f()
 
    for i = 2, 4 do
 
        f( i )
 
    end -- for i
 
    r = s
 
    s = ""
 
    for i = 5, 10 do
 
        f( i )
 
    end -- for i
 
    if s == "::." then
 
        r = r:gsub( "%-+$", "" )
 
    else
 
        if r == "--" then
 
            r = s
 
        else
 
            r = string.format( "%sT%s", r, s )
 
        end
 
    end
 
    return r
 
end -- Prototypes.tostring()
 
 
 
 
Prototypes.valueOf = function ( self )
 
    -- Returns yourselves primitive value (primitive table)
 
    -- Parameter:
 
    --    self  -- table, to be dumped
 
    -- Returns:
 
    --    table, or false
 
    local r
 
    if type( self ) == "table" then
 
        r = self[ Meta.signature ]
 
    end
 
    return r or false
 
end -- Prototypes.valueOf()
 
 
 
 
Templates.flow = function ( frame, action )
 
    -- Comparison invokation
 
    -- Parameter:
 
    --    frame  -- object
 
    -- Returns:
 
    --    string, either "" or "1"
 
    local r
 
    local s1 = frame.args[ 1 ]
 
    local s2 = frame.args[ 2 ]
 
    if s1 then
 
        s1 = mw.text.trim( s1 )
 
        if s1 == "" then
 
            s1 = false
 
        end
 
    end
 
    if s2 then
 
        s2 = mw.text.trim( s2 )
 
        if s2 == "" then
 
            s2 = false
 
        end
 
    end
 
    if s1 or s2 then
 
        local l
 
        Frame = frame
 
        l, r = pcall( Prototypes.flow,
 
                      Meta.fiat( s1 ), s2, action )
 
        if r == true then
 
            r = "1"
 
        end
 
    end
 
    return r or ""
 
end -- Templates.flow()
 
 
 
 
World.templates.formatter = function ( assigned, ask, adapt )
 
    -- Retrieve format specification string
 
    -- Parameter:
 
    --    assigned  -- table, with numbers etc.
 
    --    ask      -- string, format spec, or nil
 
    --    adapt    -- table, with options
 
    --                  .lang    -- string, with particular language code
 
    --                  .lonely  -- true: permit lonely hour
 
    -- Returns:
 
    --    1  -- string
 
    --    2  -- string or nil; append suffix (zone)
 
    local r1, r2
 
    if not ask  or  ask == "" then
 
        r1 = "c"
 
    else
 
        local template = World.templates[ ask ]
 
        r1 = ask
 
        if not template then
 
            local slang = ( adapt.lang or assigned.lang or World.slang )
 
            local tmp  = World.templates[ slang ]
 
            if tmp then
 
                template = tmp[ ask ]
 
            end
 
        end
 
        if type( template ) == "table" then
 
            local low = ( ask == "ISO" or ask == "ISO-T" )
 
            r1 = template.spec
 
            if assigned.year then
 
                if not assigned.dom then
 
                    r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" )
 
                          :gsub( "^&#160;", "" )
 
                    if not assigned.month then
 
                        r1 = r1:gsub( "[ .%-]?[FmMnt][ .%-]*", "" )
 
                    end
 
                end
 
            else
 
                r1 = r1:gsub( " ?[yY] ?", "" )
 
                if not assigned.dom then
 
                    r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" )
 
                            :gsub( "^&#160;", "" )
 
                end
 
            end
 
            if template.lift and
 
              (assigned.dom or
 
                not (assigned.month or assigned.year or assigned.bc)
 
              ) then
 
                local stamp = false
 
                if assigned.hour then
 
                    if assigned.min then
 
                        stamp = "H:i"
 
                        if assigned.sec then
 
                            stamp = "H:i:s"
 
                            if assigned.msec then
 
                                stamp = string.format( "%s.%03d",
 
                                                      stamp,
 
                                                      assigned.msec )
 
                                if assigned.mysec then
 
                                    stamp = string.format( "%s.%03d",
 
                                                          stamp,
 
                                                        assigned.mysec )
 
                                end
 
                            end
 
                        end
 
                    elseif adapt.lonely then
 
                        stamp = "H"
 
                    end
 
                end
 
                if low or ask:find( "hh:mm:ss" ) then
 
                    if stamp then
 
                        r1 = string.format( "%s %s", r1, stamp )
 
                    end
 
                end
 
                if stamp then
 
                    if low or template.long then
 
                        local scheme
 
                        if template.long then
 
                            scheme = mw.language.getContentLanguage()
 
                            scheme = scheme.code
 
                        end
 
                        r2 = World.zones.formatter( assigned, scheme )
 
                    end
 
                end
 
            end
 
            if type ( assigned.bc ) == "boolean" then
 
                local eras = World.era[ adapt.lang ]  or  World.era.en
 
                local i
 
                if not r2 then
 
                    r2 = ""
 
                end
 
                if assigned.bc then
 
                    i = 1
 
                else
 
                    i = 2
 
                end
 
                r2 = string.format( "%s&#160;%s", r2, eras[ i ] )
 
            end
 
        end
 
    end
 
    return r1, r2
 
end -- World.templates.formatter()
 
 
 
 
World.zones.formatter =  function ( assigned, align )
 
    -- Retrieve time zone specification string
 
    -- Parameter:
 
    --    assigned  -- table, with numbers etc.
 
    --                  .zone should be available
 
    --    align    -- string, format spec, or nil
 
    --                  nil, false, "+-"  -- +/- 0000
 
    --                  "Z"              -- single letter
 
    --                  "UTC"            -- "UTC", if appropriate
 
    --                  "de"              -- try localized
 
    -- Returns:
 
    --    string
 
    local r    = ""
 
    local move = 0
 
    if assigned.zone then
 
        local s = type( assigned.zone )
 
        if s == "string" then
 
            s = assigned.zone:upper()
 
            if #s == 1 then
 
                -- "YXWVUTSRQPONZABCDEFGHIKLM"
 
                move = World.zones[ "!" ]:find( s )
 
                if move then
 
                    move          = ( move - 13 ) * 100
 
                    assigned.zone = move
 
                else
 
                    assigned.zone = false
 
                end
 
            else
 
                local code = World.zones[ s ]
 
                if not code then
 
                  local slang = ( assigned.lang or
 
                                  World.slang )
 
                  local tmp  = World.zones[ slang ]
 
                  if tmp then
 
                      code = tmp[ s ]
 
                  end
 
                end
 
                if code then
 
                    move          = code
 
                    assigned.zone = move
 
                end
 
            end
 
        elseif s == "number" then
 
            move = assigned.zone
 
        end
 
    end
 
    if move then
 
        local spec = "+-"
 
        if align then
 
            if align == "Z" then
 
                if move % 100 == 0 then
 
                    r = World.zones[ "!" ]:sub( move / 100 + 13,  1 )
 
                    spec = false
 
                end
 
            elseif align ~= "+-" then
 
                if move == 0 then
 
                    r    = " UTC"
 
                    spec = false
 
                else
 
                    local part = World.zones[ align ]
 
                    if part then
 
                        for k, v in pairs( part ) do
 
                            if v == move then
 
                                r    = string.format( " (%s)", k )
 
                                spec = false
 
                                break
 
                            end
 
                        end -- for k, v
 
                    end
 
                end
 
            end
 
        end
 
        if spec == "+-" then
 
            if move < 0 then
 
                spec = "%4.4d"
 
            else
 
                spec = "+%4.4d"
 
            end
 
            r = string.format( spec, move )
 
            r = string.format( "%s:%s",
 
                              r:sub( 1, 3), r:sub( 4 ) )
 
        end
 
    end
 
    return r
 
end -- World.zones.formatter()
 
 
 
 
-- Export
 
local p = { }
 
 
function p.test( args, alien )
 
    local slang = args.lang
 
    local obj  = Meta.fiat( args[ 1 ], false, args.shift )
 
    local r
 
    if type( obj ) == "table" then
 
        local spec  = args[ 2 ]
 
        local opt
 
        if spec then
 
            spec = mw.text.trim( spec )
 
        end
 
        if slang then
 
            opt = { lang = mw.text.trim( slang ) }
 
        end
 
        r = obj:format( spec, opt )
 
    else
 
        r = ( args.noerror or "0" )
 
        if r == "0" then
 
            r = fault( "Format nicht erkannt" )
 
        else
 
            r = ""
 
        end
 
    end
 
    return r
 
end -- p.test
 
 
 
 
function p.failsafe( frame )
 
    local s = type( frame )
 
    local r, since
 
    if s == "table" then
 
        since = frame.args[ 1 ]
 
    elseif s == "string" then
 
        since = frame
 
    end
 
    if since then
 
        since = mw.text.trim( since )
 
        if since == "" then
 
            since = false
 
        end
 
    end
 
    if since then
 
        if since > Meta.serial then
 
            r = ""
 
        else
 
            r = Meta.serial
 
        end
 
    else
 
        r = Meta.serial
 
    end
 
    return r
 
end -- p.failsafe
 
 
 
 
function p.format( frame )
 
    --    1      -- stamp
 
    --    2      -- spec
 
    --    lang
 
    --    shift
 
    --    noerror
 
    local l, r
 
    local v = { frame.args[ 1 ],
 
                frame.args[ 2 ],
 
                shift  = frame.args.shift,
 
                noerror = frame.args.noerror }
 
    if not v[ 1 ]  or  v[ 1 ] == "now" then
 
        v[ 1 ]  = frame:callParserFunction( "#timel", "c", v.shift )
 
        v.shift = false
 
    end
 
    Frame  = frame
 
    l, r = pcall( p.test,  v,  frame.args[ 3 ] or frame.args.lang )
 
    if not l then
 
        r = fault( r )
 
    end
 
    return r
 
end -- p.format
 
 
 
 
function p.lt( frame )
 
    return Templates.flow( frame, "lt" )
 
end -- p.lt
 
function p.le( frame )
 
    return Templates.flow( frame, "le" )
 
end -- p.le
 
function p.eq( frame )
 
    return Templates.flow( frame, "eq" )
 
end -- p.eq
 
function p.ne( frame )
 
    return Templates.flow( frame, "ne" )
 
end -- p.ne
 
function p.ge( frame )
 
    return Templates.flow( frame, "ge" )
 
end -- p.ge
 
function p.gt( frame )
 
    return Templates.flow( frame, "gt" )
 
end -- p.gt
 
 
 
 
p.DateTime = function ()
 
    return DateTime
 
end -- p.DateTime
 
 
return p
 

Version vom 10. Januar 2026, 05:13 Uhr

Die Dokumentation für dieses Modul kann unter Modul:DateTime/Doku erstellt werden