Documentation for this module may be created at Module:Sandbox/Takidelfin/Dates/doc
-- 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 .. "<br/> <ul>Raw date: " .. testDate
for key,value in pairs(allowedFormats) do
msg = msg .. "<li> <b>Format:</b> " .. key .. " | <b>Result:</b> " .. formatDate( extractData( testDate ), key ) .. "</li>"
end
msg = msg .. "</ul>"
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