Module:Sandbox/Takidelfin/Dates

-- Task 7 for Google Code-in | Date formatting) testList = {	"31 august 2013",	"31 August 2013",	"31 August 2013 (uncertain)",	"August 27, 2013",	"29 February 2004 (uncertain)",	"29 February 2005 (uncertain)",	"27/08/2013",	"04/27/2013",	"2013-08-27",	"2013 (uncertain)",	"27",	"27 December",	"27 2017",	"sometime around 27th August 2013",	"on the 16th of December in the year of our Lord 1770",	"on the 16th of December in the year skjdgfgjksdgjkheg hgj32g gh 11 of our Lord 1770",	"99 red balloons",	"sometime around 3rd August 2013",	"31 August 103 AD",	"31 August 2013 BC",	"31 August 2013 BCE",	"31 August 103 CE",	"31 August 13 BC",	"31 August 13",	"31 August 213",	"213",	"30 BCE",	"3 may 2017",	"3 Jan 2017",	"31 February 2013",	"the quick brown fox",	"4 and 20 blackbirds ...",	"3 jan 9 AD" } possiblePatterns = {	{ pattern = "(%d+) (%w+) (%d+)", format = "dmy" },	{ pattern = "(%d+)st (%w+), (%d+)", format = "dmy" }, { pattern = "(%d+)nd (%w+), (%d+)", format = "dmy" }, { pattern = "(%d+)rd (%w+), (%d+)", format = "dmy" }, { pattern = "(%d+)th (%w+), (%d+)", format = "dmy" }, { pattern = "(%d+)st (%w+) (%d+)", format = "dmy" }, { pattern = "(%d+)nd (%w+) (%d+)", format = "dmy" }, { pattern = "(%d+)rd (%w+) (%d+)", format = "dmy" }, { pattern = "(%d+)th (%w+) (%d+)", format = "dmy" }, { pattern = "(%d+)th of (%w+) .+ (%d+)", format = "dmy" }, { pattern = "(%w+) (%d+)th (%d+)", format = "mdy" }, { pattern = "(%w+) (%d+), (%d+)", format = "mdy" }, { pattern = "(%d+)/(%d+)/(%d+)", format = "dmy" }, { pattern = "(%d+)/(%d+)/(%d+)", format = "mdy" }, { pattern = "(%d+)/(%d+)", format = "my" }, { pattern = "(%d+)-(%d+)-(%d+)", format = "ymd" }, { pattern = "(%d+) (%w+)", format = "ym" }, { pattern = "(%d%d%d%d%d)", format = "y" }, { pattern = "(%d%d%d%d)", format = "y" }, { pattern = "(%d%d%d)", format = "y" }, { pattern = "year (%d+)", format = "y" }, { pattern = "(%d+)", format = "y" } }

local allowedFormats = { ["dmy"] = "$D$ $M$ $y$ $E$", ["mdy"] = "$M$ $D$ $y$ $E$", ["iso"] = "$Y$-$m$-$d$", ["year"] = "$y$ $E$", ["y"] = "$y$ $E$", ["my"] = "$M$ $y$ $E$" }

-- First object in the array is prefix, second is a suffx local circa = { { "sometime around", "around" }, { "%(uncertain%)" } } local eraSuffix = { { "AD", "CE" }, -- { "BC", "BCE" } }

local monthsDays = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } local monthsNames = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" } -- local months = { ["January"] = 31, ["February"] = 28, ["March"] = 31, ["April"] = 30, ["May"] = 31, ["June"] = 30, ["July"] = 31, ["August"] = 31, ["September"]= 30, ["October"]= 31, ["November"] = 30, ["December"] = 31 }

local p = {}

isLeapYear = function ( yearRaw ) year = tonumber(yearRaw) return (((year % 4 == 0) and (year % 100 ~= 0)) or (year % 400 == 0)) end

extractData = function ( dateRaw ) msg = "" matched = false -- print ('-- ' .. dateRaw .. ' --') for key, value in pairs(possiblePatterns) do		if matched ~= true then local date = {} date.circa = false --				Expected date look:					date.y = Integer					date.m = Object, {						length = Integer, <= 31						id = Integer, <= 12						name = String, vaild month name starting with capital letter					}					date.d = Integer, <= date.m.length					date.era = String, vaild era shorthand					date.circa = boolean

