Module:Sandbox/Ajuanca/Dates

-- Task 9 of GCI 2019 local p = {}

-- Main function function p.formatDates(frame) local inputDate = tostring(frame.args.name) local inputFormat=tostring(frame.args.format) local inputTable = p.divideString(inputDate) local str="" local myDate = p.getDate(inputTable) local dateResults = p.checkDate(myDate) if(not(dateResults.isDateCorrect))then return dateResults.errorMessage else local outputFormat=p.chooseFormat(myDate, inputFormat) local completeMonths={"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} if(myDate.isAprox)then str="circa " end if(outputFormat=="dmy")then str = str .. myDate.day .. " " .. completeMonths[myDate.month] .. " " .. myDate.year elseif(outputFormat=="mdy")then str = str .. completeMonths[myDate.month] .. " " .. myDate.day .. ", " .. myDate.year elseif(outputFormat=="iso")then str = str .. myDate.year .. "-" .. myDate.month .. "-" .. myDate.day elseif(outputFormat=="y")then str=str..myDate.year elseif(outputFormat=="my")then str=str.. completeMonths[myDate.month] .. " " .. myDate.year end if(myDate.specifiedEra~= nil)then str = str .." "..myDate.specifiedEra end end return str end

-- Selects correct output format. -- Returns a String function p.chooseFormat(dateObject, prefference) local correctFormat="" if(dateObject.dateFormat~= nil)then correctFormat=dateObject.dateFormat else correctFormat="#error" end local compatibleFormats = {"dmy", "mdy", "iso", "y", "my"} for n, actualFormat in ipairs(compatibleFormats) do		if (prefference:lower == actualFormat) then correctFormat=prefference:lower break end end return correctFormat end

-- Truncates the input by spaces. -- Returns a table function p.divideString(stringSentence) local nameTable = {} stringSentence = string.gsub(stringSentence, "-", " ") stringSentence = string.gsub(stringSentence, "/", " ") for m in string.gmatch(stringSentence, ("%S+")) do 		table.insert(nameTable, m)	end return nameTable end

-- Detects input date. -- Returns a Object function p.getDate(stringTable) local comparison = "" local finalDate = { day = 0, month = 0, year = 0, isAprox=false, specifiedEra=nil, dateFormat=nil }	local dateIsStored = false -- Matchs string month with correspoding number. -- Returns number function getMonth(strInput) local months = {"jan", "feb", "mar", "apr", "may", "june", "july", "aug", "sept", "oct", "nov", "dec"} if(strInput:find("(%a+)"))then for w, month in ipairs(months)do if(strInput:lower:find(month)~= nil)then return tonumber(w) end end end return 0 end -- Match given ordinal number to its cardinal value. -- Returns a number function getCardinal(inputStr) if(inputStr:find("(%d+)(%a+)"))then local number, termination=inputStr:lower:match("(%d+)(%a+)") suffixes = {"st", "nd", "rd", "th"} for g, suffix in ipairs(suffixes)do if(termination:find(suffix))then return tonumber(number) end end end return 0 end -- Start comparison for z, number in ipairs(stringTable) do -- Creates a string of three followed items. if(z<=#stringTable-2)then local comparison = stringTable[z] .. " " .. stringTable[z+1] .. " " .. stringTable[z+2] -- Search for the format 'typedMonth Day, Year'. if(comparison:lower:find("(%a+) (%d+), (%d+)"))then local m, d, y = comparison:lower:match("(%a+) (%d+), (%d+)") finalDate.month=tonumber(getMonth(m)) finalDate.year=tonumber(y) finalDate.day=tonumber(d) finalDate.dateFormat="mdy" dateIsStored=true end -- Search for the format 'Day typedMonth Year' if(comparison:lower:find("(%d+) (%a+) (%d+)") and not(dateIsStored))then local d, m, y = comparison:lower:match("(%d+) (%a+) (%d+)") finalDate.month=getMonth(m:lower) finalDate.year=tonumber(y) finalDate.day=tonumber(d) finalDate.dateFormat="dmy" if(finalDate.month==0)then dateIsStored=false else dateIsStored=true end end -- Search for the format 'Day Month Year' or 'Year Month Day' if(comparison:lower:find("(%d+) (%d+) (%d+)") and not(dateIsStored))then local d, m, y= comparison:lower:match("(%d+) (%d+) (%d+)") -- If day and year are both less than 31, it'll be considered d-m-y if(tonumber(d)>31)then local temporalDay=y y=d d=temporalDay end finalDate.month=tonumber(m) finalDate.year=tonumber(y) finalDate.day=tonumber(d) -- This format isn't allowed. The most similar is ISO. finalDate.dateFormat="iso" if(finalDate.month==0)then dateIsStored=false else dateIsStored=true end end -- Search for format 'ordinalDay typedMonth Year' if(comparison:lower:find("(%w+) (%a+) (%d+)") and not(dateIsStored))then local d, m, y=comparison:lower:match("(%w+) (%a+) (%d+)") finalDate.day=getCardinal(d) finalDate.month=getMonth(m) finalDate.year=y -- This format isn't allowed. The most similar is ISO. finalDate.dateFormat="iso" if(finalDate.month==0)then dateIsStored=false else dateIsStored=true end end end -- Searchs for aproximations indicators local aproxIndicators = {"circa", "approx", "around", "uncertain"} for z, indicator in ipairs(aproxIndicators)do if(number:lower:match(indicator))then finalDate.isAprox=true end end -- Searchs for era indicators. Note that, althought it isn't used, -- the value g gives us the calendar used. local eraIndicators={{"bce", "bc"}, {"ce", "ad"}} for d, era in ipairs(eraIndicators)do for g, specific in ipairs(era)do if(specific==number:lower)then finalDate.specifiedEra=specific:upper end end end end if(not(dateIsStored))then for k, number in ipairs(stringTable) do -- Creates a string of two followed elements. if(k<=#stringTable-1)then local comparison = stringTable[k] .. " " .. stringTable[k+1] -- Search for a date in format 'Day typedMonth' if(comparison:lower:find("(%d+) (%a+)"))then local d, m=comparison:lower:match("(%d+) (%a+)") finalDate.day=tonumber(d) finalDate.month=tonumber(getMonth(m)) -- This format isn't allowed. The most similar is ISO. finalDate.dateFormat="iso" if(finalDate.month==0)then dateIsStored=false else dateIsStored=true end -- Search for a date in format 'ordinalDay typedMonth' elseif(comparison:lower:find("(%w+) (%a+)"))then local d, m=comparison:lower:match("(%w+) (%a+)") finalDate.day=getCardinal(d) finalDate.month=getMonth(m) -- This format isn't allowed. The most similar is ISO. finalDate.dateFormat="iso" if(finalDate.day==0)then dateIsStored=false else dateIsStored=true end end -- Search for a date in format 'typedMonth Year' if(comparison:lower:find("(%a+) (%d+)"))then local m, y=comparison:lower:match("(%a+) (%d+)") finalDate.month=getMonth(m) finalDate.year=tonumber(y) finalDate.dateFormat="my" if(finalDate.month==0)then dateIsStored=false else dateIsStored=true end end end end end -- Search for a result if nothing is still found. if(not(dateIsStored))then local numbersFound = {} for n, number in ipairs(stringTable)do -- Search for possible given values in an non-regulated order. if(number:find("(%a+)")~=nil)then if(finalDate.day==0 and number:find("(%d+)"))then -- Search for ordinal day finalDate.day=getCardinal(number) if(not(finalDate.day==0))then finalDate.dateFormat="dmy" end elseif(finalDate.month==0)then -- Search for months finalDate.month=tonumber(getMonth(number)) if(not(finalDate.month==0))then finalDate.dateFormat="my" end end else table.insert(numbersFound, tonumber(number)) end if(#numbersFound==1 and #stringTable==n)then finalDate.year=numbersFound[1] if((finalDate.month~=0) and (finalDate.day~=0))then finalDate.dateFormat="dmy" elseif(finalDate.month~=0)then finalDate.dateFormat="my" else finalDate.dateFormat="y" end elseif(#numbersFound==2 and #stringTable==n)then if(numbersFound[1]<=31) then finalDate.day=numbersFound[1] finalDate.year=numbersFound[2] else finalDate.year=numberFound[1] finalDate.day=numberFound[2] end finalDate.dateFormat="y" end end end return finalDate end

-- Checks that given date -- Returns a object function p.checkDate(givenDate) -- Detects is the given year is leap. -- Returns a boolean function isLeap(yearInput) local yearIsLeap = (tonumber(yearInput)%4)==0 if((tonumber(yearInput)%100)==0 )then yearIsLeap = (tonumber(yearInput)%400)==0 end return yearIsLeap end local finalObject = { isDateCorrect=true, errorMessage="" }	-- Check if day is correct if(not(givenDate.day==nil))then if(givenDate.day>31)then finalObject.isDateCorrect=false finalObject.errorMessage="That day doesn't exists!" return finalObject end end if(not(givenDate.month==nil))then if(givenDate.month==2)then if(isLeap(givenDate.year))then if(givenDate.day>29)then finalObject.isDateCorrect=false finalObject.errorMessage="That day doesn't exists!" end else if(givenDate.day>=29)then finalObject.isDateCorrect=false finalObject.errorMessage="That day doesn't exists!" end end end end -- Check if month is correct if(not(givenDate.month==nil))then if(givenDate.month>12)then finalObject.isDateCorrect=false finalObject.errorMessage="That month doesn't exist!" end end -- Check if date is nil if(givenDate.year==0 and givenDate.day==0 and givenDate.month==0)then finalObject.isDateCorrect=false finalObject.errorMessage="No date was detected." end return finalObject end return p