มอดูล:Infobox date field metadata

local main = {};
local monthIndices = {
'มกราคม' = 1,
'กุมภาพันธ์' = 2,
'มีนาคม' = 3,
'เมษายน' = 4,
'พฤษภาคม' = 5,
'มิถุนายน' = 6,
'กรกฎาคม' = 7,
'สิงหาคม' = 8,
'กันยายน' = 9,
'ตุลาคม' = 10,
'พฤศจิกายน' = 11,
'ธันวาคม' = 12,
'ม.ค.' = 1,
'ก.พ.' = 2,
'มี.ค.' = 3,
'เม.ย.' = 4,
--'พ.ค.' = 5, -- long one would have caught this already
'มิ.ย.' = 6,
'ก.ค.' = 7,
'ส.ค.' = 8,
'ก.ย.' = 9,
'ต.ค.' = 10,
'พ.ย.' = 11,
'ธ.ค.' = 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 checkIfDayValid(day, month, year)
-- First check that the month can have at least this many days
if (day > monthDaysmonth) then return false end
-- February leap year check
if (month == 2) then
-- On Feb 29, if we don't have a year (incomplete date), assume 29 can be valid once every 
if (day == 29 and not year) then return true end
-- On Feb 29, check for valid year - every 4 but not 100 but 400
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(month)
return month ~= 0 and month = 1583 -- up to 9999
end
function checkIfHourValid(hour)
return hour  mw.ustring.len(seekString))
	--else -- let it capture digits again, this time '10am' '8p.m.' will be separate
	--	return ELEMENT_INVALID -- not a valid ordinal indicator
		end
--else -- let it capture digits again, this time '10am' '8p.m.' will be separate
--    return ELEMENT_INVALID -- just the invalid, the number of digits (3+) won't match any patterns
end
end