local match = {} match.match1, match.match2, match.match3 = dateRaw:match( value.pattern )

for ci, ciValue in pairs(circa) do				for cmi,cmiValue in pairs(ciValue) do					if ci == 1 then local patternDropped = value.pattern:gsub( "%)", "" ):gsub( "%(", "" ) local patternFinal = "(" .. cmiValue .. ") " .. patternDropped local circaPossibleMatch = dateRaw:match( patternFinal ) -- print(patternFinal) if circaPossibleMatch ~= nil then if string.lower( circaPossibleMatch ) == string.lower( cmiValue ) then date.circa = true end end else local patternDropped = value.pattern:gsub( "%)", "" ):gsub( "%(", "" ) local patternFinal = patternDropped .. " (" .. cmiValue .. ")" local circaPossibleMatch = dateRaw:match( patternFinal ) if circaPossibleMatch ~= nil then if string.lower( circaPossibleMatch ) == string.lower( cmiValue:gsub("%%%(", "("):gsub("%%%)", ")") ) then date.circa = true end end end end -- match.circa = dateRaw:match( "%d+ (AB)" ) end

for ei=1,2 do				-- suffix or preffix local era = eraSuffix[ei] for emi, emiPattern in pairs(eraSuffix[ei]) do					local patternDropped = value.pattern:gsub( "%)", "" ):gsub( "%(", "" ) local patternFinal = patternDropped .. " (" .. emiPattern .. ")" local circaPossibleMatch = dateRaw:match( patternFinal ) if circaPossibleMatch ~= nil then if string.lower( circaPossibleMatch ) == string.lower( emiPattern ) then date.era = emiPattern end end end end -- Each value.pattern (pattern) has got an value.format (date format) associated with it			-- this loops iterates thorough them and assings variables to proper value.formats

for i=1,string.len( value.format ) + 1 do date[string.sub( value.format, i, i)] = match["match" .. i]			end

if tonumber(date.y) ~= nil then date.y = tonumber(date.y)				matched = true end

if date.m ~= nil and date.m ~= "" then matched = false local month = {} if tonumber(date.m) ~= nil then if (tonumber( date.m ) or 13) <= 12 then month.name = monthsNames[tonumber( date.m )] month.length = monthsDays[tonumber( date.m )] month.id = tonumber( date.m ) matched = true else if value.format == 'mdy' then -- print('INVALID ENTRY\n\n') return "Invalid entry" end end elseif #date.m > 1 then for mi=1,#monthsNames do						local monthPrefixPossible = string.upper( string.sub( monthsNames[mi], 1, 3 ) ) local monthPrefix = string.upper( string.sub( date.m, 1, 3 ) ) if monthPrefix == monthPrefixPossible then -- print(monthPrefix, monthPrefixPossible) month.name = monthsNames[mi] month.length = monthsDays[mi] month.id = mi							matched = true end end else date.m = nil end

if month.name ~= nil and month.length ~= nil then matched = true date.m = month if date.d ~= nil and date.d ~= "" then matched = false date.d = tonumber( date.d ) if date.m.length == 29 and date.y ~= nil then if isLeapYear(date.y) == true and date.d <= 29 then matched = true else -- print('INVALID ENTRY\n\n') matched = true return "Invalid entry" end elseif not (date.d <= month.length) then date.d = nil else matched = true end end end end if matched == true then -- local dateMsg = "" -- if date.y then -- dateMsg = dateMsg .. date.y				-- end -- if date.m then -- 	dateMsg = dateMsg .. " " .. date.m.name -- end -- if date.d then -- 	dateMsg = dateMsg .. " " .. date.d				-- end -- if date.circa then -- 	dateMsg = "circa " .. dateMsg -- end -- if date.era then -- 	dateMsg = dateMsg .. " " .. date.era -- end -- print( 'FORMAT: ' .. value.format ) -- print( 'PATTERN: ' .. value.pattern ) -- print( 'CIRCA: ' .. tostring(date.circa) ) -- print( 'ERA: ' .. tostring(date.era) ) -- print( '--- Result ---' ) -- print(dateMsg) -- print('\n\n') return date end end end end

