imported>NordNordWest K (Änderte den Schutzstatus für „Modul:DateTime“: Häufig eingebundenes Modul: https://de.wikipedia.org/w/index.php?title=Wikipedia%3AAdministratoren%2FAnfragen&type=revision&diff=170687742&oldid=170687631 ([Bearbeiten=Nur Administratoren] (unbe…) |
imported>Jivee Blau (2020-09-30 - Administratoren-Anfrage: https://de.wikipedia.org/w/index.php?title=Wikipedia:Administratoren/Anfragen&diff=204106510&oldid=204105921) |
||
Zeile 1: | Zeile 1: | ||
local DateTime = { serial = " | local DateTime = { serial = "2020-09-30", | ||
suite = "DateTime" } | suite = "DateTime", | ||
item = 20652535 } | |||
-- Date and time objects | |||
local Failsafe = DateTime | |||
local GlobalMod = DateTime | |||
local Calc = { } | local Calc = { } | ||
local Meta = { } | local Meta = { } | ||
Zeile 12: | Zeile 16: | ||
months4 = { } } | months4 = { } } | ||
local MaxYear = 2099 | local MaxYear = 2099 | ||
local Frame | local Frame | ||
DateTime.char = { nbsp = mw.ustring.char( 160 ), | |||
tab = mw.ustring.char( 9 ) } | |||
World.era = { en = { "BC", "AD" } } | World.era = { en = { "BC", "AD" } } | ||
World.monthsAbbr = { en = { n = 3 } } | World.monthsAbbr = { en = { n = 3 } } | ||
Zeile 103: | Zeile 107: | ||
HST = -1000 -- Hawaiian Standard Time | HST = -1000 -- Hawaiian Standard Time | ||
} | } | ||
local foreignModule = function ( access, advanced, append, alt, alert ) | |||
-- Fetch global module | |||
-- Precondition: | |||
-- access -- string, with name of base module | |||
-- advanced -- true, for require(); else mw.loadData() | |||
-- append -- string, with subpage part, if any; or false | |||
-- alt -- number, of wikidata item of root; or false | |||
-- alert -- true, for throwing error on data problem | |||
-- Postcondition: | |||
-- Returns whatever, probably table | |||
-- 2020-01-01 | |||
local storage = access | |||
local fun, lucky, r | |||
if advanced then | |||
fun = require | |||
else | |||
fun = mw.loadData | |||
end | |||
if append then | |||
storage = string.format( "%s/%s", storage, append ) | |||
end | |||
lucky, r = pcall( fun, "Module:" .. storage ) | |||
if not lucky then | |||
local suited | |||
GlobalMod.globalModules = GlobalMod.globalModules or { } | |||
suited = GlobalMod.globalModules[ access ] | |||
if not suited and | |||
type( alt ) == "number" and | |||
alt > 0 then | |||
suited = string.format( "Q%d", alt ) | |||
suited = mw.wikibase.getSitelink( suited ) | |||
GlobalMod.globalModules[ access ] = suited or true | |||
end | |||
if type( suited ) == "string" then | |||
storage = suited | |||
if append then | |||
storage = string.format( "%s/%s", storage, append ) | |||
end | |||
lucky, r = pcall( fun, storage ) | |||
end | |||
if not lucky and alert then | |||
error( "Missing or invalid page: " .. storage ) | |||
end | |||
end | |||
return r | |||
end -- foreignModule() | |||
Zeile 124: | Zeile 177: | ||
-- Returns: | -- Returns: | ||
-- string, HTML span | -- string, HTML span | ||
local e = mw.html.create( "span" ) | |||
:addClass( "error" ) | |||
:wikitext( a ) | |||
return tostring( e ) | |||
end -- fault() | end -- fault() | ||
Zeile 260: | Zeile 316: | ||
end -- Meta.fiat() | end -- Meta.fiat() | ||
setmetatable( DateTime, Meta.tableL ) | setmetatable( DateTime, Meta.tableL ) | ||
DateTime.serial = nil | DateTime.serial = nil | ||
Zeile 327: | Zeile 382: | ||
n = min + m | n = min + m | ||
else -- dom | else -- dom | ||
if adjust.month and adjust.year | if adjust.month and adjust.year and | ||
adjust.month >= 1 and | adjust.month >= 1 and | ||
adjust.month <= 12 and | adjust.month <= 12 and | ||
adjust.year > 1900 then | adjust.year > 1900 then | ||
max = Calc. | if n > 0 then | ||
if adjust.month == | max = Calc.final( adjust ) | ||
while n > max do | |||
n = n - max | |||
if adjust.month < 12 then | |||
adjust.month = adjust.month + 1 | |||
else | |||
adjust.month = 1 | |||
adjust.year = adjust.year + 1 | |||
end | |||
max = Calc.final( adjust ) | |||
end -- while n <= max | |||
else | |||
while n < 1 do | |||
if adjust.month == 1 then | |||
adjust.month = 12 | |||
adjust.year = adjust.year - 1 | |||
else | |||
adjust.month = adjust.month - 1 | |||
end | |||
max = Calc.final( adjust ) | |||
n = n + max | |||
end -- while n < 1 | |||
end | end | ||
end | end | ||
end | end | ||
end | |||
adjust[ s ] = n | adjust[ s ] = n | ||
end | end | ||
Zeile 354: | Zeile 418: | ||
end -- for i | end -- for i | ||
end -- Calc.fair() | end -- Calc.fair() | ||
Calc.final = function ( adjust ) | |||
-- Retrieve number of days in particular month | |||
-- Parameter: | |||
-- adjust -- table, with date specification | |||
-- Returns: | |||
-- number, of days in month | |||
local r = Calc.months[ adjust.month ] | |||
if adjust.month == 2 and | |||
( adjust.year % 4 ~= 0 or | |||
adjust.year % 400 == 0 ) then | |||
r = 28 | |||
end | |||
return r | |||
end -- Calc.final() | |||
Zeile 444: | Zeile 525: | ||
if amount <= 4 then | if amount <= 4 then | ||
r.year = tonumber( analyse ) | r.year = tonumber( analyse ) | ||
elseif | elseif amount == 14 then | ||
-- timestamp | -- timestamp | ||
r.year = tonumber( analyse:sub( 1, 4 ) ) | r.year = tonumber( analyse:sub( 1, 4 ) ) | ||
r.month = tonumber( analyse:sub( 5, | r.month = tonumber( analyse:sub( 5, 6 ) ) | ||
r.dom = tonumber( analyse:sub( 7, | r.dom = tonumber( analyse:sub( 7, 8 ) ) | ||
r.hour = tonumber( analyse:sub( 9, | r.hour = tonumber( analyse:sub( 9, 10 ) ) | ||
r.min = tonumber( analyse:sub( 11, | r.min = tonumber( analyse:sub( 11, 12 ) ) | ||
r.sec = tonumber( analyse:sub( 13, | r.sec = tonumber( analyse:sub( 13, 14 ) ) | ||
else | else | ||
r = false | r = false | ||
Zeile 672: | Zeile 753: | ||
if rS:byte( 1, 1 ) == 45 then | if rS:byte( 1, 1 ) == 45 then | ||
local suffix | local suffix | ||
s = rS:match( "^%-([ | s = rS:match( "^%-([0-3][0-9])" ) | ||
if s then | if s then | ||
n = tonumber( s ) | n = tonumber( s ) | ||
Zeile 746: | Zeile 827: | ||
n = #s2 | n = #s2 | ||
if n <= 2 and #s3 == 4 then | if n <= 2 and #s3 == 4 then | ||
rO.dom = tonumber( | rO.dom = tonumber( s2 ) | ||
rO.year = tonumber( s3 ) | rO.year = tonumber( s3 ) | ||
rO.dom2 = ( n == 2 ) | rO.dom2 = ( n == 2 ) | ||
Zeile 1.161: | Zeile 1.242: | ||
-- analyse -- string to be interpreted | -- analyse -- string to be interpreted | ||
-- alien -- string with language code, or nil | -- alien -- string with language code, or nil | ||
-- add -- | -- add -- table, with interval, or nil | ||
-- Returns: | -- Returns: | ||
-- table, if parsed | -- table, if parsed | ||
Zeile 1.168: | Zeile 1.249: | ||
local r | local r | ||
if type( analyse ) == "string" then | if type( analyse ) == "string" then | ||
local strip = mw.ustring.char( 0x5B, 0x200E, 0x200F, 0x5D ) | |||
r = analyse:gsub( " ", " " ) | r = analyse:gsub( " ", " " ) | ||
:gsub( " ", " " ) | :gsub( " ", " " ) | ||
:gsub( "&#x[aA]0;", " " ) | :gsub( "&#x[aA]0;", " " ) | ||
:gsub( " ", " " ) | :gsub( " ", " " ) | ||
:gsub( | :gsub( DateTime.char.nbsp, " " ) | ||
:gsub( | :gsub( DateTime.char.tab, " " ) | ||
:gsub( " +", " " ) | :gsub( " +", " " ) | ||
:gsub( "%[%[", "" ) | :gsub( "%[%[", "" ) | ||
:gsub( "%]%]", "" ) | :gsub( "%]%]", "" ) | ||
:gsub( strip, "" ) | |||
r = mw.text.trim( r ) | r = mw.text.trim( r ) | ||
if r == "" then | if r == "" then | ||
r = { } | r = { } | ||
else | else | ||
local slang = ( alien or "" ) | local slang = ( alien or "" ) | ||
local parser = { en = "GermanEnglish", | |||
de = "GermanEnglish", | |||
frr = "GermanEnglish", | |||
nds = "GermanEnglish" } | |||
local suitable | |||
if slang == "" then | if slang == "" then | ||
slang = "en" | slang = "en" | ||
Zeile 1.190: | Zeile 1.278: | ||
end | end | ||
end | end | ||
slang = slang:lower() | slang = slang:lower() | ||
suitable = parser[ slang ] | |||
if suitable then | |||
local l | local l | ||
l, r = pcall( Parser | l, r = pcall( Parser[ suitable ], r ) | ||
if l and r then | if l and r then | ||
if not Prototypes.fair( r ) then | if not Prototypes.fair( r ) then | ||
Zeile 1.200: | Zeile 1.289: | ||
r = Prototypes.future( r, add ) | r = Prototypes.future( r, add ) | ||
end | end | ||
else | |||
r = "invalid format" | |||
end | end | ||
else | else | ||
r = "unknown language" | r = "unknown language: " .. slang | ||
end | end | ||
end | end | ||
Zeile 1.213: | Zeile 1.304: | ||
Private. | Private.field = function ( at, ask, adapt, atleast ) | ||
-- | -- Format object as string | ||
-- Parameter: | -- Parameter: | ||
-- | -- at -- DateTime | ||
-- | -- ask -- string, with format spec, or nil | ||
-- adapt -- table, with options, or nil | |||
-- .lang -- string, with particular language code | |||
-- .london -- true: UTC output; default: local | |||
-- .lonely -- true: permit lonely hour | |||
-- atleast -- string, with default value, or nil | |||
-- Returns: | -- Returns: | ||
-- | -- string, or false, if invalid, or number for julian date | ||
local r = | local r, spec | ||
if type( ask ) == "string" then | |||
if ask:sub( 1, 1 ) == "$" then | |||
if ask:sub( 1, 11 ) == "$JulianDate" then | |||
local luxury = ( ask:sub( -2 ) == ",$" ) | |||
if ask:sub( 1, 14 ) == "$JulianDateJul" then | |||
at.legacy = true | |||
elseif ask:sub( 1, 15 ) == "$JulianDateGreg" then | |||
else | |||
at.legacy = Private.former( at ) | |||
end | |||
r = Private.fixed( at, luxury ) | |||
elseif ask:sub( 1, 11 ) == "$JulianCal$" then | |||
adapt.legacy = true | |||
spec = ask:sub( 12 ) | |||
elseif ask:sub( 1, 3 ) == "$\"$" then | |||
r = ask:sub( 4 ) | |||
else | |||
spec = ask | |||
end | |||
else | else | ||
spec = ask | |||
end | end | ||
else | else | ||
spec = false | |||
end | |||
if not r then | |||
r = Private.format( at, spec, adapt ) | |||
end | |||
return r or atleast | |||
end -- Private.field() | |||
Private.fixed = function ( at, advanced ) | |||
-- Compute julian date | |||
-- Parameter: | |||
-- at -- DateTime | |||
-- .legacy -- true: at is in Julian calendar | |||
-- advanced -- true: format long number | |||
-- Returns: | |||
-- number, or string | |||
local mM, mMY, mY, nY, r | |||
if at.year then | |||
mY = at.year * 12 | |||
else -- actually invalid | |||
mY = 0 | |||
end | |||
if at.month then | |||
mMY = at.month | |||
else | |||
mMY = 1 | |||
end | |||
mMY = mMY + 57609 | |||
if at.dom then | |||
r = at.dom | |||
else | |||
r = 1 | |||
end | end | ||
mM = ( mY + mMY ) * 0.08333333333 -- divided by 12 months | |||
nY = math.floor( mM - 1 ) | |||
r = math.floor( nY * 365.25 ) | |||
+ math.floor( ( mMY%12 + 4 ) * 30.6 ) | |||
+ r | |||
if at.legacy then | |||
-- | r = r - 32205.5 | ||
else | |||
r = r - math.floor( nY * 0.01 ) -- no leap day in century | |||
+ math.floor( nY * 0.0025 ) -- but every 400 years | |||
- 32167.5 | |||
end | |||
if at.hour then -- divided by 24 hours per day | |||
r = r + at.hour * 0.0416666666666667 | |||
else | |||
r = r + 0.5 | |||
end | |||
if at.min then -- divided by 1440 minutes per day | |||
r = r + at.min * 0.000694444444 | |||
end | |||
if at.sec then -- divided by 86400 seconds per day | |||
r = r + at.min * 0.00001157407407 | |||
end | |||
if at.bc then | |||
r = 3442406 - r | |||
if at.legacy then | |||
r = r + 3 | |||
end | end | ||
end | end | ||
end -- Private. | if advanced then | ||
local slang = ( at.lang or World.slang ) | |||
local o = mw.language.new( slang ) | |||
r = o:formatNum( r ) | |||
end | |||
return r | |||
end -- Private.fixed() | |||
Private. | Private.flow = function ( at1, at2 ) | ||
-- | -- Compare two objects | ||
-- Parameter: | -- Parameter: | ||
-- | -- at1 -- DateTime | ||
-- at2 -- DateTime | |||
-- Returns: | -- Returns: | ||
-- | -- -1, 0, 1 or nil if not comparable | ||
local | local r = 0 | ||
if at1.bc or at2.bc and at1.bc ~= at2.bc then | |||
if at1.bc then | |||
if | r = -1 | ||
else | else | ||
r = 1 | |||
end | end | ||
else | |||
local life = false | |||
local s, v1, v2 | |||
for i = 2, 10 do | |||
s = Meta.order[ i ] | |||
v1 = at1[ s ] | |||
end | v2 = at2[ s ] | ||
return r | if v1 or v2 then | ||
end -- Private. | 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. | Private.foreign = function () | ||
-- | -- Retrieve localization submodule | ||
if not Meta.localized then | |||
local d = foreignModule( DateTime.suite, | |||
false, | |||
"local", | |||
DateTime.item ) | |||
if type( d ) == "table" then | |||
local wk | |||
if d.slang then | |||
Meta.suite = string.format( "%s %s", | |||
Meta.suite, d.slang ) | |||
World.slang = d.slang | |||
end | |||
for k, v in pairs( d ) do | |||
wk = World[ k ] | |||
if wk and wk.en then | |||
for subk, subv in pairs( v ) do | |||
wk[ subk ] = subv | |||
end -- for k, v | |||
else | |||
World[ k ] = v | |||
end | |||
end -- for k, v | |||
end | |||
Meta.localized = true | |||
end | |||
end -- Private.foreign() | |||
Private.format = function ( at, ask, adapt ) | |||
-- Format object as string | |||
-- Parameter: | -- Parameter: | ||
-- | -- at -- table, with numbers etc. | ||
-- ask -- string, format spec, or nil | |||
-- adapt -- table, with options, or nil | |||
-- .lang -- string, with particular language code | |||
-- .london -- true: UTC output; default: local | |||
-- .lonely -- true: permit lonely hour | |||
-- Returns: | -- Returns: | ||
-- string, | -- string, or not | ||
local r | local slang = at.lang or "en" | ||
if | local opts = { lang = slang } | ||
local | local babel, r | ||
if | if type( adapt ) == "table" then | ||
if type( adapt.lang ) == "string" then | |||
local i = adapt.lang:find( "-", 3, true ) | |||
if i then | |||
slang = adapt.lang:lower() | |||
opts.lang = slang:sub( 1, i - 1 ) | |||
else | |||
opts.lang = adapt.lang:lower() | |||
end | |||
end | |||
opts.london = adapt.london | |||
opts.lonely = adapt.lonely | |||
end | |||
babel = mw.language.new( opts.lang:lower() ) | |||
if babel then | |||
local shift, show, stamp, suffix, limit4, locally | |||
if at.month then | |||
stamp = World.monthsLong.en[ at.month ] | |||
if at.year then | |||
stamp = string.format( "%s %04d", stamp, at.year ) | |||
end | |||
if at.dom then | |||
stamp = string.format( "%d %s", at.dom, stamp ) | |||
end | |||
if ask and ask:find( "Mon4", 1, true ) then | |||
local mon4 = World.months4[ opts.lang:lower() ] | |||
if mon4 and mon4[ at.month ] then | |||
limit4 = true | |||
end | |||
end | |||
elseif at.year then | |||
stamp = string.format( "%04d", at.year ) | |||
end | end | ||
if | if at.hour then | ||
if stamp then | |||
stamp = stamp .. " " | |||
else | |||
stamp = "" | |||
end | |||
stamp = string.format( "%s%02d:", stamp, at.hour ) | |||
if at.min then | |||
stamp = string.format( "%s%02d", stamp, at.min ) | |||
if at.sec then | |||
stamp = string.format( "%s:%02d", | |||
stamp, at.sec ) | |||
if at.msec then | |||
stamp = string.format( "%s.%03d", | |||
stamp, at.msec ) | |||
if at.mysec then | |||
stamp = string.format( "%s%03d", | |||
stamp, | |||
at.mysec ) | |||
end | |||
end | |||
end | |||
else | else | ||
stamp = stamp .. "00" | |||
end | end | ||
if at.zone then | |||
stamp = stamp .. World.zones.formatter( at, "+-" ) | |||
end | |||
end | |||
show, suffix = World.templates.formatter( at, ask, opts ) | |||
if limit4 then | |||
show = show:gsub( "M", "F" ) | |||
end | end | ||
if | if type( opts.london ) == "boolean" then | ||
locally = not opts.london | |||
else | |||
locally = true | |||
end | end | ||
end | r = babel:formatDate( show, stamp, locally ) | ||
r = r:gsub( " $", "" ) | |||
if at.year and at.year < 1000 then | |||
r = r:gsub( string.format( "%04d", at.year ), | |||
tostring( at.year ) ) | |||
end | |||
if at.month then | |||
local bucket, m, suite, x | |||
if show:find( "F", 1, true ) then | |||
suite = "monthsLong" | |||
elseif show:find( "M", 1, true ) then | |||
suite = "monthsAbbr" | |||
end | |||
bucket = World[ suite ] | |||
if bucket then | |||
m = bucket[ opts.lang:lower() ] | |||
if slang then | |||
x = bucket[ slang:lower() ] | |||
end | |||
if m then | |||
local base = m[ at.month ] | |||
local ex | |||
if x then | |||
ex = x[ at.month ] | |||
end | |||
if suite == "monthsAbbr" then | |||
local stop | |||
if ex then | |||
stop = x.suffix | |||
base = ex | |||
else | |||
stop = m.suffix | |||
end | |||
if base and stop then | |||
local shift, std | |||
std = string.format( "%s%%%s", | |||
base[ 1 ], stop ) | |||
shift = string.format( "%s%s", | |||
base[ 2 ], stop ) | |||
r = mw.ustring.gsub( r, std, shift ) | |||
end | |||
elseif suite == "monthsLong" then | |||
if base and ex then | |||
r = mw.ustring.gsub( r, base, ex ) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
if suffix then | |||
r = r .. suffix | |||
end | |||
end | |||
return r | return r | ||
end -- Private. | end -- Private.format() | ||
Private.former = function ( at ) | |||
-- | -- Analyze whether Julian calendar | ||
-- Parameter: | -- Parameter: | ||
-- | -- at -- table, to be evaluated | ||
-- Returns: | -- Returns: | ||
-- | -- true, i | ||
local r = | local r | ||
if at.year then | |||
if at.year < 1582 then | |||
r = true | |||
elseif at.year == 1582 then | |||
if at.month then | |||
if at.month < 10 then | |||
r = true | |||
elseif at.month == 10 then | |||
r = ( at.dom <= 15 ) | |||
end | |||
end | |||
end | |||
end | |||
return r | return r | ||
end -- | end -- Private.former() | ||
Private.from = function ( attempt ) | |||
-- | -- Create valid raw table from arbitrary table | ||
-- Parameter: | -- Parameter: | ||
-- | -- attempt -- table, to be evaluated | ||
-- Returns: | -- Returns: | ||
-- | -- table, with valid components, or nil | ||
local r = ( type( | local data = { } | ||
if | 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 | |||
local | return r | ||
end -- Private.from() | |||
Private.future = function ( add ) | |||
-- Normalize move interval | |||
-- Parameter: | |||
-- add -- string or number, to be added | |||
-- Returns: | |||
-- table, with shift, or false/nil | |||
local r | |||
if add then | |||
local s = type( add ) | |||
if s == "string" and add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then | |||
r = tonumber( add ) | |||
s = "number" | |||
end | |||
if s == "number" then | |||
if r == 0 then | |||
r = false | |||
else | |||
r = string.format( "%d second", r or add ) | |||
end | |||
elseif s == "string" then | |||
r = add | |||
else | |||
r = false | |||
end | |||
if r then | |||
r = Calc.future( r ) | |||
end | |||
end | |||
return r | |||
end -- Private.future() | |||
local | |||
Prototypes.clone = function ( self ) | |||
-- Clone object | |||
-- Parameter: | |||
-- self -- table, with object, to be cloned | |||
-- Returns: | |||
-- table, with object | |||
local r = { [ Meta.signature ] = self[ Meta.signature ] } | |||
setmetatable( r, Meta.tableI ) | |||
return r | |||
end -- Prototypes.clone() | |||
Prototypes.failsafe = function ( self, atleast ) | |||
-- Retrieve versioning and check for compliance | |||
-- Precondition: | |||
-- self -- table, or not, with DateTime object, unused | |||
-- atleast -- string, with required version | |||
-- or "wikidata" or "~" or "@" or false | |||
-- Postcondition: | |||
-- Returns string -- with queried version/item, also if problem | |||
-- false -- if appropriate | |||
-- 2020-08-17 | |||
local since = atleast | |||
local last = ( since == "~" ) | |||
local linked = ( since == "@" ) | |||
local link = ( since == "item" ) | |||
local r | |||
if last or link or linked or since == "wikidata" then | |||
local item = Meta.item | |||
since = false | |||
if type( item ) == "number" and item > 0 then | |||
local suited = string.format( "Q%d", item ) | |||
if linkedlink then | |||
r = suited | |||
else | |||
local entity = mw.wikibase.getEntity( suited ) | |||
if type( entity ) == "table" then | |||
local seek = Failsafe.serialProperty or "P348" | |||
local vsn = entity:formatPropertyValues( seek ) | |||
if type( vsn ) == "table" and | |||
type( vsn.value ) == "string" and | |||
vsn.value ~= "" then | |||
if last and | |||
vsn.value == ( Meta.serial or | |||
DateTime.serial ) then | |||
r = false | |||
elseif linked then | |||
if mw.title.getCurrentTitle().prefixedText | |||
== mw.wikibase.getSitelink( suited ) then | |||
r = false | |||
else | |||
r = suited | |||
end | end | ||
else | |||
r = vsn.value | |||
end | |||
if last then | |||
r = false | |||
else | |||
r = vsn.value | |||
end | end | ||
end | end | ||
end | end | ||
end | |||
end | end | ||
end | |||
if type( r ) == "nil" then | |||
if not since or since <= Meta.serial then | |||
r = Meta.serial | |||
else | |||
r = false | |||
end | |||
end | |||
return r | |||
end -- Prototypes.failsafe() | |||
Prototypes.fair = function ( self, access, assign ) | |||
-- Check formal validity of table | |||
-- Parameter: | |||
-- self -- table, to be checked | |||
-- access -- string or nil, single item to be checked | |||
-- assign -- single access value to be checked | |||
-- Returns: | |||
-- true, if valid; false, if not | |||
local r = ( type( self ) == "table" ) | |||
if r then | |||
local defs = { year = { max = MaxYear }, | |||
month = { min = 1, | |||
max = 12 }, | |||
week = { min = 1, | |||
max = 53 }, | |||
dom = { min = 1, | |||
max = 31 }, | |||
hour = { max = 23 }, | |||
min = { max = 59 }, | |||
sec = { max = 61 }, | |||
msec = { max = 999 }, | |||
mysec = { max = 999 } | |||
} | |||
local fNum = | |||
function ( k, v ) | |||
local ret = true | |||
local dk = defs[ k ] | |||
if dk then | |||
if type( dk.max ) == "number" then | |||
ret = ( type( v ) == "number" ) | |||
if ret then | |||
local min | |||
if dk.min then | |||
min = dk.min | |||
else | |||
min = 0 | |||
end | |||
ret = ( v >= min and v <= dk.max | |||
and math.floor( v ) == v ) | |||
if ret and dk.f then | |||
ret = dk.f( v ) | |||
end | |||
end | |||
end | end | ||
end | end | ||
end | return ret | ||
end -- fNum() | |||
if self.bc then | |||
local | defs.year.max = 999999 | ||
end | |||
defs.dom.f = | |||
function () | |||
local ret | |||
if | local d | ||
if not | if access == "dom" then | ||
d = assign | |||
else | |||
d = self.dom | |||
end | |||
if d then | |||
ret = ( d <= 28 ) | |||
if not ret then | |||
local m | |||
if access == "month" then | |||
m = assign | |||
else | |||
m = self.month | |||
end | |||
if m then | |||
ret = ( d <= Calc.months[ m ] ) | |||
if ret then | |||
local y | |||
if access == "year" then | |||
y = assign | |||
else | |||
y = self.year | |||
end | |||
if d == 29 and m == 2 and y then | |||
if y % 4 ~= 0 or | |||
( y % 100 == 0 and | |||
y % 400 ~= 0 ) then | |||
ret = false | |||
end | |||
end | |||
end | |||
end | end | ||
end | end | ||
else | |||
ret = true | |||
end | end | ||
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 | |||
end -- | 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" ) | |||
r = | |||
r = | |||
end | end | ||
end | 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 | end | ||
return r | return r | ||
end -- Prototypes. | end -- Prototypes.fair() | ||
Prototypes. | Prototypes.figure = function ( self, assign ) | ||
-- | -- Assign month by name | ||
-- Parameter: | -- Parameter: | ||
-- self | -- self -- table, to be filled | ||
-- | -- assign -- string, with month name | ||
-- Returns: | -- Returns: | ||
-- if | -- number 1...12, if valid; false, if not | ||
local r = false | |||
if type( self ) == "table" and type( assign ) == "string" then | |||
local | r = Parser.monthNumber( assign ) | ||
if type( self ) == "table" | if r then | ||
self.month = r | |||
end | |||
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 | |||
local | 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 | |||
r = ( | end | ||
end | |||
r = | |||
end | |||
end | |||
end | end | ||
else | |||
r = false | |||
end | |||
return r | |||
end -- Prototypes.first() | |||
Prototypes.fix = function ( self ) | |||
-- Adapt this object to local time if no explicit zone given | |||
-- Parameter: | |||
-- self -- table, with numbers etc. | |||
if type( self ) == "table" and | |||
not self.zone then | |||
local seconds = Prototypes.format( self, "Z" ) | |||
Prototypes.future( self, - tonumber( seconds ) ) | |||
end | |||
end -- Prototypes.fix() | |||
Prototypes.flow = function ( self, another, assert ) | |||
-- Compare this object with another timestamp | |||
-- Parameter: | |||
-- self -- table, with numbers etc. | |||
-- another -- DateTime or string or nil (now) | |||
-- assert -- nil, or string with operator | |||
-- "lt", "le", "eq", "ne", "ge", "gt", | |||
-- "<", "<=", "==", "~=", "<>", ">=", "=>", ">" | |||
-- Returns: | |||
-- if assert: true or false | |||
-- else: -1, 0, 1 | |||
-- nil if invalid | |||
local base, other, r | |||
if type( self ) == "table" then | |||
base = self | |||
other = another | |||
elseif type( another ) == "table" then | |||
base = another | |||
other = self | |||
end | end | ||
if base then | |||
if type( other ) ~= "table" then | |||
other = Meta.fiat( other ) | |||
if type( | |||
end | end | ||
if type( other ) == "table" then | |||
r = Private.flow( base, other ) | |||
if r and type( assert ) == "string" then | |||
if | 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 = ">" | |||
if | |||
if | |||
end | end | ||
r = ( same:find( s, 1, true ) ~= nil ) | |||
else | else | ||
r = nil | |||
end | end | ||
end | end | ||
end | |||
end | |||
end | end | ||
return r | return r | ||
end -- Prototypes. | end -- Prototypes.flow() | ||
Prototypes. | Prototypes.format = function ( self, ask, adapt ) | ||
-- | -- Format object as string | ||
-- Parameter: | -- Parameter: | ||
-- self -- table, | -- self -- table, with numbers etc. | ||
-- ask -- string, format spec, or nil | |||
-- table, with multiple formats | |||
-- string may contain multiple formats joined by "|||" | |||
-- adapt -- table, with options, or nil | |||
-- .lang -- string, with particular language code | |||
-- .london -- true: UTC output; default: local | |||
-- .lonely -- true: permit lonely hour | |||
-- Returns: | -- Returns: | ||
-- string, if defined; false, if not | -- string, or false, if invalid, or number for julian date | ||
local r | |||
if type( self ) == "table" then | |||
local s = type( ask ) | |||
local poly | |||
if s == "string" and ask:find( "|||", 1, true ) then | |||
poly = mw.text.split( ask, "|||" ) | |||
elseif s == "table" then | |||
poly = ask | |||
end | |||
if poly then | |||
r = "" | |||
for i = 1, #poly do | |||
r = r .. Private.field( self, poly[ i ], adapt ) | |||
end -- for i | |||
else | |||
r = Private.field( self, ask, adapt ) | |||
end | |||
end | |||
return r or false | |||
end -- Prototypes.format() | |||
Prototypes.full = function ( self ) | |||
-- Retrieve month name in current language | |||
-- Parameter: | |||
-- self -- table, to be evaluated | |||
-- Returns: | |||
-- string, if defined; false, if not | |||
local r | local r | ||
if type( self ) == "table" and self.month then | if type( self ) == "table" and self.month then | ||
Zeile 1.825: | Zeile 2.174: | ||
end | end | ||
if r then | if r then | ||
raw = r[ Meta.signature ] | if r[ Meta.signature ] then | ||
rel = Private.future( shift ) | raw = r[ Meta.signature ] | ||
else | |||
raw = r | |||
end | |||
if type( shift ) == "table" then | |||
rel = shift | |||
else | |||
rel = Private.future( shift ) | |||
end | |||
end | end | ||
if raw and rel then | if raw and rel then | ||
Zeile 1.850: | Zeile 2.207: | ||
-- Returns: | -- Returns: | ||
-- string | -- string | ||
local dels = { false, "", "-", "-", "", " | local dels = { false, "", "-", "-", "", "", ":", ":", ".", "" } | ||
local wids = { false, 4, 2, 2, 2, 2, | local wids = { false, 4, 2, 2, 2, 2, 2, 2, 3, 3 } | ||
local s = "" | local s = "" | ||
local n, r, spec | local n, r, spec | ||
Zeile 1.862: | Zeile 2.219: | ||
end | end | ||
end -- f() | end -- f() | ||
for i = 2, | for i = 2, 5 do | ||
f( i ) | f( i ) | ||
end -- for i | end -- for i | ||
r = s | r = s | ||
s = "" | s = "" | ||
for i = | for i = 6, 10 do | ||
f( i ) | f( i ) | ||
end -- for i | end -- for i | ||
Zeile 1.877: | Zeile 2.234: | ||
else | else | ||
r = string.format( "%sT%s", r, s ) | r = string.format( "%sT%s", r, s ) | ||
end | |||
end | |||
r = r:gsub( "%.$", "" ) | |||
if self.bc then | |||
if self.year then | |||
r = "-" .. r | |||
else | |||
r = r .. " BC" | |||
end | end | ||
end | end | ||
Zeile 1.948: | Zeile 2.313: | ||
if not ask or ask == "" then | if not ask or ask == "" then | ||
r1 = "c" | r1 = "c" | ||
elseif ask == "*" then | |||
if World.present then | |||
if assigned.hour then | |||
if assigned.dom or assigned.month or assigned.year then | |||
if World.present.both and | |||
World.present.date and | |||
World.present.time then | |||
r1 = World.present.both | |||
:gsub( "$date", World.present.date ) | |||
:gsub( "$time", World.present.time ) | |||
else | |||
r1 = World.present.date | |||
end | |||
end | |||
r1 = r1 or World.present.time | |||
else | |||
r1 = World.present.date | |||
end | |||
end | |||
r1 = r1 or "c" | |||
else | else | ||
local template = World.templates[ ask ] | local template = World.templates[ ask ] | ||
Zeile 1.956: | Zeile 2.341: | ||
if tmp then | if tmp then | ||
template = tmp[ ask ] | template = tmp[ ask ] | ||
end | |||
if not template then | |||
local i = slang:find( "-", 3, true ) | |||
if i then | |||
slang = slang:sub( 1, i - 1 ):lower() | |||
tmp = World.templates[ slang ] | |||
if tmp then | |||
template = tmp[ ask ] | |||
end | |||
end | |||
end | end | ||
end | end | ||
Zeile 1.977: | Zeile 2.372: | ||
end | end | ||
if template.lift and | if template.lift and | ||
(assigned.dom or | ( assigned.dom or | ||
not ( assigned.month or assigned.year or assigned.bc ) | |||
) then | ) then | ||
local stamp = false | local stamp = false | ||
Zeile 2.001: | Zeile 2.396: | ||
end | end | ||
end | end | ||
if low or ask:find( "hh:mm:ss" ) then | if low or ask:find( "hh:mm:ss", 1, true ) then | ||
if stamp then | if stamp then | ||
r1 = string.format( "%s %s", r1, stamp ) | r1 = string.format( "%s %s", r1, stamp ) | ||
end | end | ||
elseif ask:find( "hh:mm", 1, true ) and | |||
stamp and | |||
#stamp > 3 then | |||
r1 = string.format( "%s H:i", r1 ) | |||
end | end | ||
if stamp then | if stamp then | ||
Zeile 2.057: | Zeile 2.456: | ||
if #s == 1 then | if #s == 1 then | ||
-- "YXWVUTSRQPONZABCDEFGHIKLM" | -- "YXWVUTSRQPONZABCDEFGHIKLM" | ||
move = World.zones[ "!" ]:find( s ) | move = World.zones[ "!" ]:find( s, 1, true ) | ||
if move then | if move then | ||
move = ( move - 13 ) * 100 | move = ( move - 13 ) * 100 | ||
Zeile 2.072: | Zeile 2.471: | ||
if tmp then | if tmp then | ||
code = tmp[ s ] | code = tmp[ s ] | ||
end | |||
if not code and | |||
slang ~= "en" and | |||
World.zones.en then | |||
code = World.zones.en[ s ] | |||
end | end | ||
end | end | ||
Zeile 2.122: | Zeile 2.526: | ||
return r | return r | ||
end -- World.zones.formatter() | end -- World.zones.formatter() | ||
-- Export | -- Export | ||
local p = { } | local p = { } | ||
p.test = function ( args, alien ) | |||
local slang = args.lang | local slang = args.lang or alien | ||
local obj = Meta.fiat( args[ 1 ], false, args.shift ) | local obj = Meta.fiat( args[ 1 ], false, args.shift ) | ||
local r | local r | ||
if type( obj ) == "table" then | if type( obj ) == "table" then | ||
local spec = args[ 2 ] | local spec = args[ 2 ] | ||
local opt | local opt | ||
if spec then | if spec then | ||
spec = mw.text.trim( spec ) | spec = mw.text.trim( spec ) | ||
end | end | ||
if slang then | if slang then | ||
opt = { lang = mw.text.trim( slang ) } | opt = { lang = mw.text.trim( slang ) } | ||
end | end | ||
r = obj:format( spec, opt ) | r = obj:format( spec, opt ) | ||
else | else | ||
r = ( args.noerror or "0" ) | r = ( args.noerror or "0" ) | ||
if r == "0" then | if r == "0" then | ||
r = fault( "Format | r = fault( "Format invalid" ) | ||
else | else | ||
r = "" | r = "" | ||
end | |||
if args.errCat then | |||
local cats = mw.text.split( args.errCat, "%s*|%s*" ) | |||
for i = 1, #cats do | |||
r = string.format( "%s[[Category:%s]]", r, cats[ i ] ) | |||
end -- for i | |||
end | end | ||
end | end | ||
Zeile 2.155: | Zeile 2.565: | ||
p.failsafe = function ( frame ) | |||
local s = type( frame ) | local s = type( frame ) | ||
local r, since | local r, since | ||
Zeile 2.161: | Zeile 2.571: | ||
since = frame.args[ 1 ] | since = frame.args[ 1 ] | ||
elseif s == "string" then | elseif s == "string" then | ||
since = mw.text.trim( since ) | since = mw.text.trim( since ) | ||
if since == "" then | if since == "" then | ||
Zeile 2.169: | Zeile 2.576: | ||
end | end | ||
end | end | ||
return Prototypes.failsafe( false, since ) or "" | |||
end -- p.failsafe | end -- p.failsafe | ||
p.format = function ( frame ) | |||
-- 1 -- stamp | -- 1 -- stamp | ||
-- 2 -- spec | -- 2 -- spec | ||
Zeile 2.193: | Zeile 2.591: | ||
frame.args[ 2 ], | frame.args[ 2 ], | ||
shift = frame.args.shift, | shift = frame.args.shift, | ||
noerror = frame.args.noerror } | noerror = frame.args.noerror, | ||
errCat = frame.args.errCat } | |||
if not v[ 1 ] or v[ 1 ] == "now" then | if not v[ 1 ] or v[ 1 ] == "now" then | ||
v[ 1 ] = frame:callParserFunction( "#timel", "c", v.shift ) | v[ 1 ] = frame:callParserFunction( "#timel", "c", v.shift ) | ||
Zeile 2.208: | Zeile 2.607: | ||
p.lt = function ( frame ) | |||
return Templates.flow( frame, "lt" ) | return Templates.flow( frame, "lt" ) | ||
end -- p.lt | end -- p.lt | ||
p.le = function ( frame ) | |||
return Templates.flow( frame, "le" ) | return Templates.flow( frame, "le" ) | ||
end -- p.le | end -- p.le | ||
p.eq = function ( frame ) | |||
return Templates.flow( frame, "eq" ) | return Templates.flow( frame, "eq" ) | ||
end -- p.eq | end -- p.eq | ||
p.ne = function ( frame ) | |||
return Templates.flow( frame, "ne" ) | return Templates.flow( frame, "ne" ) | ||
end -- p.ne | end -- p.ne | ||
p.ge = function ( frame ) | |||
return Templates.flow( frame, "ge" ) | return Templates.flow( frame, "ge" ) | ||
end -- p.ge | end -- p.ge | ||
p.gt = function ( frame ) | |||
return Templates.flow( frame, "gt" ) | return Templates.flow( frame, "gt" ) | ||
end -- p.gt | end -- p.gt |
Die Dokumentation für dieses Modul kann unter Modul:DateTime/Doku erstellt werden
local DateTime = { serial = "2020-09-30", suite = "DateTime", item = 20652535 } -- Date and time objects local Failsafe = DateTime local GlobalMod = DateTime local Calc = { } local Meta = { } local Parser = { } local Private = { } local Prototypes = { } local Templates = { } local World = { slang = "en", monthsLong = { }, monthsParse = { }, months4 = { } } local MaxYear = 2099 local Frame DateTime.char = { nbsp = mw.ustring.char( 160 ), tab = mw.ustring.char( 9 ) } World.era = { en = { "BC", "AD" } } World.monthsAbbr = { en = { n = 3 } } World.monthsLong.en = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" } World.monthsParse.en = { [ "Apr" ] = 4, [ "Aug" ] = 8, [ "Dec" ] = 12, [ "Feb" ] = 2, [ "Jan" ] = 1, [ "Jul" ] = 7, [ "Jun" ] = 6, [ "Mar" ] = 3, [ "May" ] = 5, [ "Nov" ] = 11, [ "Oct" ] = 10, [ "Sep" ] = 9 } World.months4.en = { [ 6 ] = true, [ 7 ] = true } World.templates = { [ "ISO" ] = { spec = "Y-m-d", lift = true }, [ "ISO-T" ] = { spec = "c" }, [ "timestamp" ] = { spec = "YmdHis" }, [ "default" ] = { spec = "H:i, j 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 foreignModule = function ( access, advanced, append, alt, alert ) -- Fetch global module -- Precondition: -- access -- string, with name of base module -- advanced -- true, for require(); else mw.loadData() -- append -- string, with subpage part, if any; or false -- alt -- number, of wikidata item of root; or false -- alert -- true, for throwing error on data problem -- Postcondition: -- Returns whatever, probably table -- 2020-01-01 local storage = access local fun, lucky, r if advanced then fun = require else fun = mw.loadData end if append then storage = string.format( "%s/%s", storage, append ) end lucky, r = pcall( fun, "Module:" .. storage ) if not lucky then local suited GlobalMod.globalModules = GlobalMod.globalModules or { } suited = GlobalMod.globalModules[ access ] if not suited and type( alt ) == "number" and alt > 0 then suited = string.format( "Q%d", alt ) suited = mw.wikibase.getSitelink( suited ) GlobalMod.globalModules[ access ] = suited or true end if type( suited ) == "string" then storage = suited if append then storage = string.format( "%s/%s", storage, append ) end lucky, r = pcall( fun, storage ) end if not lucky and alert then error( "Missing or invalid page: " .. storage ) end end return r end -- foreignModule() local function capitalize( a ) -- Upcase first character, downcase anything else -- Parameter: -- a -- string -- Returns: -- string return mw.ustring.upper( mw.ustring.sub( a, 1, 1 ) ) .. mw.ustring.lower( mw.ustring.sub( a, 2 ) ) end -- capitalize() local function fault( a ) -- Format error message by class=error -- Parameter: -- a -- string, error message -- Returns: -- string, HTML span local e = mw.html.create( "span" ) :addClass( "error" ) :wikitext( a ) return tostring( e ) end -- fault() local function frame() if not Frame then Frame = mw.getCurrentFrame() end return Frame end -- frame() Meta.localized = false Meta.serial = DateTime.serial Meta.signature = "__datetime" Meta.suite = "{DateTime}" Meta.components = { lang = "string", bc = "boolean", year = "number", month = "number", week = "number", dom = "number", hour = "number", min = "number", sec = "number", msec = "number", mysec = "number", zone = false, leap = "boolean", jul = "boolean" } Meta.order = { "bc", "year", "month", "week", "dom", "hour", "min", "sec", "msec", "mysec" } Meta.tableI = { -- instance metatable __index = function ( self, access ) local r = self[ Meta.signature ][ access ] if r == nil then if access == "serial" then r = Meta.serial elseif access == "suite" then r = "DateTime" else r = Prototypes[ access ] end end return r end, __newindex = function ( self, access, assign ) if type( access ) == "string" then local data = self[ Meta.signature ] if assign == nil then local val = data[ access ] data[ access ] = nil if not Prototypes.fair( data ) then data[ access ] = val end elseif Prototypes.fair( data, access, assign ) then data[ access ] = assign end end return end, __add = function ( op1, op2 ) return Prototypes.future( op1, op2, true ) end, __eq = function ( op1, op2 ) return Prototypes.flow( op1, op2, "eq" ) end, __lt = function ( op1, op2 ) return Prototypes.flow( op1, op2, "lt" ) end, __le = function ( op1, op2 ) return Prototypes.flow( op1, op2, "le" ) end, __tostring = function ( e ) return Prototypes.tostring( e ) end, __call = function ( func, ... ) return Meta.fiat( ... ) end } -- Meta.tableI Meta.tableL = { -- library metatable __index = function ( self, access ) local r if access == "serial" then r = Meta.serial elseif access == "suite" then r = Meta.suite end return r end, __newindex = function () return end, __tostring = function () return Meta.suite end, __call = function ( func, ... ) return Meta.fiat( ... ) end } -- Meta.tableL Meta.fiat = function ( assign, alien, add ) -- Create instance object (constructor) -- Parameter: -- assign -- string, with initial timestamp, or nil -- nil -- now -- false -- empty object -- table -- clone this object, or copy from raw -- ignore remaining parameters -- alien -- string, with language code, or nil -- add -- string, with interval (PHP strtotime), or nil -- Returns: -- table, as DateTime object -- string or false, if failed local r Private.foreign() if type( assign ) == "table" then if assign.suite == Meta.suite and getmetatable( assign ) == Meta.tableI then r = assign[ Meta.signature ] else r = Private.from( assign ) end else r = Private.factory( assign, alien, add ) end if type( r ) == "table" then r = { [ Meta.signature ] = r } setmetatable( r, Meta.tableI ) end return r end -- Meta.fiat() setmetatable( DateTime, Meta.tableL ) DateTime.serial = nil Calc.months = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -- Calc.fast = function ( at ) -- -- Quick scan of full ISO stamp -- -- Parameter: -- -- apply -- string, ISO -- -- Returns: -- -- table, with numeric components -- local r = { } -- r.year = tonumber( at:sub( 1, 4 ) ) -- r.month = tonumber( at:sub( 6, 2 ) ) -- r.dom = tonumber( at:sub( 9, 2 ) ) -- r.hour = tonumber( at:sub( 12, 2 ) ) -- r.min = tonumber( at:sub( 14, 2 ) ) -- r.sec = tonumber( at:sub( 17, 2 ) ) -- if at:sub( 19, 1 ) == "." then -- r.msec = tonumber( at:sub( 20, 3 ) ) -- if #at > 22 then -- r.mysec = tonumber( at:sub( 23, 3 ) ) -- end -- end -- return r -- end -- Calc.fast() Calc.fair = function ( adjust ) -- Normalize numeric components -- Parameter: -- adjust -- table, with raw numbers local ranges = { year = { min = -999, max = 9999 }, month = { min = 1, max = 12, mod = 12 }, dom = { min = 1, max = 28 }, hour = { mod = 24 }, min = { mod = 60 }, sec = { mod = 60 }, msec = { mod = 1000 }, mysec = { mod = 1000 } } local m, max, min, move, n, range, s for i = 10, 2, -1 do s = Meta.order[ i ] n = adjust[ s ] if n or move then range = ranges[ s ] if range then min = range.min or 0 max = range.max or ( range.mod - 1 ) if move then n = ( n or 0 ) + move move = false end if n < min or n > max then if range.mod then m = n % range.mod move = ( n - m ) / range.mod n = min + m else -- dom if adjust.month and adjust.year and adjust.month >= 1 and adjust.month <= 12 and adjust.year > 1900 then if n > 0 then max = Calc.final( adjust ) while n > max do n = n - max if adjust.month < 12 then adjust.month = adjust.month + 1 else adjust.month = 1 adjust.year = adjust.year + 1 end max = Calc.final( adjust ) end -- while n <= max else while n < 1 do if adjust.month == 1 then adjust.month = 12 adjust.year = adjust.year - 1 else adjust.month = adjust.month - 1 end max = Calc.final( adjust ) n = n + max end -- while n < 1 end end end end adjust[ s ] = n end end end -- for i end -- Calc.fair() Calc.final = function ( adjust ) -- Retrieve number of days in particular month -- Parameter: -- adjust -- table, with date specification -- Returns: -- number, of days in month local r = Calc.months[ adjust.month ] if adjust.month == 2 and ( adjust.year % 4 ~= 0 or adjust.year % 400 == 0 ) then r = 28 end return r end -- Calc.final() Calc.future = function ( add ) -- Parse move interval -- Parameter: -- add -- string, with GNU relative items -- Returns: -- table, with numeric components, or false local r, token local units = { year = true, month = true, fortnight = { slot = "dom", mult = 14 }, week = { slot = "dom", mult = 7 }, dom = true, hour = true, min = true, sec = true } local story = string.format( " %s ", add:lower() ) :gsub( "%s+", " " ) :gsub( " yesterday ", " -1 dom " ) :gsub( " tomorrow ", " 1 dom " ) :gsub( "(%l)s ", "%1 " ) :gsub( " day ", " dom " ) :gsub( " minute ", " min " ) :gsub( " second ", " sec " ) local feed = function () local slice token, slice = story:match( "^( (%S+)) " ) return slice end local fed = function () story = story:sub( #token + 1 ) end local m, n, s, u while true do s = feed() if s then n = 1 if s:match( "^[+-]?%d+$" ) then n = tonumber( s ) fed() s = feed() end if s then u = units[ s ] end if s and u then fed() if u ~= true then s = u.slot n = n * u.mult end if feed() == "ago" then if n > 0 then n = - n end fed() end r = r or { } r[ s ] = ( r[ s ] or 0 ) + n else r = false break -- while true end else break -- while true end end -- while true return r end -- Calc.future() Parser.digitsHeading = function ( analyse, alone, amount, add ) -- String analysis, if digits only or at least 4 digits heading -- Parameter: -- analyse -- string to be scanned, starting with digit -- digits only, else starting with exactly 4 digits -- alone -- true, if only digits -- amount -- number of heading digits -- add -- table, to be extended -- Returns: -- table, extended if parsed -- false, if invalid text format local r = add if alone then -- digits only if amount <= 4 then r.year = tonumber( analyse ) elseif amount == 14 then -- timestamp r.year = tonumber( analyse:sub( 1, 4 ) ) r.month = tonumber( analyse:sub( 5, 6 ) ) r.dom = tonumber( analyse:sub( 7, 8 ) ) r.hour = tonumber( analyse:sub( 9, 10 ) ) r.min = tonumber( analyse:sub( 11, 12 ) ) r.sec = tonumber( analyse:sub( 13, 14 ) ) else r = false end elseif amount == 4 then local s, sep, sx = analyse:match( "^(%d+)([%-%.:Ww]?)(.*)$" ) r.year = tonumber( s ) if sep == "-" then -- ISO s, sep, sx = sx:match( "^(%d%d)(-?)(.*)$" ) if s then r.month = tonumber( s ) r.month2 = true if sep == "-" then s, sep, sx = sx:match( "^(%d%d?)([ T]?)(.*)$" ) if s then r.dom = tonumber( s ) if sep == "T" then r.month2 = nil else r.dom2 = ( #s == 2 ) end if sep then r = Parser.time( sx, r, sep == "T" ) end else r = false end elseif sx and sx ~= "" then r = false end else r = false end elseif sep:lower() == "w" then if sx then s = sx:match( "^(%d%d?)$" ) if s then r.week = tonumber( s ) if r.week < 1 or r.week > 53 then r = false end else r = false end else r = false end else r = false end if r then r.iso = true end elseif amount == 8 then -- ISO compact local s, sz = analyse:match( "^%d+T(%d+)([.+-]?%d*%a*)$" ) if s then local n = #s if n == 2 or n == 4 or n == 6 then r.year = tonumber( analyse:sub( 1, 4 ) ) r.month = tonumber( analyse:sub( 5, 6 ) ) r.dom = tonumber( analyse:sub( 7, 8 ) ) r.hour = tonumber( analyse:sub( 10, 11 ) ) if n > 2 then r.min = tonumber( s:sub( 3, 4 ) ) if n == 6 then r.sec = tonumber( s:sub( 5, 6 ) ) end n, s = sz:match( "^(%.%d+)([+-]?[%a%d]*)$" ) if n then n = n .. "00" r.msec = tonumber( n:sub( 1, 3 ) ) if #n >= 6 then r.mysec = tonumber( n:sub( 4, 6 ) ) end sz = s end end if sz ~= "" then s, sz = sz:match( "^([+-]?)(%a*)$" ) if s == "" then if sz:match( "^(%u)$" ) then r.zone = sz else s = false end elseif #s == 1 then r.zone = s .. sz else s = false end end else s = false end end if s then r = false end end return r end -- Parser.digitsHeading() Parser.eraGermanEnglish = function ( analyse ) -- String analysis, for German and English era -- v. Chr. v. u. Z. n. Chr. AD BC A.D. B.C. B.C.E. -- Parameter: -- analyse -- string -- Returns: -- 1 -- table, with boolean era, if any -- 2 -- string, with era stripped off, if any local rO = { } local rS = analyse local s, switch = analyse:match( "^(.+) ([vn])%. ?Chr%.$" ) if switch then rS = s rO.bc = ( switch == "v" ) elseif analyse:find( " v%. ?u%. ?Z%.$" ) then rS = analyse:match( "^(.+) v%. ?u%. ?Z%.$" ) rO.bc = true elseif analyse:find( " B%.? ?C%.? ?E?%.?$" ) then rS = analyse:match( "^(.+) B%.? ?C%.? ?E?%.?$" ) rO.bc = true elseif analyse:find( "^A%.? ?D%.? " ) then rS = analyse:match( "^A%.? ?D%.? (.*)$" ) rO.bc = false end return rO, rS end -- Parser.eraGermanEnglish() Parser.european = function ( ahead, adhere, analyse, assign ) -- String analysis, retrieve date style: DOM MONTH YEAR -- Parameter: -- ahead -- string, with first digits, not more than 2 -- adhere -- string, with first separator; not ":" -- analyse -- string, remainder following adhere -- assign -- table -- Returns: -- table, extended if parsed local r = assign local s, s2, sx if adhere == "." or adhere == ". " then -- 23.12.2013 -- 23. Dezember 2013 s, sx = analyse:match( "^(%d%d?)%.(.*)$" ) if s then r = Parser.putDate( false, s, ahead, assign ) r = Parser.yearTime( sx, r ) else s, sx = mw.ustring.match( analyse, "^ ?([%a&;]+%.?) ?(.*)$" ) if s then local n = Parser.monthNumber( s ) if n then r.month = n r.dom = tonumber( ahead ) r.dom2 = ( #ahead == 2 ) r = Parser.yearTime( sx, r ) else r = false end else r = false end end elseif adhere == " " then -- 23 Dec 2013 s, sx = mw.ustring.match( analyse, "^([%a&;]+%.?) ?(.*)$" ) if s then local n = Parser.monthNumber( s ) if n then r.month = n r.dom = tonumber( ahead ) r.dom2 = ( #ahead == 2 ) r = Parser.yearTime( sx, r ) else r = false end else r = false end else r = false end return r end -- Parser.european() Parser.isoDate = function ( analyse, assign ) -- String analysis, retrieve month heading ISO date -- Parameter: -- analyse -- string, with heading hyphen -- assign -- table -- Returns: -- 1 -- table, extended if parsed -- 2 -- stripped string, or false, if invalid text format local rO, rS if analyse:match( "^%-%-?[0-9]" ) then local n, s rO = assign rS = analyse:sub( 2 ) s = rS:match( "^([012][0-9])%-" ) if s then n = tonumber( s ) if n >= 1 and n <= 12 then rO.month = n rS = rS:sub( 3 ) else rO = false end end if rO then if rS:byte( 1, 1 ) == 45 then local suffix s = rS:match( "^%-([0-3][0-9])" ) if s then n = tonumber( s ) if n >= 1 and n <= 31 then rO.dom = n rS = rS:sub( 4 ) else rO = false end else rS:sub( 2 ) end else rO = false end if rO then if #rS > 0 then if rO.dom then n = rS:byte( 1, 1 ) if n == 32 or n == 84 then rS = rS:sub( 2 ) else rO = false end else rO = false end end end end else rO = false end if rO then rO.iso = true else rS = false end return rO, rS end -- Parser.isoDate() Parser.monthHeading = function ( analyse, assign ) -- String analysis, retrieve month heading date (US only) -- Parameter: -- analyse -- string, with heading word -- assign -- table -- Returns: -- 1 -- table, extended if parsed -- 2 -- stripped string, or false, if invalid text format local rO = assign local rS = analyse local s, sep = mw.ustring.match( analyse, "^([%a&;]+%.?)([^%a%.]?)" ) if s then -- might begin with month name "December 23, 2013" local n = Parser.monthNumber( s ) if n then rO.month = n if sep == "" then rS = "" else local s2, s3 n = mw.ustring.len( s ) + 1 s = mw.ustring.sub( analyse, n ) s2 = s:match( "^ (%d%d%d?%d?)$" ) if s2 then rO.year = tonumber( s2 ) rS = "" else s2, s3, rS = s:match( "^ (%d+), (%d+)( ?.*)$" ) if s2 and s3 then n = #s2 if n <= 2 and #s3 == 4 then rO.dom = tonumber( s2 ) rO.year = tonumber( s3 ) rO.dom2 = ( n == 2 ) else rO = false end else rO = false end end end else rO = false end else rO = false end if not rO then rS = false end return rO, rS end -- Parser.monthHeading() Parser.monthNumber = function ( analyse ) -- String analysis, retrieve month number -- Parameter: -- analyse -- string, with month name including any period -- Returns: -- number, 1...12 if found -- false or nil, if not detected local r = false local s = mw.ustring.match( analyse, "^([%a&;]+)%.?$" ) if s then local given s = capitalize( s ) for k, v in pairs( World.monthsLong ) do given = World.monthsParse[ k ] if given then r = given[ s ] end if not r then given = World.monthsLong[ k ] for i = 1, 12 do if given[ i ] == s then r = i break end end -- for i end if r then break end end -- for k, v end return r end -- Parser.monthNumber() Parser.putDate = function ( aYear, aMonth, aDom, assign ) -- Store date strings -- Parameter: -- aYear -- string, with year, or false -- aMonth -- string, with numeric month -- aDom -- string, with day of month -- assign -- table -- Returns: -- table, extended local r = assign if aYear then r.year = tonumber( aYear ) end r.month = tonumber( aMonth ) r.dom = tonumber( aDom ) r.month2 = ( #aMonth == 2 ) r.dom2 = ( #aDom == 2 ) return r end -- Parser.putDate() Parser.time = function ( analyse, assign, adjusted ) -- String analysis, retrieve time components -- Parameter: -- analyse -- string, with time part -- assign -- table -- adjusted -- true: fixed length of 2 digits expected -- Returns: -- table, extended if parsed -- false, if invalid text format local r = assign if analyse ~= "" then local s, sx = analyse:match( "^(%d+)(:?.*)$" ) if s then local n = #s if n <= 2 then r.hour = tonumber( s ) if not adjusted then r.hour2 = ( n == 2 ) end else sx = false r = false end if sx then s, sx = sx:match( "^:(%d+)(:?(.*))$" ) if s then if #s == 2 then r.min = tonumber( s ) if sx == "" then sx = false end else sx = false r = false end if sx then local sep local scan = "^([:,] ?)(%d+)(.*)$" sep, s, sx = sx:match( scan ) if sep == ":" then if #s == 2 then r.sec = tonumber( s ) end elseif sep == ", " then r = Parser.wikiDate( s .. sx, r ) sx = false else r = false end end else r = false end end if sx and sx ~= "" then s = sx:match( "^%.(%d+)$" ) if s then s = s .. "00" r.msec = tonumber( s:sub( 1, 3 ) ) if #s >= 6 then r.mysec = tonumber( s:sub( 4, 6 ) ) end else r = false end end else r = false end end return r end -- Parser.time() Parser.wikiDate = function ( analyse, assign ) -- String analysis, for date after wiki ~~~~~ signature time -- dmy 10:28, 30. Dez. 2013 -- ymd 10:28, 2013 Dez. 30 -- Parameter: -- analyse -- string -- assign -- table -- Returns: -- table, extended if parsed -- false, if invalid text format local r local s = analyse:match( "^(2%d%d%d) " ) local sx if s then -- ymd "10:28, 2013 Dez. 30" local n = false r = assign r.year = tonumber( s ) s = analyse:sub( 6 ) s, sx = mw.ustring.match( analyse:sub( 6 ), "^([%a&;]+)%.? (%d%d?)$" ) if s then n = Parser.monthNumber( s ) if n then r.month = n end end if n then r.dom = tonumber( sx ) r.dom2 = ( #sx == 2 ) else r = false end else -- dmy "10:28, 30. Dez. 2013" local sep s, sep, sx = analyse:match( "^(%d%d?)(%.? ?)(%a.+)$" ) if s then r = Parser.european( s, sep, sx, assign ) else r = false end end return r end -- Parser.wikiDate() Parser.yearTime = function ( analyse, assign ) -- String analysis, for possible year and possible time -- Parameter: -- analyse -- string, starting with year -- assign -- table -- Returns: -- table, extended if parsed -- false, if invalid text format local r = assign local n = #analyse if n > 0 then local s, sx if n == 4 then if analyse:match( "^%d%d%d%d$" ) then s = analyse sx = false end else s = analyse:match( "^(%d%d%d%d)[ ,]" ) if s then sx = analyse:sub( 5 ) else local suffix s, sx, suffix = analyse:match( "^(%d+)([ ,]?)(.*)$" ) if s then local j = #sx n = #s if n < 4 and ( j == 1 or #suffix == 0 ) then sx = analyse:sub( n + j ) else s = false end end end end if s then r.year = tonumber( s ) if sx then s, sx = sx:match( "^(,? ?)(%d.*)$" ) if #s >= 1 then r = Parser.time( sx, r ) end end else r = false end end return r end -- Parser.yearTime() Parser.zone = function ( analyse, assign ) -- String analysis, for time zone -- +/-nn +/-nnnn (AAAa) -- Parameter: -- analyse -- string -- assign -- table -- Returns: -- 1 -- table, with number or string zone, if any, or false -- 2 -- string, with zone stripped off, if any local rO = assign local rS = analyse local s, sign, shift, sub s = "^(.+)([+-])([01]%d):?(%d?%d?)$" s, sign, shift, sub = analyse:match( s ) if sign then if s:find( ":%d%d *$" ) then if sub then if #sub == 2 then rO.zone = tonumber( shift .. sub ) else rO = false end else rO.zone = tonumber( shift ) * 100 end if rO then if sign == "-" then rO.zone = - rO.zone end rS = mw.text.trim( s ) end end elseif analyse:find( "%(.*%)$" ) then s, shift = analyse:match( "^(.+)%((%a%a%a%a?)%)$" ) if shift then rO.zone = shift:upper() rS = mw.text.trim( s ) else rO = false end else s, shift = analyse:match( "^(.+%d) ?(%a+)$" ) if shift then local n = #shift if n == 1 then rO.zone = shift:upper() elseif n == 3 then if shift == "UTC" or shift == "GMT" then rO.zone = 0 end end if rO.zone then rS = s end end end return rO, rS end -- Parser.zone() Parser.GermanEnglish = function ( analyse ) -- String analysis, for German and English formats -- Parameter: -- analyse -- string, with date or time or parts of it -- Returns: -- table, if parsed -- false, if invalid text format local r, s = Parser.eraGermanEnglish( analyse ) r, s = Parser.zone( s, r ) if r then local start, sep, sx = s:match( "^(%d+)([ %-%.:WwT]?)(.*)$" ) if start then -- begins with one or more digits (ASCII) local n = #start local lazy = ( start == s and ( n >=4 or type( r.bc == "boolean" ) ) ) if n == 4 or n == 8 or lazy then r = Parser.digitsHeading( s, lazy, n, r ) elseif n <= 2 then if sep == ":" then r, s = Parser.time( s, r ) elseif sep == "" then r = false else r = Parser.european( start, sep, sx, r ) end else r = false end else local rM, sM = Parser.monthHeading( s, r ) if rM then r = rM else r, sM = Parser.isoDate( s, r ) end if r and sM ~= "" then r = Parser.time( sM, r ) end end end return r end -- Parser.GermanEnglish() Private.factory = function ( assign, alien, add ) -- Create DateTime table (constructor) -- Parameter: -- assign -- string, with initial timestamp, or nil -- nil -- now -- false -- empty object -- alien -- string, with language code, or nil -- add -- string, with interval (PHP strtotime), or nil -- Returns: -- table, for DateTime object -- string or false, if failed local l = true local slang = mw.text.trim( alien or World.slang or "en" ) local r if assign == false then r = { } else local stamp = ( assign or "now" ) local shift if add then shift = Private.future( add ) end r = false if stamp == "now" then stamp = frame():callParserFunction( "#timel", "c", shift ) shift = false else local seconds = stamp:match( "^#(%d+)$" ) if seconds then stamp = os.date( "!%Y-%m-%dT%H:%M:%S", tonumber( seconds ) ) end end l, r = pcall( Private.fetch, stamp, slang, shift ) end if l and type( r ) == "table" then if slang ~= "" then r.lang = slang end end return r end -- Private.factory() Private.fetch = function ( analyse, alien, add ) -- Retrieve object from string -- Parameter: -- analyse -- string to be interpreted -- alien -- string with language code, or nil -- add -- table, with interval, or nil -- Returns: -- table, if parsed -- false, if invalid text format -- string, if serious error (args) local r if type( analyse ) == "string" then local strip = mw.ustring.char( 0x5B, 0x200E, 0x200F, 0x5D ) r = analyse:gsub( " ", " " ) :gsub( " ", " " ) :gsub( "&#x[aA]0;", " " ) :gsub( " ", " " ) :gsub( DateTime.char.nbsp, " " ) :gsub( DateTime.char.tab, " " ) :gsub( " +", " " ) :gsub( "%[%[", "" ) :gsub( "%]%]", "" ) :gsub( strip, "" ) r = mw.text.trim( r ) if r == "" then r = { } else local slang = ( alien or "" ) local parser = { en = "GermanEnglish", de = "GermanEnglish", frr = "GermanEnglish", nds = "GermanEnglish" } local suitable if slang == "" then slang = "en" else local s = slang:match( "^(%a+)%-" ) if s then slang = s end end slang = slang:lower() suitable = parser[ slang ] if suitable then local l l, r = pcall( Parser[ suitable ], r ) if l and r then if not Prototypes.fair( r ) then r = false elseif add then r = Prototypes.future( r, add ) end else r = "invalid format" end else r = "unknown language: " .. slang end end else r = "bad type" end return r end -- Private.fetch() Private.field = function ( at, ask, adapt, atleast ) -- Format object as string -- Parameter: -- at -- DateTime -- ask -- string, with format spec, or nil -- adapt -- table, with options, or nil -- .lang -- string, with particular language code -- .london -- true: UTC output; default: local -- .lonely -- true: permit lonely hour -- atleast -- string, with default value, or nil -- Returns: -- string, or false, if invalid, or number for julian date local r, spec if type( ask ) == "string" then if ask:sub( 1, 1 ) == "$" then if ask:sub( 1, 11 ) == "$JulianDate" then local luxury = ( ask:sub( -2 ) == ",$" ) if ask:sub( 1, 14 ) == "$JulianDateJul" then at.legacy = true elseif ask:sub( 1, 15 ) == "$JulianDateGreg" then else at.legacy = Private.former( at ) end r = Private.fixed( at, luxury ) elseif ask:sub( 1, 11 ) == "$JulianCal$" then adapt.legacy = true spec = ask:sub( 12 ) elseif ask:sub( 1, 3 ) == "$\"$" then r = ask:sub( 4 ) else spec = ask end else spec = ask end else spec = false end if not r then r = Private.format( at, spec, adapt ) end return r or atleast end -- Private.field() Private.fixed = function ( at, advanced ) -- Compute julian date -- Parameter: -- at -- DateTime -- .legacy -- true: at is in Julian calendar -- advanced -- true: format long number -- Returns: -- number, or string local mM, mMY, mY, nY, r if at.year then mY = at.year * 12 else -- actually invalid mY = 0 end if at.month then mMY = at.month else mMY = 1 end mMY = mMY + 57609 if at.dom then r = at.dom else r = 1 end mM = ( mY + mMY ) * 0.08333333333 -- divided by 12 months nY = math.floor( mM - 1 ) r = math.floor( nY * 365.25 ) + math.floor( ( mMY%12 + 4 ) * 30.6 ) + r if at.legacy then r = r - 32205.5 else r = r - math.floor( nY * 0.01 ) -- no leap day in century + math.floor( nY * 0.0025 ) -- but every 400 years - 32167.5 end if at.hour then -- divided by 24 hours per day r = r + at.hour * 0.0416666666666667 else r = r + 0.5 end if at.min then -- divided by 1440 minutes per day r = r + at.min * 0.000694444444 end if at.sec then -- divided by 86400 seconds per day r = r + at.min * 0.00001157407407 end if at.bc then r = 3442406 - r if at.legacy then r = r + 3 end end if advanced then local slang = ( at.lang or World.slang ) local o = mw.language.new( slang ) r = o:formatNum( r ) end return r end -- Private.fixed() Private.flow = function ( at1, at2 ) -- Compare two objects -- Parameter: -- at1 -- DateTime -- at2 -- DateTime -- Returns: -- -1, 0, 1 or nil if not comparable local r = 0 if at1.bc or at2.bc and at1.bc ~= at2.bc then if at1.bc then r = -1 else r = 1 end else local life = false local s, v1, v2 for i = 2, 10 do s = Meta.order[ i ] v1 = at1[ s ] v2 = at2[ s ] if v1 or v2 then if v1 and v2 then if v1 < v2 then r = -1 elseif v1 > v2 then r = 1 end elseif life then if v2 then r = -1 else r = 1 end else r = nil end if r ~= 0 then if at1.bc and r then r = r * -1 end break -- for i end life = true end end -- for i end return r end -- Private.flow() Private.foreign = function () -- Retrieve localization submodule if not Meta.localized then local d = foreignModule( DateTime.suite, false, "local", DateTime.item ) if type( d ) == "table" then local wk if d.slang then Meta.suite = string.format( "%s %s", Meta.suite, d.slang ) World.slang = d.slang end for k, v in pairs( d ) do wk = World[ k ] if wk and wk.en then for subk, subv in pairs( v ) do wk[ subk ] = subv end -- for k, v else World[ k ] = v end end -- for k, v end Meta.localized = true end end -- Private.foreign() Private.format = function ( at, ask, adapt ) -- Format object as string -- Parameter: -- at -- table, with numbers etc. -- ask -- string, format spec, or nil -- adapt -- table, with options, or nil -- .lang -- string, with particular language code -- .london -- true: UTC output; default: local -- .lonely -- true: permit lonely hour -- Returns: -- string, or not local slang = at.lang or "en" local opts = { lang = slang } local babel, r if type( adapt ) == "table" then if type( adapt.lang ) == "string" then local i = adapt.lang:find( "-", 3, true ) if i then slang = adapt.lang:lower() opts.lang = slang:sub( 1, i - 1 ) else opts.lang = adapt.lang:lower() end end opts.london = adapt.london opts.lonely = adapt.lonely end babel = mw.language.new( opts.lang:lower() ) if babel then local shift, show, stamp, suffix, limit4, locally if at.month then stamp = World.monthsLong.en[ at.month ] if at.year then stamp = string.format( "%s %04d", stamp, at.year ) end if at.dom then stamp = string.format( "%d %s", at.dom, stamp ) end if ask and ask:find( "Mon4", 1, true ) then local mon4 = World.months4[ opts.lang:lower() ] if mon4 and mon4[ at.month ] then limit4 = true end end elseif at.year then stamp = string.format( "%04d", at.year ) end if at.hour then if stamp then stamp = stamp .. " " else stamp = "" end stamp = string.format( "%s%02d:", stamp, at.hour ) if at.min then stamp = string.format( "%s%02d", stamp, at.min ) if at.sec then stamp = string.format( "%s:%02d", stamp, at.sec ) if at.msec then stamp = string.format( "%s.%03d", stamp, at.msec ) if at.mysec then stamp = string.format( "%s%03d", stamp, at.mysec ) end end end else stamp = stamp .. "00" end if at.zone then stamp = stamp .. World.zones.formatter( at, "+-" ) end end show, suffix = World.templates.formatter( at, ask, opts ) if limit4 then show = show:gsub( "M", "F" ) end if type( opts.london ) == "boolean" then locally = not opts.london else locally = true end r = babel:formatDate( show, stamp, locally ) r = r:gsub( " $", "" ) if at.year and at.year < 1000 then r = r:gsub( string.format( "%04d", at.year ), tostring( at.year ) ) end if at.month then local bucket, m, suite, x if show:find( "F", 1, true ) then suite = "monthsLong" elseif show:find( "M", 1, true ) then suite = "monthsAbbr" end bucket = World[ suite ] if bucket then m = bucket[ opts.lang:lower() ] if slang then x = bucket[ slang:lower() ] end if m then local base = m[ at.month ] local ex if x then ex = x[ at.month ] end if suite == "monthsAbbr" then local stop if ex then stop = x.suffix base = ex else stop = m.suffix end if base and stop then local shift, std std = string.format( "%s%%%s", base[ 1 ], stop ) shift = string.format( "%s%s", base[ 2 ], stop ) r = mw.ustring.gsub( r, std, shift ) end elseif suite == "monthsLong" then if base and ex then r = mw.ustring.gsub( r, base, ex ) end end end end end if suffix then r = r .. suffix end end return r end -- Private.format() Private.former = function ( at ) -- Analyze whether Julian calendar -- Parameter: -- at -- table, to be evaluated -- Returns: -- true, i local r if at.year then if at.year < 1582 then r = true elseif at.year == 1582 then if at.month then if at.month < 10 then r = true elseif at.month == 10 then r = ( at.dom <= 15 ) end end end end return r end -- Private.former() Private.from = function ( attempt ) -- Create valid raw table from arbitrary table -- Parameter: -- attempt -- table, to be evaluated -- Returns: -- table, with valid components, or nil local data = { } local r for k, v in pairs( Meta.components ) do if v then v = ( type( attempt[ k ] ) == v ) else v = true end if v then data[ k ] = attempt[ k ] end end -- for k, v if Prototypes.fair( data ) then r = data end return r end -- Private.from() Private.future = function ( add ) -- Normalize move interval -- Parameter: -- add -- string or number, to be added -- Returns: -- table, with shift, or false/nil local r if add then local s = type( add ) if s == "string" and add:match( "^%s*[+-]?%d+%.?%d*%s*$" ) then r = tonumber( add ) s = "number" end if s == "number" then if r == 0 then r = false else r = string.format( "%d second", r or add ) end elseif s == "string" then r = add else r = false end if r then r = Calc.future( r ) end end return r end -- Private.future() Prototypes.clone = function ( self ) -- Clone object -- Parameter: -- self -- table, with object, to be cloned -- Returns: -- table, with object local r = { [ Meta.signature ] = self[ Meta.signature ] } setmetatable( r, Meta.tableI ) return r end -- Prototypes.clone() Prototypes.failsafe = function ( self, atleast ) -- Retrieve versioning and check for compliance -- Precondition: -- self -- table, or not, with DateTime object, unused -- atleast -- string, with required version -- or "wikidata" or "~" or "@" or false -- Postcondition: -- Returns string -- with queried version/item, also if problem -- false -- if appropriate -- 2020-08-17 local since = atleast local last = ( since == "~" ) local linked = ( since == "@" ) local link = ( since == "item" ) local r if last or link or linked or since == "wikidata" then local item = Meta.item since = false if type( item ) == "number" and item > 0 then local suited = string.format( "Q%d", item ) if linkedlink then r = suited else local entity = mw.wikibase.getEntity( suited ) if type( entity ) == "table" then local seek = Failsafe.serialProperty or "P348" local vsn = entity:formatPropertyValues( seek ) if type( vsn ) == "table" and type( vsn.value ) == "string" and vsn.value ~= "" then if last and vsn.value == ( Meta.serial or DateTime.serial ) then r = false elseif linked then if mw.title.getCurrentTitle().prefixedText == mw.wikibase.getSitelink( suited ) then r = false else r = suited end else r = vsn.value end if last then r = false else r = vsn.value end end end end end end if type( r ) == "nil" then if not since or since <= Meta.serial then r = Meta.serial else r = false end end return r end -- Prototypes.failsafe() Prototypes.fair = function ( self, access, assign ) -- Check formal validity of table -- Parameter: -- self -- table, to be checked -- access -- string or nil, single item to be checked -- assign -- single access value to be checked -- Returns: -- true, if valid; false, if not local r = ( type( self ) == "table" ) if r then local defs = { year = { max = MaxYear }, month = { min = 1, max = 12 }, week = { min = 1, max = 53 }, dom = { min = 1, max = 31 }, hour = { max = 23 }, min = { max = 59 }, sec = { max = 61 }, msec = { max = 999 }, mysec = { max = 999 } } local fNum = function ( k, v ) local ret = true local dk = defs[ k ] if dk then if type( dk.max ) == "number" then ret = ( type( v ) == "number" ) if ret then local min if dk.min then min = dk.min else min = 0 end ret = ( v >= min and v <= dk.max and math.floor( v ) == v ) if ret and dk.f then ret = dk.f( v ) end end end end return ret end -- fNum() if self.bc then defs.year.max = 999999 end defs.dom.f = function () local ret local d if access == "dom" then d = assign else d = self.dom end if d then ret = ( d <= 28 ) if not ret then local m if access == "month" then m = assign else m = self.month end if m then ret = ( d <= Calc.months[ m ] ) if ret then local y if access == "year" then y = assign else y = self.year end if d == 29 and m == 2 and y then if y % 4 ~= 0 or ( y % 100 == 0 and y % 400 ~= 0 ) then ret = false end end end end end else ret = true end return ret end -- defs.dom.f() defs.sec.f = function () local ret local second if access == "sec" then second = assign else second = self.sec end if second then ret = ( second <= 59 ) if not ret and self.leap then ret = true end end return ret end -- defs.sec.f() if access or assign then r = ( type( access ) == "string" ) if r then local def = defs[ access ] if def then r = fNum( access, assign ) if r then if def == "dom" or def == "month" or def == "year" then r = defs.dom.f() end end elseif access == "lang" then r = ( type( assign ) == "string" ) if r then r = assign:match( "^%l%l%l?-?%a*$" ) end elseif access == "london" then r = ( type( assign ) == "boolean" ) end end else local life = false local leak = false local s, v for i = 1, 10 do s = Meta.order[ i ] v = self[ s ] if v then if not life and leak then -- gap detected r = false break else if not fNum( s, v ) then r = false break -- for i end life = true leak = true end elseif i == 3 then if not self.week then life = false end elseif i ~= 4 then life = false end end -- for i if self.week and ( self.month or self.dom ) then r = false end end end return r end -- Prototypes.fair() Prototypes.figure = function ( self, assign ) -- Assign month by name -- Parameter: -- self -- table, to be filled -- assign -- string, with month name -- Returns: -- number 1...12, if valid; false, if not local r = false if type( self ) == "table" and type( assign ) == "string" then r = Parser.monthNumber( assign ) if r then self.month = r end end return r end -- Prototypes.figure() Prototypes.first = function ( self ) -- Retrieve abbreviated month name in current language -- Parameter: -- self -- table, to be evaluated -- Returns: -- string, if defined; false, if not local r if type( self ) == "table" and self.month then local slang = ( self.lang or World.slang ) r = World.monthsLong[ slang ] if r then local brief = World.monthsAbbr[ slang ] r = r[ self.month ] if brief then local ex = brief[ self.month ] local s = brief.suffix if ex then r = ex[ 2 ] else local n = brief.n or 3 r = mw.ustring.sub( r, 1, n ) end if s then r = r .. s end end end else r = false end return r end -- Prototypes.first() Prototypes.fix = function ( self ) -- Adapt this object to local time if no explicit zone given -- Parameter: -- self -- table, with numbers etc. if type( self ) == "table" and not self.zone then local seconds = Prototypes.format( self, "Z" ) Prototypes.future( self, - tonumber( seconds ) ) end end -- Prototypes.fix() Prototypes.flow = function ( self, another, assert ) -- Compare this object with another timestamp -- Parameter: -- self -- table, with numbers etc. -- another -- DateTime or string or nil (now) -- assert -- nil, or string with operator -- "lt", "le", "eq", "ne", "ge", "gt", -- "<", "<=", "==", "~=", "<>", ">=", "=>", ">" -- Returns: -- if assert: true or false -- else: -1, 0, 1 -- nil if invalid local base, other, r if type( self ) == "table" then base = self other = another elseif type( another ) == "table" then base = another other = self end if base then if type( other ) ~= "table" then other = Meta.fiat( other ) end if type( other ) == "table" then r = Private.flow( base, other ) if r and type( assert ) == "string" then local trsl = { lt = "<", ["<"] = "<", le = "<=", ["<="] = "<=", eq = "=", ["=="] = "=", ne = "<>", ["<>"] = "<>", ["~="] = "<>", ge = ">=", [">="] = ">=", ["=>"] = ">=", gt = ">", [">"] = ">" } local same = trsl[ assert:lower() ] if same then local s = "=" if r < 0 then s = "<" elseif r > 0 then s = ">" end r = ( same:find( s, 1, true ) ~= nil ) else r = nil end end end end return r end -- Prototypes.flow() Prototypes.format = function ( self, ask, adapt ) -- Format object as string -- Parameter: -- self -- table, with numbers etc. -- ask -- string, format spec, or nil -- table, with multiple formats -- string may contain multiple formats joined by "|||" -- adapt -- table, with options, or nil -- .lang -- string, with particular language code -- .london -- true: UTC output; default: local -- .lonely -- true: permit lonely hour -- Returns: -- string, or false, if invalid, or number for julian date local r if type( self ) == "table" then local s = type( ask ) local poly if s == "string" and ask:find( "|||", 1, true ) then poly = mw.text.split( ask, "|||" ) elseif s == "table" then poly = ask end if poly then r = "" for i = 1, #poly do r = r .. Private.field( self, poly[ i ], adapt ) end -- for i else r = Private.field( self, ask, adapt ) end end return r or false end -- Prototypes.format() Prototypes.full = function ( self ) -- Retrieve month name in current language -- Parameter: -- self -- table, to be evaluated -- Returns: -- string, if defined; false, if not local r if type( self ) == "table" and self.month then local slang = ( self.lang or World.slang ) r = World.monthsLong[ slang ] if r then r = r[ self.month ] end else r = false end return r end -- Prototypes.full() Prototypes.future = function ( self, add, allocate ) -- Relative move by interval -- Parameter: -- self -- table, to be used as base -- add -- string or number, to be added -- allocate -- true, if a clone shall be returned -- Returns: -- table, with shift local r, raw, rel, shift if type( self ) == "table" then r = self shift = add elseif type( add ) == "table" then r = add shift = self end if r then if r[ Meta.signature ] then raw = r[ Meta.signature ] else raw = r end if type( shift ) == "table" then rel = shift else rel = Private.future( shift ) end end if raw and rel then if allocate then r = Prototypes.clone( r ) raw = r[ Meta.signature ] end for k, v in pairs( rel ) do raw[ k ] = ( raw[ k ] or 0 ) + v end -- for k, v Calc.fair( raw ) r[ Meta.signature ] = raw end return r end -- Prototypes.future() Prototypes.tostring = function ( self ) -- Stringify yourself -- Parameter: -- self -- table, to be stringified -- Returns: -- string local dels = { false, "", "-", "-", "", "", ":", ":", ".", "" } local wids = { false, 4, 2, 2, 2, 2, 2, 2, 3, 3 } local s = "" local n, r, spec local f = function ( a ) n = self[ Meta.order[ a ] ] s = s .. dels[ a ] if n then spec = string.format( "%%s%%0%dd", wids[ a ] ) s = string.format( spec, s, n ) end end -- f() for i = 2, 5 do f( i ) end -- for i r = s s = "" for i = 6, 10 do f( i ) end -- for i if s == "::." then r = r:gsub( "%-+$", "" ) else if r == "--" then r = s else r = string.format( "%sT%s", r, s ) end end r = r:gsub( "%.$", "" ) if self.bc then if self.year then r = "-" .. r else r = r .. " BC" end end return r end -- Prototypes.tostring() Prototypes.valueOf = function ( self ) -- Returns yourselves primitive value (primitive table) -- Parameter: -- self -- table, to be dumped -- Returns: -- table, or false local r if type( self ) == "table" then r = self[ Meta.signature ] end return r or false end -- Prototypes.valueOf() Templates.flow = function ( frame, action ) -- Comparison invokation -- Parameter: -- frame -- object -- Returns: -- string, either "" or "1" local r local s1 = frame.args[ 1 ] local s2 = frame.args[ 2 ] if s1 then s1 = mw.text.trim( s1 ) if s1 == "" then s1 = false end end if s2 then s2 = mw.text.trim( s2 ) if s2 == "" then s2 = false end end if s1 or s2 then local l Frame = frame l, r = pcall( Prototypes.flow, Meta.fiat( s1 ), s2, action ) if r == true then r = "1" end end return r or "" end -- Templates.flow() World.templates.formatter = function ( assigned, ask, adapt ) -- Retrieve format specification string -- Parameter: -- assigned -- table, with numbers etc. -- ask -- string, format spec, or nil -- adapt -- table, with options -- .lang -- string, with particular language code -- .lonely -- true: permit lonely hour -- Returns: -- 1 -- string -- 2 -- string or nil; append suffix (zone) local r1, r2 if not ask or ask == "" then r1 = "c" elseif ask == "*" then if World.present then if assigned.hour then if assigned.dom or assigned.month or assigned.year then if World.present.both and World.present.date and World.present.time then r1 = World.present.both :gsub( "$date", World.present.date ) :gsub( "$time", World.present.time ) else r1 = World.present.date end end r1 = r1 or World.present.time else r1 = World.present.date end end r1 = r1 or "c" else local template = World.templates[ ask ] r1 = ask if not template then local slang = ( adapt.lang or assigned.lang or World.slang ) local tmp = World.templates[ slang ] if tmp then template = tmp[ ask ] end if not template then local i = slang:find( "-", 3, true ) if i then slang = slang:sub( 1, i - 1 ):lower() tmp = World.templates[ slang ] if tmp then template = tmp[ ask ] end end end end if type( template ) == "table" then local low = ( ask == "ISO" or ask == "ISO-T" ) r1 = template.spec if assigned.year then if not assigned.dom then r1 = r1:gsub( "[ .%-]?[dDjlNwz][ .,%-]*", "" ) :gsub( "^ ", "" ) 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.%03d", stamp, assigned.msec ) if assigned.mysec then stamp = string.format( "%s.%03d", stamp, assigned.mysec ) end end end elseif adapt.lonely then stamp = "H" end end if low or ask:find( "hh:mm:ss", 1, true ) then if stamp then r1 = string.format( "%s %s", r1, stamp ) end elseif ask:find( "hh:mm", 1, true ) and stamp and #stamp > 3 then r1 = string.format( "%s H:i", r1 ) end if stamp then if low or template.long then local scheme if template.long then scheme = mw.language.getContentLanguage() scheme = scheme.code end r2 = World.zones.formatter( assigned, scheme ) end end end if type ( assigned.bc ) == "boolean" then local eras = World.era[ adapt.lang ] or World.era.en local i if not r2 then r2 = "" end if assigned.bc then i = 1 else i = 2 end r2 = string.format( "%s %s", r2, eras[ i ] ) end end end return r1, r2 end -- World.templates.formatter() World.zones.formatter = function ( assigned, align ) -- Retrieve time zone specification string -- Parameter: -- assigned -- table, with numbers etc. -- .zone should be available -- align -- string, format spec, or nil -- nil, false, "+-" -- +/- 0000 -- "Z" -- single letter -- "UTC" -- "UTC", if appropriate -- "de" -- try localized -- Returns: -- string local r = "" local move = 0 if assigned.zone then local s = type( assigned.zone ) if s == "string" then s = assigned.zone:upper() if #s == 1 then -- "YXWVUTSRQPONZABCDEFGHIKLM" move = World.zones[ "!" ]:find( s, 1, true ) if move then move = ( move - 13 ) * 100 assigned.zone = move else assigned.zone = false end else local code = World.zones[ s ] if not code then local slang = ( assigned.lang or World.slang ) local tmp = World.zones[ slang ] if tmp then code = tmp[ s ] end if not code and slang ~= "en" and World.zones.en then code = World.zones.en[ s ] end end if code then move = code assigned.zone = move end end elseif s == "number" then move = assigned.zone end end if move then local spec = "+-" if align then if align == "Z" then if move % 100 == 0 then r = World.zones[ "!" ]:sub( move / 100 + 13, 1 ) spec = false end elseif align ~= "+-" then if move == 0 then r = " UTC" spec = false else local part = World.zones[ align ] if part then for k, v in pairs( part ) do if v == move then r = string.format( " (%s)", k ) spec = false break end end -- for k, v end end end end if spec == "+-" then if move < 0 then spec = "%4.4d" else spec = "+%4.4d" end r = string.format( spec, move ) r = string.format( "%s:%s", r:sub( 1, 3), r:sub( 4 ) ) end end return r end -- World.zones.formatter() -- Export local p = { } p.test = function ( args, alien ) local slang = args.lang or alien local obj = Meta.fiat( args[ 1 ], false, args.shift ) local r if type( obj ) == "table" then local spec = args[ 2 ] local opt if spec then spec = mw.text.trim( spec ) end if slang then opt = { lang = mw.text.trim( slang ) } end r = obj:format( spec, opt ) else r = ( args.noerror or "0" ) if r == "0" then r = fault( "Format invalid" ) else r = "" end if args.errCat then local cats = mw.text.split( args.errCat, "%s*|%s*" ) for i = 1, #cats do r = string.format( "%s[[Category:%s]]", r, cats[ i ] ) end -- for i end end return r end -- p.test p.failsafe = function ( frame ) local s = type( frame ) local r, since if s == "table" then since = frame.args[ 1 ] elseif s == "string" then since = mw.text.trim( since ) if since == "" then since = false end end return Prototypes.failsafe( false, since ) or "" end -- p.failsafe p.format = function ( frame ) -- 1 -- stamp -- 2 -- spec -- lang -- shift -- noerror local l, r local v = { frame.args[ 1 ], frame.args[ 2 ], shift = frame.args.shift, noerror = frame.args.noerror, errCat = frame.args.errCat } if not v[ 1 ] or v[ 1 ] == "now" then v[ 1 ] = frame:callParserFunction( "#timel", "c", v.shift ) v.shift = false end Frame = frame l, r = pcall( p.test, v, frame.args[ 3 ] or frame.args.lang ) if not l then r = fault( r ) end return r end -- p.format p.lt = function ( frame ) return Templates.flow( frame, "lt" ) end -- p.lt p.le = function ( frame ) return Templates.flow( frame, "le" ) end -- p.le p.eq = function ( frame ) return Templates.flow( frame, "eq" ) end -- p.eq p.ne = function ( frame ) return Templates.flow( frame, "ne" ) end -- p.ne p.ge = function ( frame ) return Templates.flow( frame, "ge" ) end -- p.ge p.gt = function ( frame ) return Templates.flow( frame, "gt" ) end -- p.gt p.DateTime = function () return DateTime end -- p.DateTime return p