Module:Sandbox/dxa-kly/Dates

local indef = {"about", "approaching", "approximate", "around", "%f[%a]c%.%f[%A]", "%f[%a]ca%f[%A]", "circa", "close to", "doubt", "dubious", "estimate", "in the area of", "in the neighborhood of", "in the neighbourhood of", "in the neighborhood of", "in the region of", "more or less", "near", "or so", "order of", "roughly", "something like", "speculative", "tentative", "uncertain", "unclear", "unreliable", "unsettled", "unsure"} local months = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"} local fullmonths = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} local eras = {"ad", "bce", "bc", "ce"} local bad = "Invalid entry"

local p = {}

function p.day_try( d, m, y ) if m == 1 or m == 3 or m == 5 or m == 7 or m == 8 or m == 10 or m == 12 then d_in_m = 31 elseif m == 4 or m == 6 or m == 9 or m == 11 then d_in_m = 30 elseif m == 2 then if y % 4 < 1 and y % 400 > 0 then d_in_m = 29 else d_in_m = 28 end else d_in_m = 0 end if d > 0 and d <= d_in_m then return 1 end end

function p.era_try( str, ptn, idx ) for i = 1, #eras do   values = {str:match(ptn .. " (" .. eras[i] .. ")")}   for j = 1, #values do      if j == idx then if idx > 2 and idx < 5 then return values[idx] elseif idx == 2 then return values[1], values[2] end end end end end

function p.indef_try( str ) for i = 1, #indef do   if str:match(indef[i]) then return true end end return false end

function p.mth_str_to_int( m ) for i = 1, #months do   if months[i] == m then return i end end end

function p.slash_try( str ) sls_num1, sls_num2, sls_y = str:match("(%d+)/(%d+)/(%d+)") sls_num1 = tonumber(sls_num1) sls_num2 = tonumber(sls_num2) if sls_num1 then if sls_num2 > 12 then if not p.day_try(sls_num2, sls_num1, sls_y) then return bad end return "iso", sls_y, sls_num1, sls_num2 else if not p.day_try(sls_num1, sls_num2, sls_y) then return bad end return "iso", sls_y, sls_num2, sls_num1 end end end

function p.hyphenated_try( str ) hyn_y, hyn_m, hyn_d = str:match("(%d+)-(%d+)-(%d+)") hyn_y = tonumber(hyn_y) hyn_m = tonumber(hyn_m) hyn_d = tonumber(hyn_d) if hyn_y then if not p.day_try(hyn_d, hyn_m, hyn_y) then return bad end return "iso", hyn_y, hyn_m, hyn_d end end

function p.named_month_try( str ) for _, pattern in pairs( months ) do   dmy_d, dmy_m, dmy_y = str:match( "(%d+).-(" .. pattern .. ").-(%d+)" ) mdy_m, mdy_d, mdy_y = str:match( "(" .. pattern .. ").-(%d+)[^%d]+(%d+)") if dmy_d then dmy_d = tonumber(dmy_d) dmy_m = p.mth_str_to_int(dmy_m) dmy_y = tonumber(dmy_y) if not p.day_try(tonumber(dmy_d), dmy_m, tonumber(dmy_y)) then return bad end era = p.era_try(str, "(%d+).-(" .. pattern .. ").-(%d+)", 4) if era then return "dmy", dmy_y, dmy_m, dmy_d, era else return "dmy", dmy_y, dmy_m, dmy_d end elseif mdy_m then mdy_m = p.mth_str_to_int(mdy_m) mdy_d = tonumber(mdy_d) mdy_y = tonumber(mdy_y) if not p.day_try(tonumber(mdy_d), mdy_m, tonumber(mdy_y)) then return bad end era = p.era_try(str, "(" .. pattern .. ").-(%d+)[^%d]+(%d+)", 4) if era then return "mdy", mdy_y, mdy_m, mdy_d, era else return "mdy", mdy_y, mdy_m, mdy_d end end end fail1_d, fail1_m, fail1_y = str:match( "(%d+).-(%a+).-(%d+)" ) fail2_m, fail2_d, fail2_y = str:match( "(%a+).-(%d+)[^%d]+(%d+)") if fail1_d or fail2_m then return bad end end