-- Digits
local foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(0-9+)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
-- Additionally check how many digits we actually have, as arbitrary number isn't valid
if (#foundMatch  mw.ustring.len(seekString))
elseif (#foundMatch == 4) then -- most likely a year
return ELEMENT_FOURDIGITS, tonumber(foundMatch), (currentPosition > mw.ustring.len(seekString))
else
return ELEMENT_INVALID -- just the invalid, the number of digits (3 or 5+) won't match any patterns
end
end
-- Time period - a.m./p.m. (before letters)
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^%s*(ap%.?m%.?)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_TIMEPERIOD, foundMatch, (currentPosition > mw.ustring.len(seekString))
end
-- Word
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(A-Za-z+)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
if (#foundMatch >= 3) then
-- Find the possible month name index
monthIndex = monthIndicesmw.ustring.lower(foundMatch)
if (monthIndex) then
return ELEMENT_MONTHWORD, monthIndex, (currentPosition > mw.ustring.len(seekString))
else
return ELEMENT_INVALID -- just the invalid, the word didn't match a valid month name
end
else
-- TODO LETTERS
return ELEMENT_INVALID -- just the invalid, the word was too short to be valid month name
end
end
-- Time separator (colon without whitespace)
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(:)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_TIMESEPARATOR, foundMatch, (currentPosition > mw.ustring.len(seekString))
end
-- Comma and any following whitespace
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(,%s*)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_COMMA, foundMatch, (currentPosition > mw.ustring.len(seekString))
end

-- Period and any following whitespace ('Feb. 2010' or '29. June')
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(%.%s*)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_PERIODWHITESPACE, foundMatch, (currentPosition > mw.ustring.len(seekString))
end

-- Dash with possible whitespace or Date separator (dash without whitespace)
-- Both non-breaking spaces - ' - '
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^( %-–— )', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_DASH, foundMatch, (currentPosition > mw.ustring.len(seekString))
end
-- Non-breaking space - ' - '
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^( %-–—%s*)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_DASH, foundMatch, (currentPosition > mw.ustring.len(seekString))
end
-- Dash entity code and both non-breaking spaces - ' – '
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^( &nmdash; )', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_DASH, foundMatch, (currentPosition > mw.ustring.len(seekString))
end
-- Dash entity code and non-breaking space - ' – '
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^( &nmdash;%s*)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_DASH, foundMatch, (currentPosition > mw.ustring.len(seekString))
end  
-- Dash entity code - ' – '
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(%s*&nmdash;%s*)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_DASH, foundMatch, (currentPosition > mw.ustring.len(seekString))
end 
-- Regular whitespace
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(%s*%-–—%s*)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location

if (foundMatch == '-') then -- nothing else is date separator, no hyphens no stuff like that
return ELEMENT_DATESEPARATOR, foundMatch, (currentPosition > mw.ustring.len(seekString))
else
return ELEMENT_DASH, foundMatch, (currentPosition > mw.ustring.len(seekString)) -- we will actually need to check for DATESEPARATOR as well, as that one stole the '-' case
end
end
-- Whitespace (after all others that capture whitespace)
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^(%s+)', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_WHITESPACE, foundMatch, (currentPosition > mw.ustring.len(seekString))
end
-- Whitespace -- same as above but using   (other that for dashes)
foundPositionStart, foundPositionEnd, foundMatch = mw.ustring.find(seekString, '^ ', currentPosition)
if (foundPositionStart) then
currentPosition = foundPositionEnd + 1 -- this is our new start location
return ELEMENT_WHITESPACE, foundMatch, (currentPosition > mw.ustring.len(seekString))
end
return ELEMENT_INVALID -- just the invalid, we won't be parsing this further
end
function parseDateString(input)
-- Reset our seek string and position
seekString = input
currentPosition = 1
local elements = {}
local values = {}
-- Seek the entire string now
local numberOfElements = 0
repeat
foundElement, foundValue, eos = seekNextElement()
-- If we found something we can't process, return as unparsable
if (foundElement == ELEMENT_INVALID) then return nil end
numberOfElements = numberOfElements + 1
elementsnumberOfElements = foundElement
valuesnumberOfElements = foundValue
until eos
--
local s = input .. ' -> ' .. numberOfElements .. ' elements: '
for currentElementIndex = 1, numberOfElements do
s = s .. ' #' .. elementscurrentElementIndex .. '=' .. valuescurrentElementIndex
end
do return s end  

-- Now comes an uber-deep if-then-else tree
-- This is roughly the most efficient step-by-step parsing, something like log(N)
-- Doing each combination via pattern/"Regex" is way slower
-- Having each combination a clean function/preset means checking every element, so way slower
-- Only immediate big improvement is to only seekNextElement() when actually checking that deep, though this will make a (even bigger) mess
if (elements1 == ELEMENT_ONETWODIGITS or elements1 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- '3' or '10' or '12th'
if (elements2 == ELEMENT_WHITESPACE or elements2 == ELEMENT_PERIODWHITESPACE) then -- '3 ' or '3. '
if (elements3 == ELEMENT_MONTHWORD) then -- '3 May'
if (numberOfElements == 3) then return checkAndOutput(nil, values3, values1, nil, nil, nil) end
if (elements4 == ELEMENT_WHITESPACE or elements4 == ELEMENT_PERIODWHITESPACE or elements4 == ELEMENT_COMMA) then -- '3 May ' or '3 Feb. ' or '3 May, '
if (elements5 == ELEMENT_FOURDIGITS) then -- '3 May 2013'
if (numberOfElements == 5) then return checkAndOutput(values5, values3, values1, nil, nil, nil) end
if (elements6 == ELEMENT_WHITESPACE or elements6 == ELEMENT_COMMA) then -- '3 May 2013, '
if (elements7 == ELEMENT_ONETWODIGITS) then -- '3 May 2013, 10'
if (elements8 == ELEMENT_TIMEPERIOD) then -- '3 May 2013, 10 am'
if (numberOfElements == 8) then return checkAndOutput(values5, values3, values1, values7 + periodHourAdd(values8), nil, nil) end
elseif (elements8 == ELEMENT_TIMESEPARATOR) then -- '3 May 2013, 10:'
if (elements9 == ELEMENT_ONETWODIGITS) then -- '3 May 2013, 10:38'
if (numberOfElements == 9) then return checkAndOutput(values5, values3, values1, values7, values9, nil) end
if (elements10 == ELEMENT_TIMEPERIOD) then -- '3 May 2013, 10:38 am'
if (numberOfElements == 10) then return checkAndOutput(values5, values3, values1, values7 + periodHourAdd(values10), values9, nil) end
elseif (elements10 == ELEMENT_TIMESEPARATOR) then -- '3 May 2013, 10:38:'
if (elements11 == ELEMENT_ONETWODIGITS) then -- '3 May 2013, 10:38:27'
if (numberOfElements == 11) then return checkAndOutput(values5, values3, values1, values7, values9, values11) end
if (elements12 == ELEMENT_TIMEPERIOD) then -- '3 May 2013, 10:38:27 am'
if (numberOfElements == 12) then return checkAndOutput(values5, values3, values1, values7 + periodHourAdd(values12), values9, values11) end
end
end                                            
end                                        
end                                    
end                                
end
elseif (elements6 == ELEMENT_DASH or elements6 == ELEMENT_DATESEPARATOR) then -- '3 May 2013 - '
if (elements7 == ELEMENT_ONETWODIGITS or elements7 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- '3 May 2013 - 12' or '3rd May 2013 - 12th'
if (elements8 == ELEMENT_WHITESPACE) then -- '3 May 2013 - 12 '
if (elements9 == ELEMENT_MONTHWORD) then -- '3 May 2013 - 12 February'
if (elements10 == ELEMENT_WHITESPACE) then -- '3 May 2013 - 12 February '
if (elements11 == ELEMENT_FOURDIGITS) then -- '3 May 2013 - 12 February 2014'
if (numberOfElements == 11) then return checkAndOutput(values5, values3, values1, nil, nil, nil, values11, values9, values7, nil, nil, nil) end
end
end
end
end
end
end
end                
elseif (elements4 == ELEMENT_DASH or elements4 == ELEMENT_DATESEPARATOR) then -- '3 May - '
if (elements5 == ELEMENT_ONETWODIGITS or elements5 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- '3 May - 12' or '3rd May - 12th'
if (elements6 == ELEMENT_WHITESPACE) then -- '3 May - 12 '
if (elements7 == ELEMENT_MONTHWORD) then -- '3 May - 12 October'
if (numberOfElements == 7) then return checkAndOutput(nil, values3, values1, nil, nil, nil, nil, values7, values5, nil, nil, nil) end
if (elements8 == ELEMENT_COMMA or elements8 == ELEMENT_WHITESPACE) then -- '3 May - 12 October '
if (elements9 == ELEMENT_FOURDIGITS) then -- '3 May - 12 October 2013'
if (numberOfElements == 9) then return checkAndOutput(values9, values3, values1, nil, nil, nil, values9, values7, values5, nil, nil, nil) end
end
end
end
end
end
end
end
elseif (elements2 == ELEMENT_DASH or elements2 == ELEMENT_DATESEPARATOR) then -- '3 - '
if (elements3 == ELEMENT_ONETWODIGITS or elements3 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- '3 - 12' or '3rd - 12th'
if (elements4 == ELEMENT_WHITESPACE) then -- '3 - 12 '
if (elements5 == ELEMENT_MONTHWORD) then -- '3 - 12 May'
if (numberOfElements == 5) then return checkAndOutput(nil, values5, values1, nil, nil, nil, nil, values5, values3, nil, nil, nil) end
if (elements6 == ELEMENT_COMMA or elements6 == ELEMENT_WHITESPACE) then -- '3 - 12 May '
if (elements7 == ELEMENT_FOURDIGITS) then -- '3 - 12 May 2013'
if (numberOfElements == 7) then return checkAndOutput(values7, values5, values1, nil, nil, nil, values7, values5, values3, nil, nil, nil) end
end
end
end
end
end
end
-- Here's a case where we want to optimize or rather add readability and trim redundancy
-- Basically, any time '10am', '10:28', '10:28am', '10:28:27', '10:28:27am' can be followed by a date, which means 5 copies of 30+ lines (urgh)
-- Instead we will only check once, but using a different element index offset if needed, so date might start at element 4 or 5 or 6 etc.
-- Currently we only have '10', but if it turns out to be a time we will be checking if it's followed by a date
local wasTime = false -- by default we didn't find a valid time syntax, we have '10' and that's not a time by itself without 'am/pm' or further precision
local possibleHour, possibleMinute, possibleSecond -- temporary values that we will fill as far as we can when parsing time and use for time+date combo if one is found
local i = 0 -- this is our offset from the closest possible location for date seeking
if (elements2 == ELEMENT_TIMESEPARATOR and elements1 ~= ELEMENT_ONETWODIGITSWITHORDINAL) then -- '10:' but not '10th:'
possibleHour = values1 -- only once we see ':' (or 'am' below) it is likely a time
if (elements3 == ELEMENT_ONETWODIGITS) then -- '10:28'
if (numberOfElements == 3) then return checkAndOutput(nil, nil, nil, values1, values3, nil) end
possibleMinute = values3
wasTime = true -- this is a valid final time, so we can check date appended to this
i = 1 -- '10', ':' and '28' are three elements, so we start 1 further from 3
if (elements4 == ELEMENT_TIMESEPARATOR) then -- '10:28:'
wasTime = false -- a time can't end with separator, so if this is last match, we aren't appending any dates
if (elements5 == ELEMENT_ONETWODIGITS) then -- '10:28:27'
if (numberOfElements == 5) then return checkAndOutput(nil, nil, nil, values1, values3, values5) end
possibleSecond = values5
wasTime = true -- this is a valid final time, so we can check date appended to this
i = 3 -- '10', ':', '28', ':' and '27' are five elements, so we start 3 further from 3
if (elements6 == ELEMENT_TIMEPERIOD) then -- '10:28:27 am'
if (numberOfElements == 6) then return checkAndOutput(nil, nil, nil, values1 + periodHourAdd(values6), values3, values5) end
possibleHour = values1 + periodHourAdd(values6) -- hour now needs possible adjusting since we saw a time period
-- wasTime = true -- already set
i = 4 -- '10', ':', '28', ':', '27' and 'am' are six elements, so we start 4 further from 3
end
end
elseif (elements4 == ELEMENT_TIMEPERIOD) then -- '10:28 am'
if (numberOfElements == 4) then return checkAndOutput(nil, nil, nil, values1 + periodHourAdd(values4), values3, nil) end
wasTime = true -- this is a valid final time, so we can check date appended to this
possibleHour = values1 + periodHourAdd(values4) -- hour now needs possible adjusting since we saw a time period
i = 2 -- '10', ':', '28' and 'am' are four elements, so we start 2 further from 3
end
end
elseif (elements2 == ELEMENT_TIMEPERIOD) then -- '10 am'
if (numberOfElements == 2) then return checkAndOutput(nil, nil, nil, values1 + periodHourAdd(values2), nil, nil) end
possibleHour = values1 + periodHourAdd(values2) -- only once we see 'am' (or ':' above) it is likely a time
wasTime = true -- this is a valid final time, so we can check date appended to this
i = 0 -- '10' and 'am' are two elements, so we start at 3 - default
end
if (wasTime) then -- '10am', '10:28', '10:28am', '10:28:27', '10:28:27am' (using just '10:28:27...' below)
-- Now we will try to append a date to the time
if (elements3+i == ELEMENT_WHITESPACE or elements3+i == ELEMENT_COMMA) then -- '10:28:27, '
if (elements4+i == ELEMENT_ONETWODIGITS) then -- '10:28:27, 3'
if (elements5+i == ELEMENT_WHITESPACE) then -- '10:28:27, 3 '
if (elements6+i == ELEMENT_MONTHWORD) then -- '10:28:27, 3 May'
if (elements7+i == ELEMENT_WHITESPACE) then -- '10:28:27, 3 May '
if (elements8+i == ELEMENT_FOURDIGITS) then -- '10:28:27, 3 May 2013'
if (numberOfElements == 8+i) then return checkAndOutput(values8+i, values6+i, values4+i, possibleHour, possibleMinute, possibleSecond) end
end
end
end
end
elseif (elements4+i == ELEMENT_MONTHWORD) then -- '10:28:27, May'
if (elements5+i == ELEMENT_WHITESPACE) then -- '10:28:27, May '
if (elements6+i == ELEMENT_ONETWODIGITS) then -- '10:28:27, May 3'
if (elements7+i == ELEMENT_COMMA or elements7+i == ELEMENT_WHITESPACE) then -- '10:28:27, May 3, '
if (elements8+i == ELEMENT_FOURDIGITS) then -- '10:28:27, May 3, 2013'
if (numberOfElements == 8+i) then return checkAndOutput(values8+i, values4+i, values6+i, possibleHour, possibleMinute, possibleSecond) end
end
end
end
end
elseif (elements4+i == ELEMENT_FOURDIGITS) then -- '10:28:27, 2013'
if (elements5+i == ELEMENT_DATESEPARATOR) then -- '10:28:27, 2013-'
if (elements6+i == ELEMENT_ONETWODIGITS) then -- '10:28:27, 2013-05'
if (elements7+i == ELEMENT_DATESEPARATOR) then -- '10:28:27, 2013-05-'
if (elements8+i == ELEMENT_ONETWODIGITS) then -- '10:28:27, 2013-05-03'
if (numberOfElements == 8+i) then return checkAndOutput(values4+i, values6+i, values8+i, possibleHour, possibleMinute, possibleSecond) end
end
end
end
end
end
end
end
elseif (elements1 == ELEMENT_FOURDIGITS) then -- '2013'
if (numberOfElements == 1) then return checkAndOutput(values1, nil, nil, nil, nil, nil) end
if (elements2 == ELEMENT_DATESEPARATOR) then -- '2013-'
if (elements3 == ELEMENT_ONETWODIGITS) then -- '2013-05'
--if (numberOfElements == 3) then return checkAndOutput(values1, values3, nil, nil, nil, nil) end
-- This is actually ambiguous -- 2008-12 can be years 2008 to 2012 or it could be Decemeber 2008; few cases, so just ignoring
if (elements4 == ELEMENT_DATESEPARATOR) then -- '2013-05-'
if (elements5 == ELEMENT_ONETWODIGITS) then -- '2013-05-03'
if (numberOfElements == 5) then return checkAndOutput(values1, values3, values5, nil, nil, nil) end
if (elements6 == ELEMENT_WHITESPACE or elements6 == ELEMENT_COMMA) then -- '2013-05-03, '
if (elements7 == ELEMENT_ONETWODIGITS) then -- '2013-05-03, 10'
if (elements8 == ELEMENT_TIMEPERIOD) then -- '2013-05-03, 10 am'
if (numberOfElements == 8) then return checkAndOutput(values1, values3, values5, values7 + periodHourAdd(values8), nil, nil) end
elseif (elements8 == ELEMENT_TIMESEPARATOR) then -- '2013-05-03, 10:'
if (elements9 == ELEMENT_ONETWODIGITS) then -- '2013-05-03, 10:38'
if (numberOfElements == 9) then return checkAndOutput(values1, values3, values5, values7, values9, nil) end
if (elements10 == ELEMENT_TIMEPERIOD) then -- '2013-05-03, 10:38 am'
if (numberOfElements == 10) then return checkAndOutput(values1, values3, values5, values7 + periodHourAdd(values10), values9, nil) end
elseif (elements10 == ELEMENT_TIMESEPARATOR) then -- '2013-05-03, 10:38:'
if (elements11 == ELEMENT_ONETWODIGITS) then -- '2013-05-03, 10:38:27'
if (numberOfElements == 11) then return checkAndOutput(values1, values3, values5, values7, values9, values11) end
if (elements12 == ELEMENT_TIMEPERIOD) then -- '2013-05-03, 10:38:27 am'
if (numberOfElements == 12) then return checkAndOutput(values1, values3, values5, values7 + periodHourAdd(values12), values9, values11) end
end
end
end
end
end
end
end
end
end
end
end -- can't elseif, because we have ELEMENT_DATESEPARATOR, which repeats above
if (elements2 == ELEMENT_DASH or elements2 == ELEMENT_DATESEPARATOR) then -- '2013 - '
if (elements3 == ELEMENT_FOURDIGITS) then -- '2013 - 2014'
if (numberOfElements == 3) then return checkAndOutput(values1, nil, nil, nil, nil, nil, values3, nil, nil, nil, nil, nil) end
end
elseif (elements2 == ELEMENT_WHITESPACE or elements2 == ELEMENT_COMMA) then -- '2013 ' or '2013, '
if (elements3 == ELEMENT_MONTHWORD) then -- '2013 May'
if (numberOfElements == 3) then return checkAndOutput(values1, values3, nil, nil, nil, nil) end
-- 2013 May - 2013 April (let's see first if this is ever used real-world)
if (elements4 == ELEMENT_WHITESPACE) then -- '2013 May '
		if (elements5 == ELEMENT_ONETWODIGITS) then -- '2013 May 15'
			if (numberOfElements == 5) then return checkAndOutput(values1, values3, values5, nil, nil, nil) end
			end
			end
elseif (elements3 == ELEMENT_ONETWODIGITS) then -- '2013 15' or '2013, 15'
if (elements4 == ELEMENT_WHITESPACE) then -- '2013 15 '
		if (elements5 == ELEMENT_MONTHWORD) then -- '2013 15 May'
			if (numberOfElements == 5) then return checkAndOutput(values1, values5, values3, nil, nil, nil) end
			end
			end
end
end
elseif (elements1 == ELEMENT_MONTHWORD) then -- 'May'
if (numberOfElements == 1) then return checkAndOutput(nil, values1, nil, nil, nil, nil) end
if (elements2 == ELEMENT_WHITESPACE or elements2 == ELEMENT_PERIODWHITESPACE) then -- 'May ' or 'Feb. '
if (elements3 == ELEMENT_ONETWODIGITS or elements3 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- 'May 3' or 'May 3rd'
if (numberOfElements == 3) then return checkAndOutput(nil, values1, values3, nil, nil, nil) end
if (elements4 == ELEMENT_COMMA or elements4 == ELEMENT_WHITESPACE) then -- 'May 3, '
if (elements5 == ELEMENT_FOURDIGITS) then -- 'May 3, 2013'
if (numberOfElements == 5) then return checkAndOutput(values5, values1, values3, nil, nil, nil) end
if (elements6 == ELEMENT_WHITESPACE or elements6 == ELEMENT_COMMA) then -- May 3, 2013, '
if (elements7 == ELEMENT_ONETWODIGITS) then -- May 3, 2013, 10'
if (elements8 == ELEMENT_TIMEPERIOD) then -- May 3, 2013, 10 am'
if (numberOfElements == 8) then return checkAndOutput(values5, values1, values3, values7 + periodHourAdd(values8), nil, nil) end
elseif (elements8 == ELEMENT_TIMESEPARATOR) then -- May 3, 2013, 10:'
if (elements9 == ELEMENT_ONETWODIGITS) then -- May 3, 2013, 10:38'
if (numberOfElements == 9) then return checkAndOutput(values5, values1, values3, values7, values9, nil) end
if (elements10 == ELEMENT_TIMEPERIOD) then -- May 3, 2013, 10:38 am'
if (numberOfElements == 10) then return checkAndOutput(values5, values1, values3, values7 + periodHourAdd(values10), values9, nil) end
elseif (elements10 == ELEMENT_TIMESEPARATOR) then -- May 3, 2013, 10:38:'
if (elements11 == ELEMENT_ONETWODIGITS) then -- May 3, 2013, 10:38:27'
if (numberOfElements == 11) then return checkAndOutput(values5, values1, values3, values7, values9, values11) end
if (elements12 == ELEMENT_TIMEPERIOD) then -- May 3, 2013, 10:38:27 am'
if (numberOfElements == 12) then return checkAndOutput(values5, values1, values3, values7 + periodHourAdd(values12), values9, values11) end
end
end
end
end
end
end
elseif (elements6 == ELEMENT_DASH or elements6 == ELEMENT_DATESEPARATOR) then -- 'May 3, 2013 - '
if (elements7 == ELEMENT_MONTHWORD) then -- 'May 3, 2013 - February'
if (elements8 == ELEMENT_WHITESPACE) then -- 'May 3, 2013 - February '
if (elements9 == ELEMENT_ONETWODIGITS or elements3 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- 'May 3, 2013 - February 12' or 'May 3rd, 2013 - February 12th'
if (elements10 == ELEMENT_COMMA or elements10 == ELEMENT_WHITESPACE) then -- 'May 3, 2013 - February 12, '
if (elements11 == ELEMENT_FOURDIGITS) then -- 'May 3, 2013 - February 12, 2014'
if (numberOfElements == 11) then return checkAndOutput(values5, values1, values3, nil, nil, nil, values11, values7, values9, nil, nil, nil) end
end
end
end
end
end
end
end
elseif (elements4 == ELEMENT_DASH or elements4 == ELEMENT_DATESEPARATOR) then -- 'May 3 - '
if (elements5 == ELEMENT_MONTHWORD) then -- 'May 3 - June'
if (elements6 == ELEMENT_WHITESPACE) then -- 'May 3 - June '
if (elements7 == ELEMENT_ONETWODIGITS or elements3 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- 'May 3 - June 12' or 'May 3rd - June 12th'
if (numberOfElements == 7) then return checkAndOutput(nil, values1, values3, nil, nil, nil, nil, values5, values7, nil, nil, nil) end
if (elements8 == ELEMENT_COMMA or elements8 == ELEMENT_WHITESPACE) then -- 'May 3 - June 12, '
if (elements9 == ELEMENT_FOURDIGITS) then -- 'May 3 - June 12, 2014'
if (numberOfElements == 9) then return checkAndOutput(values9, values1, values3, nil, nil, nil, values9, values5, values7, nil, nil, nil) end
end
end
end
end
elseif (elements5 == ELEMENT_ONETWODIGITS or elements3 == ELEMENT_ONETWODIGITSWITHORDINAL) then -- 'May 3 - 12' or 'May 3rd - 12th'
if (numberOfElements == 5) then return checkAndOutput(nil, values1, values3, nil, nil, nil, nil, values1, values5, nil, nil, nil) end
if (elements6 == ELEMENT_COMMA or elements6 == ELEMENT_WHITESPACE) then -- 'May 3 - 12, '
if (elements7 == ELEMENT_FOURDIGITS) then -- 'May 3 - 12, 2013'
if (numberOfElements == 7) then return checkAndOutput(values7, values1, values3, nil, nil, nil, values7, values1, values5, nil, nil, nil) end
end
end
end
end
elseif (elements3 == ELEMENT_FOURDIGITS) then -- 'May 2013'
if (numberOfElements == 3) then return checkAndOutput(values3, values1, nil, nil, nil, nil) end
if (elements4 == ELEMENT_DASH or elements4 == ELEMENT_DATESEPARATOR) then -- 'May 2013 -'
if (elements5 == ELEMENT_MONTHWORD) then -- 'May 2013 - June'
if (elements6 == ELEMENT_WHITESPACE) then -- 'May 2013 - June '
if (elements7 == ELEMENT_FOURDIGITS) then -- 'May 2013 - June 2013'
if (numberOfElements == 7) then return checkAndOutput(values3, values1, nil, nil, nil, nil, values7, values5, nil, nil, nil, nil) end
end
end
end
end
end
elseif (elements2 == ELEMENT_DASH or elements2 == ELEMENT_DATESEPARATOR) then -- 'May - '
if (elements3 == ELEMENT_MONTHWORD) then -- 'May - June'
if (elements4 == ELEMENT_WHITESPACE) then -- 'May - June '
if (elements5 == ELEMENT_FOURDIGITS) then -- 'May - June 2013'
if (numberOfElements == 5) then return checkAndOutput(values5, values1, nil, nil, nil, nil, values5, values3, nil, nil, nil, nil) end
end
end
end
elseif (elements2 == ELEMENT_COMMA) then -- 'May, '
if (elements3 == ELEMENT_FOURDIGITS) then -- 'May, 2012'
if (numberOfElements == 3) then return checkAndOutput(values3, values1, nil, nil, nil, nil) end
end
end
else
return PARSERESULT_UNRECOGNIZED -- the combination of elements was not a recognized one
end
end
function hasMetadataTemplates(input)
	
	-- This is a basic list of the template names for metadata emiting tempaltes, there are inr eality more templates and more redirects
	if (string.match(input, '%{%{Sstart %-?date')) then return true end
	if (string.match(input, '%{%{Eend %-?date')) then return true end
	if (string.match(input, '%{%{Bbirth %-?date')) then return true end
	if (string.match(input, '%{%{Ddeath %-?date')) then return true end
	if (string.match(input, '%{%{Bbirth %-?year')) then return true end
	if (string.match(input, '%{%{Ddeath %-?year')) then return true end
	if (string.match(input, '%{%{Ffilm ?date')) then return true end
	if (string.match(input, '%{%{ISO %-date')) then return true end
	return false
end
-- This function will return a raw string for generic checks and unit test, including defined parse errors
function main.parseDateOutputRaw(frame)
local input = frame.args1

-- If desired (default), unstrip and decode to have the raw markup
if (not frame.args.noUnstrip or frame.args.noUnstrip ~= 'yes') then
input = mw.text.decode(mw.text.unstrip(input))
end

-- If desired (not default), strip the field extra stuff
if (frame.args.stripExtras and frame.args.stripExtras == 'yes') then
input = stripFieldExtras(input)
end
-- If there is nothing but whitespace, don't bother
if (mw.ustring.match(input, '^%s*$')) then if (frame.args2 == 'pretty') then return frame:preprocess('\'\'Empty input\'\) else return 'Empty input' end end
local result, startDate, startTime, endDate, endTime = parseDateString(input)
if (result == PARSERESULT_FAIL) then if (frame.args2 == 'pretty') then return frame:preprocess('\'\'Failed parse\'\) else return 'Failed parse' end end
if (result == PARSERESULT_UNRECOGNIZED) then 
	local s
	if (hasMetadataTemplates(input)) then s = 'Has metadata template' else s = 'Unrecognized pattern' end
	if (frame.args2 == 'pretty') then return frame:preprocess('\'\..s..'\'\) else return s end 
	end
if (result == PARSERESULT_INVALID) then if (frame.args2 == 'pretty') then return frame:preprocess('\'\'Invalid date/time\'\) else return 'Invalid date/time' end end
if (result == PARSERESULT_INVALIDRANGE) then if (frame.args2 == 'pretty') then return frame:preprocess('\'\'Invalid date range\'\) else return 'Invalid date range' end end
if (result == PARSERESULT_INCOMPLETE) then if (frame.args2 == 'pretty') then return frame:preprocess('\'\'Incomplete date\'\) else return 'Incomplete date' end end
if (result == PARSERESULT_INCOMPLETERANGE) then if (frame.args2 == 'pretty') then return frame:preprocess('\'\'Incomplete date range\'\) else return 'Incomplete date range' end end
local s

if (startDate) then s = startDate end
if (startTime) then if (startDate) then s = s .. ' ' .. startTime else s = startTime end end
if (endDate) then s = s .. '; ' .. endDate end -- currently end date implies start date
-- currently no end time
return s
end
-- Strips whitespace from an input
function trim(value)
	local strip, count = string.gsub(value, '^%s*(.-)%s*$', '%1')
	return strip
end	
function stripFieldExtras(value)

-- todo: do progressive scan like with that seek string just for ref tags and such
-- note that we can't just replace matches with whitespace and catch them all, because it could be like '3 August2012'

local matchStrip = value:match('^(^*>^*>^*>^$') -- basic refs (quite common)
if (matchStrip) then return trim(matchStrip) end
matchStrip = value:match('^(^*>^*>^$') -- basic refs (quite common)
if (matchStrip) then return trim(matchStrip) end
matchStrip = value:match('^(^*>^$') -- basic refs (quite common)
if (matchStrip) then return trim(matchStrip) end

matchStrip = value:match('^(^*/>*/>*/>$') -- basic named ref (quite common)
if (matchStrip) then return trim(matchStrip) end

matchStrip = value:match('^(^*/>*/>$') -- basic named ref (quite common)
if (matchStrip) then return trim(matchStrip) end

matchStrip = value:match('^(^*/>$') -- basic named ref (quite common)
if (matchStrip) then return trim(matchStrip) end

matchStrip = value:match('^(^') end
if (result == PARSERESULT_UNRECOGNIZED and not hasMetadataTemplates(input)) then return frame:preprocess() end
--if (result == PARSERESULT_INVALID) then return frame:preprocess() end
--if (result == PARSERESULT_INVALIDRANGE) then return frame:preprocess() end
if (result == PARSERESULT_INCOMPLETE) then return frame:preprocess() end
if (result == PARSERESULT_INCOMPLETERANGE) then return frame:preprocess() end
-- we need to use frame:preprocess() for  or it just gets displayed
end
-- We are only doing the rest for a valid date
if (result ~= PARSERESULT_OKAY) then return nil end
local dtstartSpan, dtendSpan

-- If we have a start value and we're told to output it
if ((startDate or startTime) and frame.args.dtstart and frame.args.dtstart == 'yes') then 
if (startDate and startTime) then dtstartSpan =  .. startDate .. 'T' .. startTime .. 
elseif (startDate) then dtstartSpan =  .. startDate .. 
else dtstartSpan =  .. startTime ..  end
end
-- If we have an end value and we're told to output it
if ((endDate or endTime) and frame.args.dtend and frame.args.dtend == 'yes') then -- end values only happen when start values happen
if (endDate and endTime) then dtendSpan =  .. endDate .. 'T' .. endTime .. 
elseif (endDate) then dtendSpan =  .. endDate .. 
else dtendSpan =  .. endTime ..  end
end
local trackingCat = 
if (frame.args.trackingCat and frame.args.trackingCat == 'yes') then
trackingCat = 
end
if (dtstartSpan and dtendSpan) then return ' (' .. dtstartSpan .. ' - ' .. dtendSpan .. ')' .. trackingCat
elseif (dtstartSpan) then return ' (' .. dtstartSpan .. ')' .. trackingCat
elseif (dtendSpan) then return ' (' .. dtendSpan .. ')' .. trackingCat
else return nil end
end
function main.outputRawStripped(frame)
return stripFieldExtras(trim(mw.text.decode(mw.text.unstrip(frame.args1))))
end
function main.reoutputme(frame)
if (frame.args.preprocess and frame.args.preprocess == 'yes') then
return frame:preprocess(mw.text.decode(mw.text.unstrip(frame.args1))) .. 
else
return mw.text.decode(mw.text.unstrip(frame.args1)) .. 
end    

--local input = mw.text.decode(mw.text.unstrip(frame.args1))

s = 'Len=' .. #input .. ' '
for i = 1, string.len(input) do
s = s .. string.sub(input, i, i) .. ' '
end
return s
end
return main