Jump to content

Module:Calendar date

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by GreenC (talk | contribs) at 15:34, 25 August 2018. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

--[[ 

Display non-Gregorian holiday dates using equivalent Gregorian date

 ]]

local p = {}

--[[--------------------------< inlineError >-----------------------

     Critical error. Render output completely in red. Add to tracking category.

 ]]

local function inlineError(arg, msg, tname)

  track["Category:Calendar date template errors"] = 1
  return '<span style="font-size:100%" class="error citation-comment">Error in {{' .. tname .. '}} - Check <code style="color:inherit; border:inherit; padding:inherit;">&#124;' .. arg .. '=</code>  ' .. msg .. '</span>'

end

--[[--------------------------< trimArg >-----------------------

     trimArg returns nil if arg is "" while trimArg2 returns 'true' if arg is "" 
     trimArg2 is for args that might accept an empty value, as an on/off switch like nolink=

 ]]

local function trimArg(arg)
  if arg == "" or arg == nil then
    return nil
  else
    return mw.text.trim(arg)
  end
end
local function trimArg2(arg)
  if arg == nil then
    return nil
  else
    return mw.text.trim(arg)
  end
end

--[[--------------------------< tableLength >-----------------------

      Given a 1-D table, return number of elements

  ]]

local function tableLength(T)
  local count = 0
  for _ in pairs(T) do count = count + 1 end
  return count
end

--[[--------------------------< createTracking >-----------------------

     Return data in track[] ie. tracking categories

  ]]

local function createTracking()

  local sand = ""
  if tableLength(track) > 0 then                        
    for key,_ in pairs(track) do
      sand = sand .. "[[" .. key .. "]]"
    end
  end
  return sand

end

--[[--------------------------< verifyDate >-----------------------

    Given the date arg, return true if within date range 2000-2050 else return false

 ]]
 
 local function verifyDate(date)
 
   if not date or date == "" then
     return nil
   end

    if tonumber(date) > 1999 and tonumber(date) < 2051 then
      return "true"
    else
      return nil
    end
end

--[[--------------------------< makeDate >-----------------------

     Given a zero-padded 4-digit year, 2-digit month and 2-digit day, return a full date in df format
     df = mdy, dmy, iso, ymd

 ]]

local function makeDate(year, month, day, df, format)

  if not year or year == "" or not month or month == "" or not day or day == "" then
    return nil
  end

  local zmonth = month                                                      -- month with leading 0
  month = month:match("0*(%d+)")                                            -- month without leading 0
  if tonumber(month) < 1 or tonumber(month) > 12 then
    return year
  end
  local nmonth = os.date("%B", os.time{year=2000, month=month, day=1} )     -- month in name form       
  if not nmonth then
    return year
  end

  local zday = day
  day = zday:match("0*(%d+)")
  if tonumber(day) < 1 or tonumber(day) > 31 then
    if df == "mdy" or df == "dmy" then
      return nmonth .. " " .. year
    elseif df == "iso" then
      return year .. "-" .. zmonth 
    elseif df == "ymd" then
      return year .. " " .. nmonth
    else
      return nmonth .. " " .. year
    end
  end                                       

  if format ~= "infobox" then
    if df == "mdy" then
      return nmonth .. " " .. day .. ", " .. year         -- September 1, 2016
    elseif df == "dmy" then
      return day .. " " .. nmonth .. " " .. year          -- 1 September 2016
    elseif df == "iso" then
      return year .. "-" .. zmonth .. "-" .. zday         -- 2016-09-01
    elseif df == "ymd" then
      return year .. " " .. nmonth .. " " .. day          -- 2016 September 1
    else
      return nmonth .. " " .. day .. ", " .. year         -- September 1, 2016
    end
  else
    if df == "mdy" then
      return nmonth .. " " .. day                         -- September 1
    elseif df == "dmy" then
      return day .. " " .. nmonth                         -- 1 September
    elseif df == "iso" then
      return year .. "-" .. zmonth .. "-" .. zday         -- 2018-09-01
    elseif df == "ymd" then
      return nmonth .. " " .. day                         -- September 1
    else
      return nmonth .. " " .. day                         -- September 1
    end
  end

end

--[[--------------------------< dateOffset >-----------------------

     Given a 'origdate' in ISO format, return the date offset by number of days in 'offset' 
        eg. given "2018-02-01" and "-1" it will return "2018-01-30"
     On error, return origdate

  ]]

function dateOffset(origdate, offset)

    local datesplit = {}
    datesplit = mw.text.split(origdate, "-")
    datesplit[1], datesplit[2], datesplit[3] = tonumber(datesplit[1]), tonumber(datesplit[2]), tonumber(datesplit[3])
    local now = os.time{year = datesplit[1], month = datesplit[2], day = datesplit[3]}
    local newdate = os.date("%Y-%m-%d", now + (tonumber(offset) * 24 * 3600))
    if not newdate then 
      return origdate 
    else
      return newdate
    end
  end

--[[--------------------------< renderHoli >-----------------------

     Render the data

  ]]
  
