Documentation for this module may be created at Mòideal:Teamplaid:ConvertDate/doc

-- Converts English dates to ISO dates.
-- If the input is a single month name (e.g. "January"), returns the number for the month as a 2-digit string
-- The following formats have been tested:
--    April
--    April 2016
--    April 1, 2016
--    1 April 2016
--    1st April 2016
--    2nd April 2016
--    ...
--    5.4.2006

local p = {}

-- Turns 1 digit number strings into 2 digit number strings with leading zero
local function format_number(String)
	local Number = tonumber(String)
	if (Number < 10) then
		return "0" .. Number
	else
		return String
	end
end

-- Seems like here is no built-in is_number function
local function is_number(String)
	for c in String:gmatch"." do
		if (c~="0" and c~="1" and c~="2" and c~="3" and c~="4" and c~="5" and c~="6" and c~="7" and c~="8" and c~="9") then
		    return false
		end
	end
	return true
end

-- Returns whether a number is too big to be a day
local function is_year(String)
	return is_number(String) and tonumber(String) > 31
end

-- Returns whether a number is too big to be a month
local function not_month_number(String)
	return is_number(String) and tonumber(String) > 12
end

-- Returns whether a number can be a be a day
local function in_day_range(String)
	return is_number(String) and tonumber(String) >= 1 and tonumber(String) <= 31
end

-- Returns whether a number can be a be a month
local function in_month_range(String)
	return is_number(String) and tonumber(String) >= 1 and tonumber(String) <= 12
end

-- Checks for suffixes like "st", "nd", "," ...
-- Reurns nil if this isn't a number followed by one of those suffixes.
-- Otherwise, strips away the suffix and returns the number as a 2-digit string
local function day_from_day_with_suffix(String)
	local Suffix = string.sub(String, -1)
	if (Suffix == ",") then
		local DayString = string.sub(String, 1, string.len(String)-1)
		if (in_day_range(DayString)) then
			return DayString
		end
	end
	Suffix = string.sub(String, -2)
	if (Suffix == "st" or Suffix == "nd" or Suffix == "rd" or Suffix == "th") then
		local DayString = string.sub(String, 1, string.len(String)-2)
		if (in_day_range(DayString)) then
			return DayString
		end
	end
	return nil
end

-- Checks if the inpur has been successfully parsed
local function check_result(Date, Year, Month, Day)
	if (Date[2] == nil) then
		return tonumber(Year) > 0
	elseif (Date[3] == nil) then
		return tonumber(Year) > 0 and in_month_range(Month)
	else
		return tonumber(Year) > 0 and in_month_range(Month) and in_day_range(Day)
	end
end

local function Run(Args)
	-- Check for number of arguments
	if (Args[1] ==nil) then
		return '<span class="error">[[Teamplaid:ConvertDate]]: Feumaidh tu 1 argamaid a shònrachadh ach cha robh gin ann!</span>'
	end
	if (Args[2] ~=nil) then
		return '<span class="error">[[Teamplaid:ConvertDate]]: Na sònraich barrachd air 1 argamaid!</span>'
	end
	local Arg = mw.ustring.lower(mw.text.trim(Args[1] or ""));
	if (Arg == "") then
		return '<span class="error">[[Teamplaid:ConvertDate]]: Tha an argamaid falamh!</span>'
	end
	
	-- Output variables
	local Year = "0000"
	local Month = "00"
	local Day = "00"
	-- Track already parsed elements
	local YearIndex = 0
	local MonthIndex = 0
	local DayIndex = 0
	
	-- Months table
	local Months = {}
	Months["january"] = "01"
	Months["jan"] = "01"
	Months["jan."] = "01"
	Months["february"] = "02"
	Months["feb"] = "02"
	Months["feb."] = "02"
	Months["march"] = "03"
	Months["mar"] = "03"
	Months["mar."] = "03"
	Months["april"] = "04"
	Months["apr"] = "04"
	Months["apr."] = "04"
	Months["may"] = "05"
	Months["june"] = "06"
	Months["jun"] = "06"
	Months["jun."] = "06"
	Months["july"] = "07"
	Months["jul"] = "07"
	Months["jul."] = "07"
	Months["august"] = "08"
	Months["aug"] = "08"
	Months["aug."] = "08"
	Months["september"] = "09"
	Months["sep"] = "09"
	Months["sep."] = "09"
	Months["october"] = "10"
	Months["oct"] = "10"
	Months["oct."] = "10"
	Months["november"] = "11"
	Months["nov"] = "11"
	Months["nov."] = "11"
	Months["december"] = "12"
	Months["dec"] = "12"
	Months["dec."] = "12"
	
	-- If we only have a month, we're done.
	if (Months[Arg] ~= nil) then
		return Months[Arg]
	end
	
	-- Tokenize the string
	local Date={}
	for i in string.gmatch(Arg, "%S+") do
  		table.insert(Date, i)
	end

	-- Maybe something other than whitespace has been used as a delimiter?
	if (Date[2] == nil) then
		Date = {}
		for i in string.gmatch(Arg, "[^.]+") do
  			table.insert(Date, mw.text.trim(i))
  		end
  		if (Date[1]~=nil and Date[2]~=nil and Date[3]~=nil) then
			-- Output
			if (not check_result(Date, Date[3], Date[2], Date[1])) then
				return Args[1] -- Conversion failed
			else
				return Date[3] .. "-" .. format_number(Date[2]) .. "-" .. format_number(Date[1])
			end
  		end
	end

	-- Detect string elements as day, month or year
	local Unparsed={}
	for i, v in ipairs(Date) do
		local TempDay = day_from_day_with_suffix(v)
		if (TempDay ~= nil) then
			Day = TempDay
			DayIndex = i
		elseif (Months[v] ~= nil) then
			Month = Months[v]
			MonthIndex = i
		elseif (is_year(v)) then
			Year = v
			YearIndex = i
		elseif (not_month_number(v)) then
			Day = v
			DayIndex = i
		else
			table.insert(Unparsed, v)
		end
	end

	-- Pick up the stragglers
	for i, v in ipairs(Unparsed) do
		if (is_number(v)) then
			if (MonthIndex > 0 and YearIndex > 0) then
				Day = v
				DayIndex = i
			elseif (DayIndex > 0 and YearIndex > 0) then
				Month = v
				MonthIndex = i
			elseif (DayIndex > 0 and MonthIndex > 0) then
				Year = v
				YearIndex = i
			end
		end
	end

	-- Output
	if (not check_result(Date, Year, Month, Day)) then
		return Args[1] -- Conversion failed
	else
		return Year .. "-" .. format_number(Month) .. "-" .. format_number(Day)
	end
end

function p.Execute(frame)
	return Run(frame:getParent().args)
end

return p