function p.unclassified1_try( str, ptn ) un1_d, un1_m = str:match( "(%d+).-(" .. ptn .. ")" ) un1_d = tonumber(un1_d) un1_m = p.mth_str_to_int(un1_m) if un1_d and p.day_try(un1_d, un1_m, 1900) then return "dm", un1_d, fullmonths[un1_m] end end

function p.unclassified2_try( str, ptn ) un2_m, un2_d = str:match( "(" .. ptn .. ").-(%d+)" ) un2_d = tonumber(un2_d) un2_m = p.mth_str_to_int(un2_m) if un2_d then era = p.era_try(str, "(" .. ptn .. ").-(%d+)", 3) if era then return "my", fullmonths[un2_m], un2_d, era elseif p.day_try(un2_d, un2_m, 1900) then return "md", fullmonths[un2_m], un2_d else return "my", fullmonths[un2_m], un2_d end end end

function p.unclassified3_try( str ) un3_y, era = p.era_try(str, "(%d+)", 2) if era then return "ye", un3_y, era end end

function p.unclassified4_try( str ) t = {} for a in str:gmatch("%d+") do table.insert(t, tonumber(a)) end table.sort(t) if not (t[#t] == nil) then return "num", t[#t] end end

function p.parsedate( inp ) inp = inp or "" if not (type(inp) == "string") then return bad end inp = inp:lower has_indef = p.indef_try(inp) if p.slash_try(inp) then return has_indef, p.slash_try(inp) elseif p.hyphenated_try(inp) then return has_indef, p.hyphenated_try(inp) elseif p.named_month_try(inp) then return has_indef, p.named_month_try(inp) else for _, pattern in pairs( months ) do     if p.unclassified1_try( inp, pattern ) then return has_indef, p.unclassified1_try(inp, pattern) elseif p.unclassified2_try( inp, pattern ) then return has_indef, p.unclassified2_try(inp, pattern) end end if p.unclassified3_try(inp) then return has_indef, p.unclassified3_try(inp) elseif p.unclassified4_try(inp) then return has_indef, p.unclassified4_try(inp) end end return bad end

function p.unpackdate( frame ) s = {p.parsedate(frame.args.text)} format = frame.args.format uncertainty = "" era = "" if s[1] then uncertainty = "circa " end for i = 1, #s do   if s[i] == "Invalid entry" then return s[i] end end if s[2] == "iso" or s[2] == "dmy" or s[2] == "mdy" then year = s[3] month = s[4] day = s[5] if s[6] then era = s[6]:upper end if s[2] == "iso" then default = string.format("%04d-%02d-%02d", year, month, day) elseif s[2] == "dmy" then default = day .. " " .. fullmonths[month] .. " " .. year .. " " .. era else default = fullmonths[month] .. " " .. day .. ", " .. year .. " " .. era end elseif s[2] == "dm" or s[2] == "md" then default = s[3] .. " " .. s[4] elseif s[2] == "ye" then default = s[3] .. " " .. s[4]:upper elseif s[2] == "my" then default = s[3] .. " " .. s[4] if s[5] then default = default .. " " .. s[5]:upper end else default = s[3] end if format then if s[2] == "iso" or s[2] == "dmy" or s[2] == "mdy" then if format == s[2] then return uncertainty .. default end if format == "iso" then return uncertainty .. string.format("%04d-%02d-%02d", year, month, day) end if format == "dmy" then return uncertainty .. day .. " " .. fullmonths[month] .. " " .. year .. " " .. era end if format == "mdy" then return uncertainty .. fullmonths[month] .. " " .. day .. ", " .. year .. " " .. era end if format == "year" then return uncertainty .. year .. " " .. era end end return "Cannot apply format" else return uncertainty .. default end end

return p