Module:D'ni Time
From Guild of Archivists
Documentation for this module may be created at Module:D'ni Time/doc
-- THIS IS OBVIOUSLY NOT DONE! DO NOT TOUCH! -Vee
dnitools = require("Module:D'ni Tools")
local p = {}
local args = {}
local origArgs
local root
-- Declare important constants
local refDniHahr = 9647
local msPerHahr = 31556925216
local prorahnteePerHahr = 10 * 29 * 5 * 25 * 25 * 25
local msPerProrahn = msPerHahr / prorahnteePerHahr
local refProrahnteePerHahr = 9647 * 290 * 5 * 25 * 25 * 25
local vaileetee = {"Leefo","Leebro","Leesahn","Leetar","Leevot","Leevofo","Leevobro","Leevosahn","Leevotar","Leenovoo"}
local vaileetee_dnifont = {"lEfo","lEbro","lEsan","lEtar","lEvot","lEvofo","lEvobro","lEvosan","lEvotar","lEnovU"}
local leapSecTimeStamps = {2272060800, 2287785600, 2303683200, 2335219200, 2366755200, 2398291200, 2429913600, 2461449600, 2492985600, 2524521600, 2571782400, 2603318400, 2634854400, 2698012800, 2776982400, 2840140800, 2871676800, 2918937600, 2950473600, 2982009600, 3029443200, 3076704000, 3124137600, 3345062400, 3439756800, 3550089600, 3644697600, 3692217600}
local LeapSecOffset = 10
local function ConvertLeapSecTimeStamps()
-- convert LeapSecTimeStamps from NTP epoch (number of seconds since 1900-01-01 00:00:00)
-- to Unix standard (number of milliseconds since 1970-01-01 00:00:00)
delta = os.time({year=1900, month=1, day=1, hour=0, min=0})
for key,value in pairs(leapSecTimeStamps) do
leapSecTimeStamps[key] = value + delta
end
end
local function AdjustForLeapSeconds(timestamp)
-- Adjust timestamp for leap seconds
leapsecs = 0
for key,value in pairs(leapSecTimeStamps) do
if timestamp >= value then
leapsecs = leapsecs + 1
end
end
if leapsecs > 0 then
leapsecs = leapsecs + (LeapSecOffset - 1)
end
return timestamp + leapsecs
end
local function DejustForLeapSeconds(timestamp)
-- Adjust timestamp for leap seconds the other way
leapsecs = 0
for key,value in pairs(leapSecTimeStamps) do
if timestamp >= value then
leapsecs = leapsecs + 1
end
end
if leapsecs > 0 then
leapsecs = leapsecs + (LeapSecOffset - 1)
end
return timestamp - leapsecs
end
local function makeSurfaceTimeStamp(timeStampTable)
-- convert a UTC date-time to a adjusted timestamp
temp = os.time(timeStampTable)
return AdjustForLeapSeconds(temp)
end
-- Run conversion of leapseconds and conversion of reference Timestamp here,
-- since these require the above functions to be already declared
ConvertLeapSecTimeStamps()
local refTimeStamp = makeSurfaceTimeStamp({year=1991, month=4, day=21, hour=16, min=54})
local function adjust(dniTime)
-- Ensure DniTime values are within bounds
while (dniTime['prorahn'] > 25) do
dniTime['prorahn'] = dniTime['prorahn'] - 25
dniTime['gorahn'] = dniTime['gorahn'] + 1
end
while (dniTime['prorahn'] < 0) do
dniTime['prorahn'] = dniTime['prorahn'] + 25
dniTime['gorahn'] = dniTime['gorahn'] - 1
end
while (dniTime['gorahn'] > 25) do
dniTime['gorahn'] = dniTime['gorahn'] - 25;
dniTime['tahvo'] = dniTime['tahvo'] + 1;
end
while (dniTime['gorahn'] < 0) do
dniTime['gorahn'] = dniTime['gorahn'] + 25
dniTime['tahvo'] = dniTime['tahvo'] - 1
end
while (dniTime['tahvo'] > 25) do
dniTime['tahvo'] = dniTime['tahvo'] - 25
dniTime['gahrtahvo'] = dniTime['gahrtahvo'] + 1
end
while (dniTime['tahvo'] < 0) do
dniTime['tahvo'] = dniTime['tahvo'] + 25
dniTime['gahrtahvo'] = dniTime['gahrtahvo'] - 1
end
while (dniTime['gahrtahvo'] > 5) do
dniTime['gahrtahvo'] = dniTime['gahrtahvo'] - 5
dniTime['yahr'] = dniTime['yahr'] + 1
end
while (dniTime['gahrtahvo'] < 0) do
dniTime['gahrtahvo'] = dniTime['gahrtahvo'] + 5
dniTime['yahr'] = dniTime['yahr'] - 1
end
while (dniTime['yahr'] > 29) do
dniTime['yahr'] = dniTime['yahr'] - 29
dniTime['vailee'] = dniTime['vailee'] + 1
end
while (dniTime['yahr'] < 0) do
dniTime['yahr'] = dniTime['yahr'] + 29
dniTime['vailee'] = dniTime['vailee'] - 1
end
while (dniTime['vailee'] > 9) do
dniTime['vailee'] = dniTime['vailee'] - 10
dniTime['hahr'] = dniTime['hahr'] + 1;
end
while (dniTime['vailee'] < 0) do
dniTime['vailee'] = dniTime['vailee'] + 10
dniTime['hahr'] = dniTime['hahr'] - 1
end
return dniTime
end
local function surface2DniTime(surfaceTimeStamp)
-- Given a surfaceTimestamp
-- Return a DniTime (table)
surface = surface or refTimeStamp
dniTime = {}
delta = (surfaceTimeStamp - refTimeStamp) * 1000
dniTime['hahr'] = math.floor(delta / msPerHahr)
delta = delta - (dniTime['hahr'] * msPerHahr)
delta = delta * (prorahnteePerHahr / msPerHahr)
dniTime['vailee'] = math.floor(delta / (29 * 5 * 25 * 25 * 25))
delta = delta - (dniTime['vailee'] * (29 * 5 * 25 * 25 * 25))
dniTime['yahr'] = math.floor(delta / (5 * 25 * 25 * 25))
delta = delta - (dniTime['yahr'] * (5 * 25 * 25 * 25))
dniTime['gahrtahvo'] = math.floor(delta / (25 * 25 * 25))
delta = delta - (dniTime['gahrtahvo'] * (25 * 25 * 25))
dniTime['tahvo'] = math.floor(delta / (25 * 25))
delta = delta - (dniTime['tahvo'] * (25 * 25))
dniTime['gorahn'] = math.floor(delta / 25)
delta = delta - (dniTime['gorahn'] * 25)
dniTime['prorahn'] = math.floor(delta)
dniTime['hahr'] = dniTime['hahr'] + refDniHahr
dniTime['yahr'] = dniTime['yahr'] + 1
--return adjust(dniTime)
return dniTime
end
local function dni2SurfaceTime(dniTime)
-- Given a DniTime (table)
-- Return a surfacteTimestamp
dniTime = dniTime or {hahr = refDniHahr, vailee = 0, yahr = 1, gahrtahvo = 0, tahvo = 0, gorahn = 0, prorahn = 0}
dniTime = adjust(dniTime)
dTimeInProrahntee = dniTime['prorahn'] + (dniTime['gorahn'] * 25) + (dniTime['tahvo'] * 25 * 25) + (dniTime['gahrtahvo'] * 25 * 25 * 25) + ((dniTime['yahr'] - 1) * 5 * 25 * 25 * 25) + ((dniTime['vailee']) * 29 * 5 * 25 * 25 * 25) + (dniTime['hahr'] * 290 * 5 * 25 * 25 * 25)
dTimeDelta = refProrahnteePerHahr - dTimeInProrahntee
dTimeDelta = dTimeDelta * msPerProrahn
dTimeDelta = (refTimeStamp * 1000) - dTimeDelta
surfaceTimestamp = DejustForLeapSeconds(dTimeDelta/1000)
return os.date("*t",surfaceTimestamp)
end
-- ========Parse Functions========
-- Much of this code derived from https://help.interfaceware.com/code/details/dateparse-lua
-- Create index table for vailee
if true then
local function index_by_name(array)
local dict = {}
for i,name in pairs(array) do
name = name:lower()
dict[name] = i - 1
end
return dict
end
vailee_by_name = index_by_name(vaileetee)
end
-- Given string s, return name of vailee
local function lookup_vailee(s)
local vailee = vailee_by_name[s:lower()]
if not vailee then error('expected vailee, got "'..s..'"') end
return vailee
end
local function fix_hahr(BE,DE)
return function(s,d)
if s:upper() == BE then
d.hahr = - math.abs(d.hahr)
elseif s:upper() == DE then
d.hahr = math.abs(d.hahr)
else
error('expected '..BE..' or '..DE..', got "'..s..'"')
end
end
end
--Just returns a number (serves as 'default' for function table)
local function parseint(s)
return tonumber(s)
end
-- Possible codes in Dni format string, with associated patterns and functions.
-- The default function is parseint(), since most values are just integers, exactly
-- as we need them.
local fmt_details = {
hhhh = { '%d%d%d%d', 'hahr' };
v = { '%d+', 'vailee' };
vv = { '%d%d', 'vailee' };
vvvv = { '%a+', 'vailee', lookup_vailee };
y = { '%d+', 'yahr' };
yy = { '%d%d', 'yahr' };
g = { '%d+', 'gahrtahvo' };
gg = { '%d%d', 'gahrtahvo' };
t = { '%d+', 'tahvo' };
tt = { '%d%d', 'tahvo' };
r = { '%d+', 'gorahn' };
rr = { '%d%d', 'gorahn' };
p = { '%d+', 'prorahn' };
pp = { '%d%d', 'prorahn' };
xx = { '%a%a', 'BE or DE', fix_hahr('BE','DE') };
[' '] = { '%s*', 'whitespace' }; -- Allow omission.
[','] = { '%s*,?', 'a comma' }; -- Allow omission and leading whitespace.
w = { '%a+', 'a word' }; -- Value ignored.
n = { '%d+', 'a number' }; -- Value ignored.
}
-- Splits one part of a format string off; returns that and the rest.
local function split_fmt(fmt)
local c = fmt:match('^(%a)')
if c then
return fmt:match('^('..c..'+)(.*)')
elseif #fmt > 0 then
return fmt:sub(1,1), fmt:sub(2)
end
end
local function parseDniDateStr(s, fmt)
-- Parses the string, s, according to the format, fmt.
fmt = fmt or "hhhh vvvv y g:t:r:p"
local matched, d = '', {hahr=1,yahr=1,vailee=0,gahrtahvo=0,tahvo=0,gorahn=0,prorahn=0} --Sets default
local function fail(what, pattern) --Error msg function
if pattern then what = what..' ('..pattern..')' end
if matched ~= '' then what = what..' after "'..matched..'"' end
error('expected '..what..', got "'..s..'"')
end
while fmt ~= '' do
local head_fmt, rest_fmt = split_fmt(fmt)
local pattern, field, fun = unpack(fmt_details[head_fmt] or {})
local part, rest
if pattern then
part, rest = s:match('^('..pattern..')(.*)')
if not part then fail(field,head_fmt) end
d[field] = (fun or parseint)(part,d)
matched = matched .. part
elseif head_fmt:find('^%a') then
error('unknown date/time pattern: '..head_fmt)
elseif s:sub(1,#head_fmt) ~= head_fmt then
fail('"'..head_fmt..'"')
else
matched = matched .. s:sub(1,#head_fmt)
rest = s:sub(#head_fmt + 1)
end
s, fmt = rest, rest_fmt
end
if s ~= '' then fail('nothing') end
return d
end
local function parseSurfaceDateStr(dateStr)
-- Given a surface data string, accepts formats that #time accepts
-- Returns a surfaceDate (table)
lang = mw.getContentLanguage()
surfaceTimeStamp = lang:formatDate("U",dateStr)
surfaceTime = os.date("!*t",surfaceTimeStamp)
surfaceTime['isdst'] = true
return surfaceTime
end
-- ==========Display Functions=========
local function getVaileeName(vailee, useDniFont)
-- Return name of vailee given number (zero-indexed)
-- If useDniFont is true, return it in Dnifont text
useDniFont = useDniFont or false
if(useDniFont) then
return vaileetee_dnifont[vailee+1]
else
return vaileetee[vailee+1]
end
end
local function displayDniTime(dniTime)
-- Return dniTime (table) as string of table
return '{'..'hahr = '..dniTime['hahr']..', vailee = '..dniTime['vailee']..', vaileeName = '..getVaileeName(dniTime['vailee'])..', yahr = '..dniTime['yahr']..', gahrtahvo = '..dniTime['gahrtahvo']..', tahvo = '..dniTime['tahvo']..', gorahn = '..dniTime['gorahn']..', prorahn = '..dniTime['prorahn']..'}'
end
local function displaySurfaceTime(surface)
-- Return surfaceTime (table) as string of table
return '{'..'year = '..surface['year']..', month = '..surface['month']..', day = '..surface['day']..', hour = '..surface['hour']..', min = '..surface['min']..', sec = '..surface['sec']..', isdst = '..tostring(surface['isdst'])..'}'
end
local function formatSurfaceTime(surfaceTime,format)
format = format or 'c'
lang = mw.getContentLanguage()
return lang:formatDate(format,tostring(surfaceTime['year'])..'-'..tostring(surfaceTime['month'])..'-'..tostring(surfaceTime['day'])..' '..tostring(surfaceTime['hour'])..':'..tostring(surfaceTime['min'])..':'..tostring(surfaceTime['sec']))
end
local function formatDniTime(dniTime,format)
-- Given a DniTime (table), returns formatted string.
-- format is a string with patterns for parts of the date, like standard dates
-- Patterns in capitals padded with zeroes
-- HHHH, hhhh = hahr (4-digits, Uppercase includes +/-)
-- hh = last two digits of the hahr
-- XX = 'era' (BE or DE)
-- VVVV = vailee by name
-- VV,vv = vailee (1 indexed)
-- YY, yy = yahr
-- GG, gg = gahrtahvo
-- TT, tt = tahvo
-- RR, rr = gorahnn
-- PP, pp = prorahn
format = format or "HHHH VVVV yy gg:tt:rr:pp"
result = format
if (dniTime['hahr'] < 0) then
era = "BE"
sign = "-"
else
era = "DE"
sign = "+"
end
result = string.gsub(result,"%a+", {
["dHHHH"] = dnitools._num2dnifont(math.abs(dniTime['hahr']),4),
["HHHH"] = string.format("%04d",tostring(dniTime['hahr'])),
["dhhhh"] = dnitools._num2dnifont(math.abs(dniTime['hahr'])),
["hhhh"] = math.abs(dniTime['hahr']),
["dhh"] = dnitools._num2dnifont(string.sub(dniTime['hahr'],-2)),
["hh"] = string.sub(dniTime['hahr'],-2),
["dYY"] = dnitools._num2dnifont(dniTime['yahr'],2),
["YY"] = string.format("%02d",tostring(dniTime['yahr'])),
["dyy"] = dnitools._num2dnifont(dniTime['yahr']),
["yy"] = dniTime['yahr'],
["dVVVV"] = getVaileeName(dniTime['vailee'],true),
["VVVV"] = getVaileeName(dniTime['vailee']),
["VV"] = string.format("%02d",tostring(dniTime['vailee']+1)),
["dvv"] = dnitools._num2dnifont(dniTime['vailee'] + 1),
["vv"] = dniTime['vailee'] + 1,
["GG"] = string.format("%02d",tostring(dniTime['gahrtahvo'])),
["dgg"] = dnitools._num2dnifont(dniTime['gahrtahvo']),
["gg"] = dniTime['gahrtahvo'],
["TT"] = string.format("%02d",tostring(dniTime['tahvo'])),
["dtt"] = dnitools._num2dnifont(dniTime['tahvo']),
["tt"] = dniTime['tahvo'],
["RR"] = string.format("%02d",tostring(dniTime['gorahn'])),
["drr"] = dnitools._num2dnifont(dniTime['gorahn']),
["rr"] = dniTime['gorahn'],
["PP"] = string.format("%02d",tostring(dniTime['prorahn'])),
["pp"] = dniTime['prorahn'],
["dpp"] = dnitools._num2dnifont(dniTime['prorahn']),
["XX"] = era,
["X"] = sign,
})
return result
end
-- =============================================================================
local function preprocessSingleArg(argName)
-- If the argument exists and isn't blank, add it to the argument table.
-- Blank arguments are treated as nil to match the behaviour of ParserFunctions.
if origArgs[argName] and origArgs[argName] ~= '' then
args[argName] = origArgs[argName]
end
end
function p.surface2dni(frame)
-- If called via #invoke, use the args passed into the invoking template.
-- Otherwise, for testing purposes, assume args are being passed directly in.
if frame == mw.getCurrentFrame() then
origArgs = frame:getParent().args
else
origArgs = frame
end
output = ""
preprocessSingleArg('datetime')
preprocessSingleArg('format')
if args['datetime'] then
output = formatDniTime(surface2DniTime(makeSurfaceTimeStamp(parseSurfaceDateStr(args['datetime']))),args['format'])
else
preprocessSingleArg('year')
preprocessSingleArg('month')
preprocessSingleArg('day')
preprocessSingleArg('hour')
preprocessSingleArg('minute')
preprocessSingleArg('second')
-- Parse the data parameters
surface = {year = args['year'], month = args['month'], day = args['day'], hour = args['hour'], min = args['minute'], sec = args['second']}
-- return displaySurfaceTime(surface)
output = formatDniTime(surface2DniTime(makeSurfaceTimeStamp(surface)),args['format'])
end
return output
end
function p.dni2surface(frame)
-- If called via #invoke, use the args passed into the invoking template.
-- Otherwise, for testing purposes, assume args are being passed directly in.
if frame == mw.getCurrentFrame() then
origArgs = frame:getParent().args
else
origArgs = frame
end
preprocessSingleArg('datetime')
preprocessSingleArg('format')
preprocessSingleArg('input')
if args['datetime'] then
output = formatSurfaceTime(dni2SurfaceTime(parseDniDateStr(args['datetime'],args['input'])),args['format'])
else
preprocessSingleArg('hahr')
preprocessSingleArg('vailee')
preprocessSingleArg('yahr')
preprocessSingleArg('gahrtahvo')
preprocessSingleArg('tahvo')
preprocessSingleArg('gorahn')
preprocessSingleArg('prorahn')
-- Parse the data parameters
dni = {hahr = tonumber(args['hahr']), vailee = tonumber(args['vailee']), yahr = tonumber(args['yahr']), gahrtahvo = tonumber(args['gahrtahvo']), tahvo = tonumber(args['tahvo']), gorahn = tonumber(args['gorahn']), prorahn = tonumber(args['prorahn'])}
-- output = displaySurfaceTime(dni2SurfaceTime(dni),args['format'])
output = formatSurfaceTime(dni2SurfaceTime(dni),args['format'])
end
return output
end
function p.test(frame)
-- If called via #invoke, use the args passed into the invoking template.
-- Otherwise, for testing purposes, assume args are being passed directly in.
if frame == mw.getCurrentFrame() then
origArgs = frame:getParent().args
else
origArgs = frame
end
-- Parse the data parameters
lang = mw.getContentLanguage()
surface = {year = -1, month = 5, day = 11, hour = 4, min = 28, sec = 0, isdst = false}
fmtStr = "hhhh y v g t r p"
output = ""
output = output .. "<br/>"..displaySurfaceTime(surface)
output = output .. "<br/>"..formatSurfaceTime(surface,args['format'])
output = output .. "<br/>"..makeSurfaceTimeStamp(surface)
-- output = output .. "<br/>"..lang:formatDate("c","@"..makeSurfaceTimeStamp(surface))
return output
end
return p