function renderHoli(json,holiday,date,df,format,tname)

  local numRecords = tableLength(json.items)
  local hits = 0
  local matchdate = "^" .. date
  local startdate,enddate,outoffset,endoutoffset = nil
  local starttitle,endtitle = ""  

  -- Get first and last date of holiday 
  for i = 1, numRecords do
    if mw.ustring.find( json.items[i].date, matchdate ) then
      if hits == 0 then
        startdate = json.items[i].date
        starttitle = json.items[i].title
        hits = 1
      end
      if hits >= tonumber(json.days) then
        enddate = json.items[i].date
        endtitle = json.items[i].title
        break
      end
      hits = hits + 1
    end
  end
     
  -- Verify data is OK
  if startdate == nil or enddate == nil then 
    if mw.ustring.find( starttitle, "Chanukah" ) then  -- Hanukkah bug, template doesn't support cross-year boundary
      enddate = dateOffset(startdate, 8)
    else
      return nil
    end
  end
     
  -- Generate start-date offset (ie. holiday starts the evening before the given date)
  if json.startoffset then
    startdate = dateOffset(startdate, json.startoffset)
    if startdate ~= enddate then
      enddate = dateOffset(enddate, json.startoffset)
    else
      if json.days == "1" then
        json.days = "2"
      end
    end
  end
 
  -- Generate end-date outside-Irael offset (ie. outside Israel the holiday ends +1 day later)
  if json.endoutoffset then
    endoutoffset = dateOffset(enddate, json.endoutoffset)
  end

  -- Format dates into df format 
  local datesplit = {}
  datesplit = mw.text.split(startdate, "-")
  startdate = makeDate(datesplit[1], datesplit[2], datesplit[3], df, format)
  datesplit = mw.text.split(enddate, "-")
  enddate = makeDate(datesplit[1], datesplit[2], datesplit[3], df, format)
  if startdate == nil or enddate == nil then return nil end

  -- Add "outside of Israel" notices
  if endoutoffset then
    datesplit = mw.text.split(endoutoffset, "-")
    local leader = " "
    if format == "infobox" then leader = "<br>" end
    endoutoffset = leader .. "(" .. makeDate(datesplit[1], datesplit[2], datesplit[3], df, "infobox") .. " outside of Israel)"
  end
  if not endoutoffset then
    endoutoffset = ""
  end

  -- generate format string
  if format == "infobox" then
    format = " –<br>"
  else
    format = " – "
  end
  
  -- return output
  if startdate == enddate or json.days == "1" then            -- single date
    return json.prepend1 .. startdate .. endoutoffset
  else
    return json.prepend1 .. startdate .. format .. json.prepend2 .. enddate .. endoutoffset
  end
      
end

--[[--------------------------< calendardate >-----------------------

     Main function

  ]]

function p.calendardate(frame)

  local pframe = frame:getParent()
  local args = pframe.args

  local tname = "Calendar date"                  -- name of calling template. Change if template rename.
  local holiday = nil                            -- name of holiday
  local date = nil                               -- date of holiday (year) 
  local df = nil                                 -- date format (mdy, dmy, iso - default: iso)
  local format = nil                             -- template display format options
  
  track = {}                                     -- global tracking-category table

  --- Determine holiday
  holiday = trimArg(args.holiday)                -- required
  if not holiday then
    holiday = trimArg(args.event)                -- event alias
    if not holiday then
      return inlineError("holiday", "Missing holiday argument", tname) .. createTracking()
    end
  end

  --- Determine date
  date = trimArg(args.year)                      -- required
  if not date then
    return inlineError("year", "Missing year argument", tname) .. createTracking()
  elseif not verifyDate(date) then
    return inlineError("year", "Invalid year", tname) .. createTracking()
  end

  --- Determine format type
  format = trimArg(args.format)
  if not format then
    format = "none"
  elseif format ~= "infobox" then
    format = "none"
  end
 
  --- Parse JSON file   
  local version = mw.title.makeTitle( 'Template', tname .. '/holidays/' .. holiday .. '.js' )    
  if not version.exists then
    return inlineError("holiday", "File missing Template:" .. tname .. "/holidays/" .. holiday .. ".js", tname) .. createTracking()
  end
  local json = nil
  if version.isRedirect then
    json = mw.text.jsonDecode( version.redirectTarget:getContent() )
  else
    json = mw.text.jsonDecode( version:getContent() )
  end

  --- Determine df - priority to |df in template, otherwise df in datafile, otherwise default to dmy
  df = trimArg(args.df)
  if not df then
    if json.df then
      df = json.df
    else
      df = "dmy"
    end
  end
  if df ~= "mdy" and df ~= "dmy" and df ~= "iso" then
    df = "dmy"
  end

  --- Determine pre-pended text eg. "sunset, <date>"
  if not json.prepend1 then
    json.prepend1 = ""
  else
    json.prepend1 = json.prepend1 .. ", "
  end
  if not json.prepend2 then
    json.prepend2 = ""
  else
    json.prepend2 = json.prepend2 .. ", "
  end

  -- Render 
  local rend = renderHoli(json,holiday,date,df,format,tname)
  if not rend then
    rend = '<span style="font-size:100%" class="error citation-comment">Error in [[:Template:' .. tname .. ']]: Unknown problem. Please report on template talk page.</span>'
    track["Category:Webarchive template errors"] = 1 
  end

  return rend .. createTracking()

end

return p