Modul:DateTime: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
Diese Vorlage(n) wurde(n) fast unverändert von der deutschsprachigen Wikipedia übernommen. Es wurden nur geringfügige technische, stilistische und organisatorische Anpassungen ans ÖsterreichWiki durchgeführt.
(update) |
(2015-03-16) |
||
Zeile 1: | Zeile 1: | ||
--[=[ | --[=[ 2015-03-16 | ||
Date and time utilities | Date and time utilities | ||
]=] | ]=] | ||
Zeile 218: | Zeile 218: | ||
end | end | ||
elseif amount == 4 then | elseif amount == 4 then | ||
local s, sep, sx = analyse:match( "^(%d+)([%-%.: | local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" ) | ||
r.year = tonumber( s ) | r.year = tonumber( s ) | ||
if sep == "-" then | if sep == "-" then | ||
Zeile 248: | Zeile 248: | ||
end | end | ||
elseif sep:lower() == "w" then | elseif sep:lower() == "w" then | ||
if | if sx then | ||
s = | s = sx:match( "^(%d%d?)$" ) | ||
if s then | if s then | ||
r.week = tonumber( s ) | r.week = tonumber( s ) | ||
if r.week < 1 or r.week > 53 then | |||
r = false | |||
end | |||
else | else | ||
r = false | r = false | ||
Zeile 396: | Zeile 399: | ||
return r | return r | ||
end -- Parser.european() | end -- Parser.european() | ||
Parser.isoDate = function ( analyse, assign ) | |||
-- String analysis, retrieve month heading ISO date | |||
-- Parameter: | |||
-- analyse -- string, with heading hyphen | |||
-- assign -- table | |||
-- Returns: | |||
-- 1 -- table, extended if parsed | |||
-- 2 -- stripped string, or false, if invalid text format | |||
local rO, rS | |||
if analyse:match( "^%-%-?[0-9]" ) then | |||
local n, s | |||
rO = assign | |||
rS = analyse:sub( 2 ) | |||
s = rS:match( "^([012][0-9])%-" ) | |||
if s then | |||
n = tonumber( s ) | |||
if n >= 1 and n <= 12 then | |||
rO.month = n | |||
rS = rS:sub( 3 ) | |||
else | |||
rO = false | |||
end | |||
end | |||
if rO then | |||
if rS:byte( 1, 1 ) == 45 then | |||
local suffix | |||
s = rS:match( "^%-([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 not rO then | |||
rS = false | |||
end | |||
return rO, rS | |||
end -- Parser.isoDate() | |||
Zeile 775: | Zeile 845: | ||
end | end | ||
else | else | ||
r, | local rM, sM = Parser.monthHeading( s, r ) | ||
if r and | if rM then | ||
r = Parser.time( | r = rM | ||
else | |||
r, sM = Parser.isoDate( s, r ) | |||
end | |||
if r and sM ~= "" then | |||
r = Parser.time( sM, r ) | |||
end | end | ||
end | end | ||
Zeile 1.299: | Zeile 1.374: | ||
end | end | ||
if type( template ) == "table" then | if type( template ) == "table" then | ||
local low = ( ask == "ISO" or ask == "ISO-T" ) | |||
r1 = template.spec | r1 = template.spec | ||
if assigned.year then | if assigned.year then | ||
if not assigned.dom then | if not assigned.dom then | ||
r1 = r1:gsub( "[ .]?[ | r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" ) | ||
:gsub( "^ ", "" ) | :gsub( "^ ", "" ) | ||
if not assigned.month then | if not assigned.month then | ||
r1 = r1:gsub( "[ .%-]?[ | r1 = r1:gsub( "[ .%-]?[FmMnt][ .%-]*", "" ) | ||
end | end | ||
end | end | ||
Zeile 1.311: | Zeile 1.387: | ||
r1 = r1:gsub( " ?[yY] ?", "" ) | r1 = r1:gsub( " ?[yY] ?", "" ) | ||
if not assigned.dom then | if not assigned.dom then | ||
r1 = r1:gsub( "[ .]?[ | r1 = r1:gsub( "[ .]?[dDjlNwz][ .,%-]*", "" ) | ||
:gsub( "^ ", "" ) | :gsub( "^ ", "" ) | ||
end | end | ||
end | end | ||
if template.lift then | if template.lift and | ||
(assigned.dom or | |||
not (assigned.month or assigned.year or assigned.bc) | |||
) then | |||
local stamp = false | local stamp = false | ||
if assigned.hour then | if assigned.hour then | ||
if assigned.min then | if assigned.min then |
Version vom 20. März 2015, 09:31 Uhr
--[=[ 2015-03-16
Date and time utilities
]=]
-- local globals
local DateTime
local Parser = { }
local Private = { }
local Prototypes = { }
local World = { slang = "en",
monthsLong = { },
monthsParse = { },
months4 = { } }
local MaxYear = 2099
local Nbsp = mw.ustring.char( 160 )
local Tab = mw.ustring.char( 9 )
World.era = { en = { "BC", "AD" } }
World.monthsAbbr = { en = { n = 3 } }
World.monthsLong.en = { "January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
}
World.monthsParse.en = { [ "Apr" ] = 4,
[ "Aug" ] = 8,
[ "Dec" ] = 12,
[ "Feb" ] = 2,
[ "Jan" ] = 1,
[ "Jul" ] = 7,
[ "Jun" ] = 6,
[ "Mar" ] = 3,
[ "May" ] = 5,
[ "Nov" ] = 11,
[ "Oct" ] = 10,
[ "Sep" ] = 9
}
World.months4.en = { [ 6 ] = true,
[ 7 ] = true }
World.templates = { [ "ISO" ] =
{ spec = "Y-m-d",
lift = true },
[ "ISO-T" ] =
{ spec = "c" },
[ "timestamp" ] =
{ spec = "YmdHis" },
[ "default" ] =
{ spec = "H:i, j 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 -- fault()
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()
DateTime = function ( assign, alien )
-- Create metatable (constructor)
-- Parameter:
-- assign -- string, with initial timestamp, or nil
-- nil -- now
-- false -- empty object
-- alien -- string, with language code, or nil
-- Returns:
-- table, as DateTime object
-- string or false, if failed
local r
Private.foreign()
r = Private.factory( assign, alien )
if type( r ) == "table" then
local meta = { }
local s = "__datetime"
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
return r
end -- DateTime()
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
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 ) )
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 not rO then
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
r.msec = tonumber( s )
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 )
-- Create DateTime table (constructor)
-- Parameter:
-- assign -- string, with initial timestamp, or nil
-- nil -- now
-- false -- empty object
-- alien -- string, with language code, or nil
-- Returns:
-- table, for DateTime object
-- string or false, if failed
local l = true
local r = false
local slang = mw.text.trim( alien or World.slang or "en" )
if assign == false then
r = { }
else
local stamp = ( assign or "now" )
if stamp == "now" then
stamp = mw.getCurrentFrame():callParserFunction( "#timel",
"c" )
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 )
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 )
-- Retrieve object from string
-- Parameter:
-- analyse -- string to be interpreted
-- alien -- string with language code, 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( " ", " " )
:gsub( " ", " " )
:gsub( "&#x[aA]0;", " " )
:gsub( " ", " " )
: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
end
end
else
r = "unknown language"
end
end
else
r = "bad type"
end
return r
end -- Private.fetch()
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()
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 },
dom = { min = 1,
max = 31 },
hour = { max = 23 },
min = { max = 59 },
sec = { max = 61 },
msec = { max = 1000 }
}
local months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
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 <= 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", "dom",
"hour", "min", "sec", "msec" }
local life = false
local leak = false
local s, v
for i = 1, 8 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
end
life = true
leak = true
end
else
life = false
end
end -- for i
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.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
-- .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.%d",
stamp, self.msec )
end
end
else
stamp = stamp .. "00"
end
if self.zone then
stamp = stamp .. World.zones.formatter( self, "+-" )
end
end
show, suffix = World.templates.formatter( self, ask, opts )
if limit4 then
show = show:gsub( "M", "F" )
end
if type( opts.london ) == "boolean" then
locally = not opts.london
else
locally = true
end
r = babel:formatDate( show, stamp, locally )
r = r:gsub( " $", "" )
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()
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( "^ ", "" )
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( "^ ", "" )
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.%d",
stamp,
assigned.msec )
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 %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 r
local obj = DateTime( args[ 1 ], "de" )
if type( obj ) == "table" then
local opt
local spec = args[ 2 ]
local slang = args[ 3 ]
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.format( frame )
local l, r
local v = { frame.args[ 1 ],
frame.args[ 2 ],
frame.args[ 3 ],
noerror = frame.args.noerror }
if not v[ 1 ] or v[ 1 ] == "now" then
v[ 1 ] = frame:callParserFunction( "#timel", "c" )
end
l, r = pcall( p.test, v )
if not l then
r = fault( r )
end
return r
end -- p.format
p.DateTime = function ( ... )
return DateTime( ... )
end -- p.DateTime
return p