reformat 'date' into new format specified by format_param if pattern_idx (the current format of 'date') can be
reformatted. Does the grunt work for reformat_dates.  
The table re_formats maps pattern_idx (current format) and format_param (desired format) to a table that holds:
format string used by string.format
identifier letters ('d', 'm', 'y', 'd2', 'm2', 'y2') that serve as indexes into a table t{} that holds captures
from mw.ustring.match for the various date parts specified by patterns[pattern_idx][1]  
Items in patterns{} have the general form:
['ymd'] = {'^(%d%d%d%d)%-(%d%d)%-(%d%d)$', 'y', 'm', 'd'}, where:
['ymd'] is pattern_idx
patterns['ymd'][1] is the match pattern with captures for mw.ustring.match
patterns['ymd'][2] is an indicator letter identifying the content of the first capture
patterns['ymd'][3] ... the second capture etc.  
when a pattern matches a date, the captures are loaded into table t{} in capture order using the idemtifier
characters as indexes into t{} For the above, a ymd date is in t{} as:
t.y = first capture (year), t.m = second capture (month), t.d = third capture (day)  
To reformat, this function is called with the pattern_idx that matches the current format of the date and with
format_param set to the desired format. This function loads table t{} as described and then calls string.format
with the format string specified by re_format[pattern_idx][format_param][1] using values taken from t{} according
to the capture identifier letters specified by patterns[pattern_idx][format_param][n] where n is 2..  
local re_formats = {
['ymd'] = { -- date format is ymd; reformat to:
['mdy'] = {'%s %s, %s', 'm', 'd', 'y'}, -- |df=mdy
['dmy'] = {'%s %s %s', 'd', 'm', 'y'}, -- |df=dmy
-- ['yMd'] = {'%s %s %s', 'y', 'm', 'd'}, -- |df=yMd; not supported at en.wiki
},
['Mdy'] = { -- date format is Mdy; reformat to:
['mdy'] = {'%s %s, %s', 'm', 'd', 'y'}, -- for long/short reformatting
['dmy'] = {'%s %s %s', 'd', 'm', 'y'}, -- |df=dmy
['ymd'] = {'%s-%s-%s', 'y', 'm', 'd'}, -- |df=ymd
-- ['yMd'] = {'%s %s %s', 'y', 'm', 'd'}, -- |df=yMd; not supported at en.wiki
},
['dMy'] = { -- date format is dMy; reformat to:
['dmy'] = {'%s %s %s', 'd', 'm', 'y'}, -- for long/short reformatting
['mdy'] = {'%s %s, %s', 'm', 'd', 'y'}, -- |df=mdy
['ymd'] = {'%s-%s-%s', 'y', 'm', 'd'}, -- |df=ymd
-- ['yMd'] = {'%s %s %s', 'y', 'm', 'd'}, -- |df=yMd; not supported at en.wiki
},
['Md-dy'] = { -- date format is Md-dy; reformat to:
['mdy'] = {'%s %s–%s, %s', 'm', 'd', 'd2', 'y'}, -- for long/short reformatting
['dmy'] = {'%s–%s %s %s', 'd', 'd2', 'm', 'y'}, -- |df=dmy -> d-dMy
},
['d-dMy'] = { -- date format is d-d>y; reformat to:
['dmy'] = {'%s–%s %s %s', 'd', 'd2', 'm', 'y'}, -- for long/short reformatting
['mdy'] = {'%s %s–%s, %s', 'm', 'd', 'd2', 'y'}, -- |df=mdy -> Md-dy
},
['dM-dMy'] = { -- date format is dM-dMy; reformat to:
['dmy'] = {'%s %s – %s %s %s', 'd', 'm', 'd2', 'm2', 'y'}, -- for long/short reformatting
['mdy'] = {'%s %s – %s %s, %s', 'm', 'd', 'm2', 'd2', 'y'}, -- |df=mdy -> Md-Mdy
},
['Md-Mdy'] = { -- date format is Md-Mdy; reformat to:
['mdy'] = {'%s %s – %s %s, %s', 'm', 'd', 'm2', 'd2', 'y'}, -- for long/short reformatting
['dmy'] = {'%s %s – %s %s %s', 'd', 'm', 'd2', 'm2', 'y'}, -- |df=dmy -> dM-dMy
},
['dMy-dMy'] = { -- date format is dMy-dMy; reformat to:
['dmy'] = {'%s %s %s – %s %s %s', 'd', 'm', 'y', 'd2', 'm2', 'y2'}, -- for long/short reformatting
['mdy'] = {'%s %s, %s – %s %s, %s', 'm', 'd', 'y', 'm2', 'd2', 'y2'}, -- |df=mdy -> Mdy-Mdy
},
['Mdy-Mdy'] = { -- date format is Mdy-Mdy; reformat to:
['mdy'] = {'%s %s, %s – %s %s, %s', 'm', 'd', 'y', 'm2', 'd2', 'y2'}, -- for long/short reformatting
['dmy'] = {'%s %s %s – %s %s %s', 'd', 'm', 'y', 'd2', 'm2', 'y2'}, -- |df=dmy -> dMy-dMy
},
['My-My'] = { -- these for long/short reformatting
['any'] = {'%s %s – %s %s', 'm', 'y', 'm2', 'y2'}, -- dmy/mdy agnostic
},
['M-My'] = { -- these for long/short reformatting
['any'] = {'%s–%s %s', 'm', 'm2', 'y'}, -- dmy/mdy agnostic
},
['My'] = { -- these for long/short reformatting
['any'] = {'%s %s', 'm', 'y'}, -- dmy/mdy agnostic
},
-- ['yMd'] = { -- not supported at en.wiki
-- ['mdy'] = {'%s %s, %s', 'm', 'd', 'y'}, -- |df=mdy
-- ['dmy'] = {'%s %s %s', 'd', 'm', 'y'}, -- |df=dmy
-- ['ymd'] = {'%s-%s-%s', 'y', 'm', 'd'}, -- |df=ymd
-- },
}  
local function reformatter (date, pattern_idx, format_param, mon_len)
if not in_array (pattern_idx, {'ymd', 'Mdy', 'Md-dy', 'dMy', 'yMd', 'd-dMy', 'dM-dMy', 'Md-Mdy', 'dMy-dMy', 'Mdy-Mdy', 'My-My', 'M-My', 'My'}) then
return; -- not in this set of date format patterns then not a reformattable date
end  
if 'ymd' == format_param and in_array (pattern_idx, {'ymd', 'Md-dy', 'd-dMy', 'dM-dMy', 'Md-Mdy', 'dMy-dMy', 'Mdy-Mdy', 'My-My', 'M-My', 'My'}) then
return; -- ymd date ranges not supported at en.wiki; no point in reformatting ymd to ymd
end  
if in_array (pattern_idx, {'My', 'M-My', 'My-My'}) then -- these are not dmy/mdy so can't be 'reformatted' into either
format_param = 'any'; -- so format-agnostic
end  
-- yMd is not supported at en.wiki; when yMd is supported at your wiki, uncomment the next line
-- if 'yMd' == format_param and in_array (pattern_idx, {'yMd', 'Md-dy', 'd-dMy', 'dM-dMy', 'Md-Mdy', 'dMy-dMy', 'Mdy-Mdy'}) then -- these formats not convertable; yMd not supported at en.wiki
if 'yMd' == format_param then -- yMd not supported at en.wiki; when yMd is supported at your wiki, remove or comment-out this line
return; -- not a reformattable date
end  
local c1, c2, c3, c4, c5, c6, c7; -- these hold the captures specified in patterns[pattern_idx][1]
c1, c2, c3, c4, c5, c6, c7 = mw.ustring.match (date, patterns[pattern_idx][1]); -- get the captures  
local t = { -- table that holds k/v pairs of date parts from the captures and patterns[pattern_idx][2..]
[patterns[pattern_idx][2]] = c1; -- at minimum there is always one capture with a matching indicator letter
[patterns[pattern_idx][3] or 'x'] = c2; -- patterns can have a variable number of captures; each capture requires an indicator letter;
[patterns[pattern_idx][4] or 'x'] = c3; -- where there is no capture, there is no indicator letter so n in patterns[pattern_idx][n] will be nil;
[patterns[pattern_idx][5] or 'x'] = c4; -- the 'x' here spoofs an indicator letter to prevent 'table index is nil' error
[patterns[pattern_idx][6] or 'x'] = c5;
[patterns[pattern_idx][7] or 'x'] = c6;
[patterns[pattern_idx][8] or 'x'] = c7;
};  
if t.a then -- if this date has an anchor year capture (all convertable date formats except ymd)
if t.y2 then -- for year range date formats
t.y2 = t.a; -- use the anchor year capture when reassembling the date
else -- here for single date formats (except ymd)
t.y = t.a; -- use the anchor year capture when reassembling the date
end
end  
if tonumber(t.m) then -- if raw month is a number (converting from ymd)
if 's' == mon_len then -- if we are to use abbreviated month names
t.m = cfg.date_names['inv_local_short'][tonumber(t.m)]; -- convert it to a month name
else
t.m = cfg.date_names['inv_local_long'][tonumber(t.m)]; -- convert it to a month name
end
t.d = t.d:gsub ('0(%d)', '%1'); -- strip leading '0' from day if present
elseif 'ymd' == format_param then -- when converting to ymd
t.y = t.y:gsub ('%a', ''); -- strip CITREF disambiguator if present; anchor year already known so process can proceed; TODO: maint message?
if 1582 > tonumber (t.y) then -- ymd format dates not allowed before 1582
return;
end
t.m = string.format ('%02d', get_month_number (t.m)); -- make sure that month and day are two digits
t.d = string.format ('%02d', t.d);
elseif mon_len then -- if mon_len is set to either 'short' or 'long'
for _, mon in ipairs ({'m', 'm2'}) do -- because there can be two month names, check both
if t[mon] then
t[mon] = get_month_number (t[mon]); -- get the month number for this month (is length agnostic)
if 0 == t[mon] then return; end -- seasons and named dates can't be converted