Module:Sandbox/Harry noob/Dates

--Harry noob Google Code-in 2019, Lua Task 9

--[[ date format M= word month m= number month

d/m/y y/m/d m/d/y d M y M d y y M M y y ]]--

-- TODO: Q. how to know it is invalid day or it just doesnt match the current pattern? A. save when it matches some pattern --

local p = {} local log = mw.log

function p.canonicalDate(frame) local fmt = frame.args.format local text = frame.args.text local date, defaultFmt = p.parseDate(text) if not date then return "Invalid entry" end local formattedDate = p.formatDate(date, fmt, defaultFmt) local result = addPreSuffix(text, formattedDate) return result end

function p.parseDate(text) local d, m, y, a, b, c, mName local mayMatch log("try to match y/m/d or d/m/y or m/d/y") a, b, c = string.match(text, "(%d+)[/%- ](%d+)[/%- ](%d+)") -- y/m/d or d/m/y or m/d/y log(a, b, c)	mayMatch = a and b and c	local possibleArrangement = {{a, b, c}, {c, b, a}, {c, a, b}} mw.logObject(possibleArrangement, "possibleArrangement: ") for _, arrange in ipairs(possibleArrangement) do		y, m, d = unpack(arrange) -- this is not python :(, need to add unpack, destructing assignment doesnt works with table		if isValidNumDate(y, m, d) then			log("isValidNumDate passed 1")			return mapToNum({y, m, d}), "iso"		end	end	if mayMatch then		return nil	end	local monthShortNameToNum = {jan = 1, feb = 2, mar = 3, apr = 4,		jun = 6, jul = 7, aug = 8, sept = 9,		oct = 10, nov = 11, dec = 12}	local monthLongNameToNum = {january = 1, february = 2, march = 3,		april = 4, may = 5, june = 6, july = 7, august = 8, september = 9,		october = 10, november = 11, december = 12}	-- match long name first, and then short name	local monthFound = false	local start, stop	for monthName, monthNum in pairs(monthLongNameToNum) do		start, stop = text:lower:find(monthName)		log("month for loop 1:", monthName, monthNum, start, stop)		if start and stop then			monthFound = true mName = text:sub(start, stop) -- ~= monthName, == orginal month name log("mName:", mName) m = monthNum break end end if not monthFound then for monthName, monthNum in pairs(monthShortNameToNum) do			if monthName == "sept" then start, stop = text:lower:find("sept?") else start, stop = text:lower:find(monthName) end if start and stop then monthFound = true mName = text:sub(start, stop) -- ~= monthName, == orginal month name log("mName:", mName) m = monthNum break end end end if monthFound then log("try to match d M y or y M d") d, y = text:match("(%d+)%D-" .. mName .. "%D-(%d+)") -- d M y		if isValidNumDate(y, m, d) then log("isValidNumDate passed 2") return mapToNum({y, m, d}), "dmy" end if d and y then return nil end log("try to match M d y") d, y = text:match(mName .. "%D-(%d+)%D-(%d+)") -- M d y		if isValidNumDate(y, m, d) then log("isValidNumDate passed 4") return mapToNum({y, m, d}), "mdy" end if d and y then return nil end log("try to match M y") y = text:match(mName .. "%D-(%d+)") -- M y		log("y =", y)		if y then return mapToNum({y, m}), "my" end log("try to match y M") y = text:match("(%d+)%D-" .. mName) -- y M		log("y =", y)		if y then return mapToNum({y, m}), "ym" end end log("try to match y") y = text:match("(%d+)%D-$") -- y	if y then return {tonumber(y)}, "year" end log("no match") return nil end

-- y, m, d are string number function isValidNumDate(y, m, d)	log("isValidNumDate receive:", y, m, d)	y, m, d = tonumber(y), tonumber(m), tonumber(d) local NOfDays = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} local result = y and m and d and 1 <= m and m <= 12 and (		isLeap(y) and m == 2 and 1 <= d and      -- if is leap year and February		(d <= 29) or                        -- then test if it <= 29		(d <= NOfDays[m]))                  -- else follow general case (use assert and pcall instead of returning 2 result can type less code) log("isValidNumDate result:" .. tostring(result)) return result end

function isLeap(y) return y%4 == 0 and (y%100 ~= 0 or y%400 == 0) end

function mapToNum(arr) mw.logObject(arr, "mapToNum receive arr:") for i, v in pairs(arr) do		arr[i] = tonumber(v) end mw.logObject(arr, "mapToNum result arr:") return arr end

function p.formatDate(date, fmt, defaultFmt) local monthNames = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} local y, m, d = unpack(date) if (fmt or defaultFmt) == "iso" then return ("%d-%02d-%02d"):format(y, m, d)	elseif (fmt or defaultFmt) == "dmy" then return ("%d %s %d"):format(d, monthNames[m], y)	elseif (fmt or defaultFmt) == "mdy" then return ("%s %d, %d"):format(monthNames[m], d, y)	elseif (fmt or defaultFmt) == "my" then return ("%s %d"):format(monthNames[m], y)	elseif (fmt or defaultFmt) == "ym" then return ("%d %s"):format(y, monthNames[m]) else return tostring(y) end end

function addPreSuffix(text, dateStr) local aboutList = {"about", "around", "uncertain", "roughly", "approximate", "close to", "near "} local suffixList = {"BCE", "CE", "BC", "AD"} for i, word in ipairs(aboutList) do		if text:lower:find(word) then dateStr = "circa " .. dateStr break end end for i, word in ipairs(suffixList) do if text:find(" " .. word) then dateStr = dateStr .. " " .. word break end end return dateStr end

return p