local main = {};
local monthIndices = {
['january'] = 1,
['february'] = 2,
['march'] = 3,
['april'] = 4,
['may'] = 5,
['june'] = 6,
['july'] = 7,
['august'] = 8,
['september'] = 9,
['october'] = 10,
['november'] = 11,
['december'] = 12
}
local monthDays = {
[1] = 31,
[2] = 29, -- will check below
[3] = 31,
[4] = 30,
[5] = 31,
[6] = 30,
[7] = 31,
[8] = 31,
[9] = 30,
[10] = 31,
[11] = 30,
[12] = 31
}
function tryParseDateTime(input)
--1 May 1973, 10:38:27
--10:38:27, 1 May 1973
--10:38am
--10am
local matchDay, matchMonth, matchYear
local matchHour, matchMinute, matchSecond
-- Try dMy hms
matchDay, matchMonth, matchYear, matchHour, matchMinute, matchSecond = input:match('^([0-9][0-9]?) ([A-Za-z]+) ([0-9][0-9][0-9][0-9]),? ([0-9][0-9]?):([0-9][0-9]?):([0-9][0-9]?)$')
if (matchDay) then
local month = monthIndices[matchMonth:lower()]
if (month == nil) then return nil end
return tonumber(matchDay), month, tonumber(matchYear), tonumber(matchHour), tonumber(matchMinute), tonumber(matchSecond)
end
-- Try Mdy hms
matchMonth, matchDay, matchYear, matchHour, matchMinute, matchSecond = input:match('^([A-Za-z]+) ([0-9][0-9]?),? ([0-9][0-9][0-9][0-9]),? ([0-9][0-9]?):([0-9][0-9]?):([0-9][0-9]?)$')
if (matchDay) then
local month = monthIndices[matchMonth:lower()]
if (month == nil) then return nil end
return tonumber(matchDay), month, tonumber(matchYear), tonumber(matchHour), tonumber(matchMinute), tonumber(matchSecond)
end
-- Try hms dMy
matchHour, matchMinute, matchSecond, matchDay, matchMonth, matchYear = input:match('^([0-9][0-9]?):([0-9][0-9]?):([0-9][0-9]?),? ([0-9][0-9]?) ([A-Za-z]+) ([0-9][0-9][0-9][0-9])$')
if (matchDay) then
local month = monthIndices[matchMonth:lower()]
if (month == nil) then return nil end
return tonumber(matchDay), month, tonumber(matchYear), tonumber(matchHour), tonumber(matchMinute), tonumber(matchSecond)
end
-- Try hms Mdy
matchHour, matchMinute, matchSecond, matchMonth, matchDay, matchYear = input:match('^([0-9][0-9]?):([0-9][0-9]?):([0-9][0-9]?),? ([A-Za-z]+) ([0-9][0-9]?),? ([0-9][0-9][0-9][0-9])$')
if (matchDay) then
local month = monthIndices[matchMonth:lower()]
if (month == nil) then return nil end
return tonumber(matchDay), month, tonumber(matchYear), tonumber(matchHour), tonumber(matchMinute), tonumber(matchSecond)
end
-- todo others
-- Well, we ran out of valid date formats
return nil
end
function tryParseDateOnly(input)
local matchDay, matchMonth, matchYear
-- Try Y
matchYear = input:match('^([0-9][0-9][0-9][0-9])$')
if (matchYear) then
return nil, nil, tonumber(matchYear)
end
-- Try My
matchMonth, matchYear = input:match('^([A-Za-z]+) ([0-9][0-9][0-9][0-9])$')
if (matchMonth) then
local month = monthIndices[matchMonth:lower()]
if (month == nil) then return nil end
return nil, month, tonumber(matchYear)
end
-- Try dMy
matchDay, matchMonth, matchYear = input:match('^([0-9][0-9]?) ([A-Za-z]+) ([0-9][0-9][0-9][0-9])$')
if (matchDay) then
local month = monthIndices[matchMonth:lower()]
if (month == nil) then return nil end
return tonumber(matchDay), month, tonumber(matchYear)
end
-- Try Mdy
matchMonth, matchDay, matchYear = input:match('^([A-Za-z]+) ([0-9][0-9]?),? ([0-9][0-9][0-9][0-9])$')
if (matchDay) then
local month = monthIndices[matchMonth:lower()]
if (month == nil) then return nil end
return tonumber(matchDay), month, tonumber(matchYear)
end
-- Try ymd
matchYear, matchMonth, matchDay = input:match('^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$')
if (matchDay) then
return tonumber(matchDay), tonumber(matchMonth), tonumber(matchYear)
end
-- Well, we ran out of valid date formats
return nil
end
function checkIfDayValid(day, month)
-- First check that the month can have at least this many days
if (day > monthDays[month]) then return false end
-- February leap year check
if (month == 2) then
if (day == 29 and not ((year % 4 == 0) and (year % 100 ~= 0) or (year % 400 == 0))) then
return false
end
end
return true
end
function checkIfMonthValid(year)
return month ~= 0 and month <= 12 -- <0 never happens with [0-9] pattern
end
function checkIfYearValid(year)
return year >= 1583 -- up to 9999
end
function checkIfYearValid(hour)
return hour < 24 -- <0 never happens with [0-9] pattern
end
function checkIfMinuteValid(minute)
return minute < 60 -- <0 never happens with [0-9] pattern
end
function checkIfSecondValid(second)
return second < 60 -- <0 never happens with [0-9] pattern
end
function main.parseDate(frame)
local input = frame.args[1]
-- First, try to get a date and time
local day, month, year, hour, minute, second = tryParseDateTime(input)
-- If we have a second, we have all 6
if (second) then
if (not checkIfYearValid(year) or not checkIfMonthValid(month) or not checkIfDayValid(day, month) or not checkIfHourValid(hour) or not checkIfMinuteValid(minute) or not checkIfSecondValid(second)) then return nil end
return string.format("%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
end
-- Second, try to get just the date
day, month, year = tryParseDateOnly(input)
-- No year, no date
if (not year) then return nil end
if (year and not month) then
-- year only
if (not checkIfYearValid(year)) then return nil end
return year
end
if (year and month and not day) then
-- month and year only
if (not checkIfYearValid(year)) then return nil end
-- Month always comes from month name pattern string, so always valid, can skip the check
return string.format("%d-%02d", year, month)
end
if (not checkIfYearValid(year) or not checkIfMonthValid(month) or not checkIfDayValid(day, month) or not checkIfHourValid(hour)) then return nil end
--parent stuff (infobox itself)
--local parent = frame:getParent()
--local parentArgs = parent.args
return string.format("%d-%02d-%02d", year, month, day)
end
function main.emitMetadata(frame)
-- First parse the date and see if we get a valid output date
local date = main.parseDate(frame)
if (not date) then return nil end
local spanClass = frame.args.spanClass or 'bday dtstart published updated'
return '<span style="display:none"> (<span class="' .. spanClass .. '">' .. date .. '</span>)</span>'
end
return main