Module:D'ni Time

From Guild of Archivists
Revision as of 03:42, 14 August 2020 by BladeLakem (talk | contribs)
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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}
    dni = {hahr = -1, vailee = 1, yahr = 1, gahrtahvo = 1, tahvo = 1, gorahn = 1, prorahn = 1}

    output = ""
    output = output .. "<br/>"..displaySurfaceTime(surface)
    output = output .. "<br/>"..displayDniTime(surface2DniTime(surface))
    output = output .. "<br/>"..displayDniTime(dni)
    output = output .. "<br/>"..displaySurfaceTime(dniSurfaceTime(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