formatDate = function ( date, formatRaw ) if (date == nil) or (date == 'Invalid entry') then return 'Invalid entry' elseif formatRaw ~= nil then local format = allowedFormats[formatRaw] or 'invalidFormat' if format == 'invalidFormat' then local returnValue = "" if date.d then returnValue = returnValue .. date.d .. " "			end if date.m then returnValue = returnValue .. date.m.name .. " "			end if date.y then returnValue = returnValue .. date.y .. " "			end if date.circa then returnValue = "circa " .. returnValue end if date.era then returnValue = returnValue .. date.era .. " "			end return returnValue end

local finalFormat = format local formatIterator = format:gsub("%$", "") for l=1,#formatIterator do			local char = formatIterator:sub(l,l) -- Day if char == "d" then if date.d == nil then if formatRaw == "iso" then return "Invalid entry" end finalFormat = finalFormat:gsub("%$d%$", "") elseif #tostring(date.d) < 2 then finalFormat = finalFormat:gsub("%$d%$", 0 .. date.d)				else finalFormat = finalFormat:gsub("%$d%$", date.d)				end elseif char == "D" then if date.d == nil then if formatRaw == "iso" then return "Invalid entry" end finalFormat = finalFormat:gsub("%$D%$", "") else finalFormat = finalFormat:gsub("%$D%$", date.d)				end end -- Year if char == "y" then if date.y == nil then finalFormat = finalFormat:gsub("%$y%$", "") else finalFormat = finalFormat:gsub("%$y%$", date.y)				end elseif char == "Y" then if date.y == nil then if formatRaw == "iso" then return "Invalid entry" end finalFormat = finalFormat:gsub("%$Y%$", "") elseif 0 <= date.y and date.y <= 9999 then local zero = "0" finalFormat = finalFormat:gsub("%$Y%$", zero:rep(4 - #tostring(date.y)) .. date.y)				else return "Invalid entry" end end -- Era if char == "E" then if date.era == nil then if formatRaw == "iso" then return "Invalid entry" end finalFormat = finalFormat:gsub("%$E%$", "") else finalFormat = finalFormat:gsub("%$E%$", date.era) end end -- Month if char == "m" then if date.m == nil then if formatRaw == "iso" then return "Invalid entry" end finalFormat = finalFormat:gsub("%$m%$", "") else local zero = "0" finalFormat = finalFormat:gsub("%$m%$", zero:rep(2 - #tostring(date.m.id)) .. date.m.id) end elseif char == "M" then if date.m == nil then if formatRaw == "iso" then return "Invalid entry" end finalFormat = finalFormat:gsub("%$M%$", "") else finalFormat = finalFormat:gsub("%$M%$", date.m.name) end end end -- Circa if date.circa then if formatRaw == "iso" then return "ISO format does not allow uncertain dates" end finalFormat = "circa " .. finalFormat end

if date.era and formatRaw == "iso" then return "ISO format does not allow embedding era into date" end

finalFormat = finalFormat:gsub("^%s*(.-)%s*$", "%1") finalFormat = finalFormat:gsub("%s+", " ") return finalFormat else local returnValue = "" if date.d then returnValue = returnValue .. date.d .. " "		end if date.m then returnValue = returnValue .. date.m.name .. " "		end if date.y then returnValue = returnValue .. date.y .. " "		end if date.circa then returnValue = "circa " .. returnValue end if date.era then returnValue = returnValue .. date.era .. " "		end return returnValue end end

p.convertTest = function ( frame ) local msg = "" for i=1, #testList do		local testDate = testList[i] msg = msg .. " Raw date: " .. testDate for key,value in pairs(allowedFormats) do msg = msg .. " Format: " .. key .. " | Result: " .. formatDate( extractData( testDate ), key ) .. "" end msg = msg .. "" end return msg end

p.convertDate = function ( frame ) local date = frame.args.date local format = frame.args.format if date == nil or date == "" then return "Invalid entry" end return formatDate( extractData( date ), format ) end

return p