<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://blackwiki.org/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=103.7.131.178&amp;*</id>
	<title>blackwiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://blackwiki.org/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=103.7.131.178&amp;*"/>
	<link rel="alternate" type="text/html" href="https://blackwiki.org/index.php?title=Special:Contributions/103.7.131.178"/>
	<updated>2026-06-23T15:58:48Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.34.2</generator>
	<entry>
		<id>https://blackwiki.org/index.php?title=Module:Citation/CS1/Date_validation/sandbox&amp;diff=122380</id>
		<title>Module:Citation/CS1/Date validation/sandbox</title>
		<link rel="alternate" type="text/html" href="https://blackwiki.org/index.php?title=Module:Citation/CS1/Date_validation/sandbox&amp;diff=122380"/>
		<updated>2019-10-04T11:20:02Z</updated>

		<summary type="html">&lt;p&gt;103.7.131.178: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;--[[&lt;br /&gt;
History of changes since last sync: 2019-09-03&lt;br /&gt;
&lt;br /&gt;
2019-09-11: i18n fix for |year= ee Help_talk:Citation_Style_1#Support_year-suffix_outside_the_English_alphabet&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; F O R W A R D   D E C L A R A T I O N S &amp;gt;--------------------------------------&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local is_set, in_array;															-- imported functions from selected Module:Citation/CS1/Utilities&lt;br /&gt;
local cfg;																		-- table of tables imported from selected Module:Citation/CS1/Configuration&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[=[-------------------------&amp;lt; I S _ V A L I D _ A C C E S S D A T E &amp;gt;----------------------------------------&lt;br /&gt;
&lt;br /&gt;
returns true if:&lt;br /&gt;
	Wikipedia start date &amp;lt;= accessdate &amp;lt; today + 2 days&lt;br /&gt;
&lt;br /&gt;
Wikipedia start date is 2001-01-15T00:00:00 UTC which is 979516800 seconds after 1970-01-01T00:00:00 UTC (the start of Unix time)&lt;br /&gt;
accessdate is the date provided in |accessdate= at time 00:00:00 UTC&lt;br /&gt;
today is the current date at time 00:00:00 UTC plus 48 hours&lt;br /&gt;
	if today is 2015-01-01T00:00:00 then&lt;br /&gt;
		adding 24 hours gives 2015-01-02T00:00:00 – one second more than today&lt;br /&gt;
		adding 24 hours gives 2015-01-03T00:00:00 – one second more than tomorrow&lt;br /&gt;
&lt;br /&gt;
This function does not work if it is fed month names for languages other than English.  Wikimedia #time: parser&lt;br /&gt;
apparently doesn't understand non-Engish date month names. This function will always return false when the date&lt;br /&gt;
contains a non-English month name because good1 is false after the call to lang.formatDate().  To get around that&lt;br /&gt;
call this function with YYYY-MM-DD format dates.&lt;br /&gt;
&lt;br /&gt;
]=]&lt;br /&gt;
&lt;br /&gt;
local function is_valid_accessdate (accessdate)&lt;br /&gt;
	local lang = mw.getContentLanguage();&lt;br /&gt;
	local good1, good2;&lt;br /&gt;
	local access_ts, tomorrow_ts;												-- to hold unix time stamps representing the dates&lt;br /&gt;
		&lt;br /&gt;
	good1, access_ts = pcall( lang.formatDate, lang, 'U', accessdate );			-- convert accessdate value to unix timesatmp &lt;br /&gt;
	good2, tomorrow_ts = pcall( lang.formatDate, lang, 'U', 'today + 2 days' );	-- today midnight + 2 days is one second more than all day tomorrow&lt;br /&gt;
	&lt;br /&gt;
	if good1 and good2 then														-- lang.formatDate() returns a timestamp in the local script which which tonumber() may not understand&lt;br /&gt;
		access_ts = tonumber (access_ts) or lang:parseFormattedNumber (access_ts);			-- convert to numbers for the comparison;&lt;br /&gt;
		tomorrow_ts = tonumber (tomorrow_ts) or lang:parseFormattedNumber (tomorrow_ts);&lt;br /&gt;
	else&lt;br /&gt;
		return false;															-- one or both failed to convert to unix time stamp&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if 979516800 &amp;lt;= access_ts and access_ts &amp;lt; tomorrow_ts then					-- Wikipedia start date &amp;lt;= accessdate &amp;lt; tomorrow's date&lt;br /&gt;
		return true;&lt;br /&gt;
	else&lt;br /&gt;
		return false;															-- accessdate out of range&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; G E T _ M O N T H _ N U M B E R &amp;gt;----------------------------------------------&lt;br /&gt;
&lt;br /&gt;
returns a number according to the month in a date: 1 for January, etc.  Capitalization and spelling must be correct. If not a valid month, returns 0&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function get_month_number (month)&lt;br /&gt;
	return cfg.date_names['local'].long[month] or cfg.date_names['local'].short[month] or		-- look for local names first&lt;br /&gt;
			cfg.date_names['en'].long[month] or	cfg.date_names['en'].short[month] or			-- failing that, look for English names&lt;br /&gt;
			0;																					-- not a recognized month name&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ V A L I D _ E M B A R G O _ D A T E &amp;gt;------------------------------------&lt;br /&gt;
&lt;br /&gt;
returns true and date value if that value has proper dmy, mdy, ymd format.&lt;br /&gt;
&lt;br /&gt;
returns false and 9999 (embargoed forever) when date value is not proper format; assumes that when |embargo= is&lt;br /&gt;
set, the editor intended to embargo a pmc but |embargo= does not hold a single date.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_valid_embargo_date (v)&lt;br /&gt;
	if v:match ('^%d%d%d%d%-%d%d%-%d%d$') or									-- ymd&lt;br /&gt;
		v:match ('^%d%d?%s+%a+%s+%d%d%d%d$') or									-- dmy&lt;br /&gt;
		v:match ('^%a+%s+%d%d?%s*,%s*%d%d%d%d$') then							-- mdy&lt;br /&gt;
			return true, v;&lt;br /&gt;
	end&lt;br /&gt;
	return false, '9999';														-- if here not good date so return false and set embargo date to long time in future&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; G E T _ S E A S O N _ N U M B E R &amp;gt;--------------------------------------------&lt;br /&gt;
&lt;br /&gt;
returns a number according to the sequence of seasons in a year: 1 for Winter, etc.  Capitalization and spelling must be correct. If not a valid season, returns 0&lt;br /&gt;
&lt;br /&gt;
Uses ISO DIS 8601 2016 part 2 §4.7  Divisions of a year for hemishpere-independent seasons:&lt;br /&gt;
	21-24 = Spring, Summer, Autumn, Winter, independent of “Hemisphere”&lt;br /&gt;
&lt;br /&gt;
These additional divisions not currently supported:&lt;br /&gt;
	25-28 = Spring - Northern Hemisphere, Summer- Northern Hemisphere, Autumn - Northern Hemisphere, Winter - Northern Hemisphere&lt;br /&gt;
	29-32 = Spring – Southern Hemisphere, Summer– Southern Hemisphere, Autumn – Southern Hemisphere, Winter - Southern Hemisphere&lt;br /&gt;
	33-36 = Quarter 1, Quarter 2, Quarter 3, Quarter 4 (3 months each)&lt;br /&gt;
	37-39 = Quadrimester 1, Quadrimester 2, Quadrimester 3 (4 months each)&lt;br /&gt;
	40-41 = Semestral 1, Semestral-2 (6 months each)&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function get_season_number (season)&lt;br /&gt;
	return cfg.date_names['local'].season[season] or							-- look for local names first&lt;br /&gt;
			cfg.date_names['en'].season[season] or								-- failing that, look for English names&lt;br /&gt;
			0;																	-- not a recognized season name&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ P R O P E R _ N A M E &amp;gt;--------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
returns a non-zero number if date contains a recognized proper name.  Capitalization and spelling must be correct.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_proper_name (name)&lt;br /&gt;
	return cfg.date_names['local'].named[name] or								-- look for local names dates first&lt;br /&gt;
			cfg.date_names['en'].named[name] or									-- failing that, look for English names&lt;br /&gt;
			0;																	-- not a recognized named date&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ V A L I D _ M O N T H _ O R _ S E A S O N &amp;gt;------------------------------&lt;br /&gt;
&lt;br /&gt;
--returns true if month or season is valid (properly spelled, capitalized, abbreviated)&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_valid_month_or_season (month_season)&lt;br /&gt;
	if 0 == get_month_number (month_season) then								-- if month text isn't one of the twelve months, might be a season&lt;br /&gt;
		if 0 == get_season_number (month_season) then							-- not a month, is it a season?&lt;br /&gt;
			return false;														-- return false not a month or one of the five seasons&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return true;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ V A L I D _ Y E A R &amp;gt;----------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Function gets current year from the server and compares it to year from a citation parameter.  Years more than one year in the future are not acceptable.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
local year_limit;&lt;br /&gt;
local function is_valid_year(year)&lt;br /&gt;
	if not is_set(year_limit) then&lt;br /&gt;
		year_limit = tonumber(os.date(&amp;quot;%Y&amp;quot;))+1;									-- global variable so we only have to fetch it once&lt;br /&gt;
	end&lt;br /&gt;
	return tonumber(year) &amp;lt;= year_limit;										-- false if year is in the future more than one year&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ V A L I D _ D A T E &amp;gt;----------------------------------------------------&lt;br /&gt;
Returns true if day is less than or equal to the number of days in month and year is no farther into the future&lt;br /&gt;
than next year; else returns false.&lt;br /&gt;
&lt;br /&gt;
Assumes Julian calendar prior to year 1582 and Gregorian calendar thereafter. Accounts for Julian calendar leap&lt;br /&gt;
years before 1582 and Gregorian leap years after 1582. Where the two calendars overlap (1582 to approximately&lt;br /&gt;
1923) dates are assumed to be Gregorian.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_valid_date (year, month, day)&lt;br /&gt;
local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};&lt;br /&gt;
local month_length;&lt;br /&gt;
	if not is_valid_year(year) then												-- no farther into the future than next year&lt;br /&gt;
		return false;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	month = tonumber(month);													-- required for YYYY-MM-DD dates&lt;br /&gt;
	&lt;br /&gt;
	if (2==month) then															-- if February&lt;br /&gt;
		month_length = 28;														-- then 28 days unless&lt;br /&gt;
		if 1582 &amp;gt; tonumber(year) then											-- Julian calendar&lt;br /&gt;
			if 0==(year%4) then													-- is a leap year?&lt;br /&gt;
				month_length = 29;												-- if leap year then 29 days in February&lt;br /&gt;
			end&lt;br /&gt;
		else																	-- Gregorian calendar&lt;br /&gt;
			if (0==(year%4) and (0~=(year%100) or 0==(year%400))) then			-- is a leap year?&lt;br /&gt;
				month_length = 29;												-- if leap year then 29 days in February&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		month_length=days_in_month[month];&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if tonumber (day) &amp;gt; month_length then&lt;br /&gt;
		return false;&lt;br /&gt;
	end&lt;br /&gt;
	return true;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ V A L I D _ M O N T H _ R A N G E _ S T Y L E &amp;gt;--------------------------&lt;br /&gt;
&lt;br /&gt;
Months in a range are expected to have the same style: Jan–Mar or October–December but not February–Mar or Jul–August. &lt;br /&gt;
There is a special test for May because it can be either short or long form.&lt;br /&gt;
&lt;br /&gt;
Returns true when style for both months is the same&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_valid_month_range_style (month1, month2)&lt;br /&gt;
local len1 = month1:len();&lt;br /&gt;
local len2 = month2:len();&lt;br /&gt;
	if len1 == len2 then&lt;br /&gt;
		return true;															-- both months are short form so return true&lt;br /&gt;
	elseif 'May' == month1 or 'May'== month2 then&lt;br /&gt;
		return true;															-- both months are long form so return true&lt;br /&gt;
	elseif 3 == len1 or 3 == len2 then&lt;br /&gt;
		return false;															-- months are mixed form so return false&lt;br /&gt;
	else&lt;br /&gt;
		return true;															-- both months are long form so return true&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ V A L I D _ M O N T H _ S E A S O N _ R A N G E &amp;gt;------------------------&lt;br /&gt;
&lt;br /&gt;
Check a pair of months or seasons to see if both are valid members of a month or season pair.&lt;br /&gt;
&lt;br /&gt;
Month pairs are expected to be left to right, earliest to latest in time.&lt;br /&gt;
&lt;br /&gt;
All season ranges are accepted as valid because there are publishers out there who have published a Summer–Spring YYYY issue so ... ok&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_valid_month_season_range(range_start, range_end)&lt;br /&gt;
	local range_start_number = get_month_number (range_start);&lt;br /&gt;
	local range_end_number;&lt;br /&gt;
	&lt;br /&gt;
	if 0 == range_start_number then												-- is this a month range?&lt;br /&gt;
		range_start_number = get_season_number (range_start);					-- not a month; is it a season? get start season number&lt;br /&gt;
		range_end_number = get_season_number (range_end);						-- get end season number&lt;br /&gt;
&lt;br /&gt;
		if (0 ~= range_start_number) and (0 ~= range_end_number) then&lt;br /&gt;
			return true;														-- any season pairing is accepted&lt;br /&gt;
		end&lt;br /&gt;
		return false;															-- range_start and/or range_end is not a season&lt;br /&gt;
	end&lt;br /&gt;
																				-- here when range_start is a month&lt;br /&gt;
	range_end_number = get_month_number (range_end);							-- get end month number&lt;br /&gt;
	if range_start_number &amp;lt; range_end_number then								-- range_start is a month; does range_start precede range_end?&lt;br /&gt;
		if is_valid_month_range_style (range_start, range_end) then				-- do months have the same style?&lt;br /&gt;
			return true;														-- proper order and same style&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false;																-- range_start month number is greater than or equal to range end number; or range end isn't a month&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; M A K E _ C O I N S _ D A T E &amp;gt;------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
This function receives a table of date parts for one or two dates and an empty table reference declared in&lt;br /&gt;
Module:Citation/CS1.  The function is called only for |date= parameters and only if the |date=&amp;lt;value&amp;gt; is &lt;br /&gt;
determined to be a valid date format.  The question of what to do with invalid date formats is not answered here.&lt;br /&gt;
&lt;br /&gt;
The date parts in the input table are converted to an ISO 8601 conforming date string:&lt;br /&gt;
	single whole dates:		yyyy-mm-dd&lt;br /&gt;
	month and year dates:	yyyy-mm&lt;br /&gt;
	year dates:				yyyy&lt;br /&gt;
	ranges:					yyyy-mm-dd/yyyy-mm-dd&lt;br /&gt;
							yyyy-mm/yyyy-mm&lt;br /&gt;
							yyyy/yyyy&lt;br /&gt;
&lt;br /&gt;
Dates in the Julian calendar are reduced to year or year/year so that we don't have to do calendar conversion from&lt;br /&gt;
Julian to Proleptic Gregorian.&lt;br /&gt;
&lt;br /&gt;
The input table has:&lt;br /&gt;
	year, year2 – always present; if before 1582, ignore months and days if present&lt;br /&gt;
	month, month2 – 0 if not provided, 1-12 for months, 21-24 for seasons; 99 Christmas&lt;br /&gt;
	day, day2 –  0 if not provided, 1-31 for days&lt;br /&gt;
	&lt;br /&gt;
the output table receives:&lt;br /&gt;
	rftdate:	an IS8601 formatted date&lt;br /&gt;
	rftchron:	a free-form version of the date, usually without year which is in rftdate (season ranges and propername dates)&lt;br /&gt;
	rftssn:		one of four season keywords: winter, spring, summer, fall (lowercase)&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function make_COinS_date (input, tCOinS_date)&lt;br /&gt;
	local date;																	-- one date or first date in a range&lt;br /&gt;
	local date2 = '';															-- end of range date&lt;br /&gt;
-- start temporary Julian / Gregorian calendar uncertainty detection&lt;br /&gt;
	local year = tonumber(input.year);											-- this temporary code to determine the extent of sources dated to the Julian/Gregorian&lt;br /&gt;
	local month = tonumber(input.month);										-- interstice 1 October 1582 – 1 January 1926&lt;br /&gt;
	local day = tonumber (input.day);&lt;br /&gt;
	if (0 ~= day) and															-- day must have a value for this to be a whole date&lt;br /&gt;
		(((1582 == year) and (10 &amp;lt;= month) and (12 &amp;gt;= month)) or				-- any whole 1582 date from 1 october to 31 December or&lt;br /&gt;
			((1926 == year) and (1 == month) and (1 == input.day)) or			-- 1 January 1926 or&lt;br /&gt;
				((1582 &amp;lt; year) and (1925 &amp;gt;= year))) then						-- any date 1 January 1583 – 31 December 1925&lt;br /&gt;
					tCOinS_date.inter_cal_cat = true;							-- set category flag true&lt;br /&gt;
	end&lt;br /&gt;
-- end temporary Julian / Gergorian calendar uncertainty detection&lt;br /&gt;
	&lt;br /&gt;
	if 1582 &amp;gt; tonumber(input.year) or 20 &amp;lt; tonumber(input.month) then			-- Julian calendar or season so &amp;amp;rft.date gets year only&lt;br /&gt;
		date = input.year;&lt;br /&gt;
		if 0 ~= input.year2 and input.year ~= input.year2 then					-- if a range, only the second year portion when not the same as range start year&lt;br /&gt;
			date = string.format ('%.4d/%.4d', tonumber(input.year), tonumber(input.year2))		-- assemble the date range&lt;br /&gt;
		end&lt;br /&gt;
		if 20 &amp;lt; tonumber(input.month) then										-- if season or propername date&lt;br /&gt;
			local season = {[24]='winter', [21]='spring', [22]='summer', [23]='fall', [99]='Christmas'};	-- seasons lowercase, no autumn; proper names use title case&lt;br /&gt;
			if 0 == input.month2 then											-- single season date&lt;br /&gt;
				if 30 &amp;lt;tonumber(input.month) then&lt;br /&gt;
					tCOinS_date.rftchron = season[input.month];					-- proper name dates&lt;br /&gt;
				else&lt;br /&gt;
					tCOinS_date.rftssn = season[input.month];					-- seasons&lt;br /&gt;
				end&lt;br /&gt;
			else																-- season range with a second season specified&lt;br /&gt;
				if input.year ~= input.year2 then								-- season year – season year range or season year–year&lt;br /&gt;
					tCOinS_date.rftssn = season[input.month];					-- start of range season; keep this?&lt;br /&gt;
					if 0~= input.month2 then&lt;br /&gt;
						tCOinS_date.rftchron = string.format ('%s %s – %s %s', season[input.month], input.year, season[input.month2], input.year2);&lt;br /&gt;
					end&lt;br /&gt;
				else															-- season–season year range&lt;br /&gt;
					tCOinS_date.rftssn = season[input.month];					-- start of range season; keep this?&lt;br /&gt;
					tCOinS_date.rftchron = season[input.month] .. '–' .. season[input.month2];	-- season–season year range&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		tCOinS_date.rftdate = date;&lt;br /&gt;
		return;																	-- done&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if 0 ~= input.day then&lt;br /&gt;
		date = string.format ('%s-%.2d-%.2d', input.year, tonumber(input.month), tonumber(input.day));	-- whole date&lt;br /&gt;
	elseif 0 ~= input.month then&lt;br /&gt;
		date = string.format ('%s-%.2d', input.year, tonumber(input.month));	-- year and month&lt;br /&gt;
	else&lt;br /&gt;
		date = string.format ('%s', input.year);								-- just year&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if 0 ~= input.year2 then&lt;br /&gt;
		if 0 ~= input.day2 then&lt;br /&gt;
			date2 = string.format ('/%s-%.2d-%.2d', input.year2, tonumber(input.month2), tonumber(input.day2));		-- whole date&lt;br /&gt;
		elseif 0 ~= input.month2 then&lt;br /&gt;
			date2 = string.format ('/%s-%.2d', input.year2, tonumber(input.month2));	-- year and month&lt;br /&gt;
		else&lt;br /&gt;
			date2 = string.format ('/%s', input.year2);							-- just year&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	tCOinS_date.rftdate = date .. date2;										-- date2 has the '/' separator&lt;br /&gt;
	return;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; P A T T E R N S &amp;gt;--------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
this is the list of patterns for date formats that this module recognizes.  Approximately the first half of these&lt;br /&gt;
patterns represent formats that might be reformatted into another format.  Those that might be reformatted have&lt;br /&gt;
'indicator' letters that identify the content of the matching capture: 'd' (day), 'm' (month), 'a' (anchor year),&lt;br /&gt;
'y' (year); second day, month, year have a '2' suffix.&lt;br /&gt;
&lt;br /&gt;
These patterns are used for both date validation and for reformatting.  This table should not be moved to ~/Configuration&lt;br /&gt;
because changes to this table require changes to check_date() and to reformatter() and reformat_date()&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local patterns = {&lt;br /&gt;
	 																			-- year-initial numerical year-month-day&lt;br /&gt;
	['ymd'] = {'^(%d%d%d%d)%-(%d%d)%-(%d%d)$', 'y', 'm', 'd'},					&lt;br /&gt;
																				-- month-initial: month day, year&lt;br /&gt;
	['Mdy'] = {'^(%D-) +([1-9]%d?), +((%d%d%d%d?)%a?)$', 'm', 'd', 'a', 'y'},&lt;br /&gt;
																				-- month-initial day range: month day–day, year; days are separated by endash&lt;br /&gt;
	['Md-dy'] = {'^(%D-) +([1-9]%d?)[%-–]([1-9]%d?), +((%d%d%d%d)%a?)$', 'm', 'd', 'd2', 'a', 'y'},&lt;br /&gt;
																				-- day-initial: day month year&lt;br /&gt;
	['dMy'] = {'^([1-9]%d?) *(%D-) +((%d%d%d%d?)%a?)$', 'd', 'm', 'a', 'y'},&lt;br /&gt;
																				-- year-initial: year month day; day: 1 or 2 two digits, leading zero allowed; not supported at en.wiki&lt;br /&gt;
--	['yMd'] = {'^((%d%d%d%d?)%a?) +(%D-) +(%d%d?)$', 'a', 'y', 'm', 'd'},&lt;br /&gt;
																				-- day-range-initial: day–day month year; days are separated by endash&lt;br /&gt;
	['d-dMy'] = {'^([1-9]%d?)[%-–]([1-9]%d?) +(%D-) +((%d%d%d%d)%a?)$', 'd', 'd2', 'm', 'a', 'y'},&lt;br /&gt;
																				-- day initial month-day-range: day month - day month year; uses spaced endash&lt;br /&gt;
	['dM-dMy'] = {'^([1-9]%d?) +(%D-) +[%-–] +([1-9]%d?) +(%D-) +((%d%d%d%d)%a?)$', 'd', 'm', 'd2', 'm2', 'a', 'y'},&lt;br /&gt;
																				-- month initial month-day-range: month day – month day, year;  uses spaced endash&lt;br /&gt;
	['Md-Mdy'] = {'^(%D-) +([1-9]%d?) +[%-–] +(%D-) +([1-9]%d?), +((%d%d%d%d)%a?)$','m', 'd', 'm2', 'd2', 'a', 'y'},&lt;br /&gt;
																				-- day initial month-day-year-range: day month year - day month year; uses spaced endash&lt;br /&gt;
	['dMy-dMy'] = {'^([1-9]%d?) +(%D-) +(%d%d%d%d) +[%-–] +([1-9]%d?) +(%D-) +((%d%d%d%d)%a?)$', 'd', 'm', 'y', 'd2', 'm2', 'a', 'y2'},&lt;br /&gt;
																				-- month initial month-day-year-range: month day, year – month day, year;  uses spaced endash&lt;br /&gt;
	['Mdy-Mdy'] = {'^(%D-) +([1-9]%d?), +(%d%d%d%d) +[%-–] +(%D-) +([1-9]%d?), +((%d%d%d%d)%a?)$', 'm', 'd', 'y', 'm2', 'd2', 'a', 'y2'},&lt;br /&gt;
&lt;br /&gt;
																				-- these date formats cannot be converted, per se, but month name can be rendered short or long&lt;br /&gt;
																				-- month/season year - month/season year; separated by spaced endash&lt;br /&gt;
	['My-My'] = {'^(%D-) +(%d%d%d%d) +[%-–] +(%D-) +((%d%d%d%d)%a?)$', 'm', 'y', 'm2', 'a', 'y2'},&lt;br /&gt;
																				-- month/season range year; months separated by endash&lt;br /&gt;
	['M-My'] = {'^(%D-)[%-–](%D-) +((%d%d%d%d)%a?)$', 'm', 'm2', 'a', 'y'},&lt;br /&gt;
																				-- month/season year or proper-name year&lt;br /&gt;
	['My'] = {'^([^%d–]-) +((%d%d%d%d)%a?)$', 'm', 'a', 'y'},					-- this way because endash is a member of %D; %D- will match January–March 2019 when it shouldn't&lt;br /&gt;
																				-- these date formats cannot be converted&lt;br /&gt;
	['Sy4-y2'] = {'^(%D-) +((%d%d)%d%d)[%-–]((%d%d)%a?)$'},						-- special case Winter/Summer year-year (YYYY-YY); year separated with unspaced endash&lt;br /&gt;
	['Sy-y'] = {'^(%D-) +(%d%d%d%d)[%-–]((%d%d%d%d)%a?)$'},						-- special case Winter/Summer year-year; year separated with unspaced endash&lt;br /&gt;
	['y-y'] = {'^(%d%d%d%d?)[%-–]((%d%d%d%d?)%a?)$'},							-- year range: YYY-YYY or YYY-YYYY or YYYY–YYYY; separated by unspaced endash; 100-9999&lt;br /&gt;
	['y4-y2'] = {'^((%d%d)%d%d)[%-–]((%d%d)%a?)$'},								-- year range: YYYY–YY; separated by unspaced endash&lt;br /&gt;
	['y'] = {'^((%d%d%d%d?)%a?)$'},												-- year; here accept either YYY or YYYY&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; C H E C K _ D A T E &amp;gt;----------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Check date format to see that it is one of the formats approved by WP:DATESNO or WP:DATERANGE. Exception: only&lt;br /&gt;
allowed range separator is endash.  Additionally, check the date to see that it is a real date: no 31 in 30-day&lt;br /&gt;
months; no 29 February when not a leap year.  Months, both long-form and three character abbreviations, and seasons&lt;br /&gt;
must be spelled correctly.  Future years beyond next year are not allowed.&lt;br /&gt;
&lt;br /&gt;
If the date fails the format tests, this function returns false and does not return values for anchor_year and&lt;br /&gt;
COinS_date.  When this happens, the date parameter is used in the COinS metadata and the CITEREF identifier gets&lt;br /&gt;
its year from the year parameter if present otherwise CITEREF does not get a date value.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
	date_string - date string from date-holding parameters (date, year, accessdate, embargo, archivedate, etc.)&lt;br /&gt;
&lt;br /&gt;
Returns:&lt;br /&gt;
	false if date string is not a real date; else&lt;br /&gt;
	true, anchor_year, COinS_date&lt;br /&gt;
		anchor_year can be used in CITEREF anchors&lt;br /&gt;
		COinS_date is ISO 8601 format date; see make_COInS_date()&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function check_date (date_string, tCOinS_date, test_accessdate)&lt;br /&gt;
	local year;			-- assume that year2, months, and days are not used;&lt;br /&gt;
	local year2=0;		-- second year in a year range&lt;br /&gt;
	local month=0;&lt;br /&gt;
	local month2=0;		-- second month in a month range&lt;br /&gt;
	local day=0;&lt;br /&gt;
	local day2=0;		-- second day in a day range&lt;br /&gt;
	local anchor_year;&lt;br /&gt;
	local coins_date;&lt;br /&gt;
&lt;br /&gt;
	if date_string:match (patterns['ymd'][1]) then								-- year-initial numerical year month day format&lt;br /&gt;
		year, month, day=date_string:match (patterns['ymd'][1]);&lt;br /&gt;
		if 12 &amp;lt; tonumber(month) or 1 &amp;gt; tonumber(month) or 1582 &amp;gt; tonumber(year) or 0 == tonumber(day) then return false; end	-- month or day number not valid or not Gregorian calendar&lt;br /&gt;
		anchor_year = year;&lt;br /&gt;
	&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['Mdy'][1]) then				-- month-initial: month day, year&lt;br /&gt;
		month, day, anchor_year, year=mw.ustring.match(date_string, patterns['Mdy'][1]);&lt;br /&gt;
		month = get_month_number (month);&lt;br /&gt;
		if 0 == month then return false; end									-- return false if month text isn't one of the twelve months&lt;br /&gt;
				&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['Md-dy'][1]) then				-- month-initial day range: month day–day, year; days are separated by endash&lt;br /&gt;
		month, day, day2, anchor_year, year=mw.ustring.match(date_string, patterns['Md-dy'][1]);&lt;br /&gt;
		if tonumber(day) &amp;gt;= tonumber(day2) then return false; end				-- date range order is left to right: earlier to later; dates may not be the same;&lt;br /&gt;
		month = get_month_number (month);&lt;br /&gt;
		if 0 == month then return false; end									-- return false if month text isn't one of the twelve months&lt;br /&gt;
		month2=month;															-- for metadata&lt;br /&gt;
		year2=year;&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['dMy'][1]) then				-- day-initial: day month year&lt;br /&gt;
		day, month, anchor_year, year=mw.ustring.match(date_string, patterns['dMy'][1]);&lt;br /&gt;
		month = get_month_number (month);&lt;br /&gt;
		if 0 == month then return false; end									-- return false if month text isn't one of the twelve months&lt;br /&gt;
&lt;br /&gt;
--[[ NOT supported at en.wiki&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['yMd'][1]) then				-- year-initial: year month day; day: 1 or 2 two digits, leading zero allowed&lt;br /&gt;
		anchor_year, year, month, day=mw.ustring.match(date_string, patterns['yMd'][1]);&lt;br /&gt;
		month = get_month_number (month);&lt;br /&gt;
		if 0 == month then return false; end									-- return false if month text isn't one of the twelve months&lt;br /&gt;
-- end NOT supported at en.wiki ]]&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['d-dMy'][1]) then				-- day-range-initial: day–day month year; days are separated by endash&lt;br /&gt;
		day, day2, month, anchor_year, year=mw.ustring.match(date_string, patterns['d-dMy'][1]);&lt;br /&gt;
		if tonumber(day) &amp;gt;= tonumber(day2) then return false; end				-- date range order is left to right: earlier to later; dates may not be the same;&lt;br /&gt;
		month = get_month_number (month);&lt;br /&gt;
		if 0 == month then return false; end									-- return false if month text isn't one of the twelve months&lt;br /&gt;
		month2=month;															-- for metadata&lt;br /&gt;
		year2=year;&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['dM-dMy'][1]) then			-- day initial month-day-range: day month - day month year; uses spaced endash&lt;br /&gt;
		day, month, day2, month2, anchor_year, year=mw.ustring.match(date_string, patterns['dM-dMy'][1]);&lt;br /&gt;
		if (not is_valid_month_season_range(month, month2)) or not is_valid_year(year) then return false; end	-- date range order is left to right: earlier to later;&lt;br /&gt;
		month = get_month_number (month);										-- for metadata&lt;br /&gt;
		month2 = get_month_number (month2);&lt;br /&gt;
		year2=year;&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['Md-Mdy'][1]) then			-- month initial month-day-range: month day – month day, year;  uses spaced endash&lt;br /&gt;
		month, day, month2, day2, anchor_year, year=mw.ustring.match(date_string, patterns['Md-Mdy'][1]);&lt;br /&gt;
		if (not is_valid_month_season_range(month, month2)) or not is_valid_year(year) then return false; end&lt;br /&gt;
		month = get_month_number (month);										-- for metadata&lt;br /&gt;
		month2 = get_month_number (month2);&lt;br /&gt;
		year2=year;&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['dMy-dMy'][1]) then			-- day initial month-day-year-range: day month year - day month year; uses spaced endash&lt;br /&gt;
		day, month, year, day2, month2, anchor_year, year2=mw.ustring.match(date_string, patterns['dMy-dMy'][1]);&lt;br /&gt;
		if tonumber(year2) &amp;lt;= tonumber(year) then return false; end				-- must be sequential years, left to right, earlier to later&lt;br /&gt;
		if not is_valid_year(year2) or not is_valid_month_range_style(month, month2) then return false; end		-- year2 no more than one year in the future; months same style&lt;br /&gt;
		month = get_month_number (month);										-- for metadata&lt;br /&gt;
		month2 = get_month_number (month2);&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['Mdy-Mdy'][1]) then			-- month initial month-day-year-range: month day, year – month day, year;  uses spaced endash&lt;br /&gt;
		month, day, year, month2, day2, anchor_year, year2=mw.ustring.match(date_string, patterns['Mdy-Mdy'][1]);&lt;br /&gt;
		if tonumber(year2) &amp;lt;= tonumber(year) then return false; end				-- must be sequential years, left to right, earlier to later&lt;br /&gt;
		if not is_valid_year(year2) or not is_valid_month_range_style(month, month2) then return false; end		-- year2 no more than one year in the future; months same style&lt;br /&gt;
		month = get_month_number (month);										-- for metadata&lt;br /&gt;
		month2 = get_month_number (month2);&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['Sy4-y2'][1]) then			-- special case Winter/Summer year-year (YYYY-YY); year separated with unspaced endash&lt;br /&gt;
		local century;&lt;br /&gt;
		month, year, century, anchor_year, year2=mw.ustring.match(date_string, patterns['Sy4-y2'][1]);&lt;br /&gt;
		if 'Winter' ~= month and 'Summer' ~= month then return false end;		-- 'month' can only be Winter or Summer&lt;br /&gt;
		anchor_year=year..'–'..anchor_year;										-- assemble anchor_year from both years&lt;br /&gt;
		year2 = century..year2;													-- add the century to year2 for comparisons&lt;br /&gt;
		if 1 ~= tonumber(year2) - tonumber(year) then return false; end			-- must be sequential years, left to right, earlier to later&lt;br /&gt;
		if not is_valid_year(year2) then return false; end						-- no year farther in the future than next year&lt;br /&gt;
		month = get_season_number (month);&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['Sy-y'][1]) then				-- special case Winter/Summer year-year; year separated with unspaced endash&lt;br /&gt;
		month, year, anchor_year, year2=mw.ustring.match(date_string, patterns['Sy-y'][1]);&lt;br /&gt;
		if 'Winter' ~= month and 'Summer' ~= month then return false end;		-- 'month' can only be Winter or Summer&lt;br /&gt;
		anchor_year=year..'–'..anchor_year;										-- assemble anchor_year from both years&lt;br /&gt;
		if 1 ~= tonumber(year2) - tonumber(year) then return false; end			-- must be sequential years, left to right, earlier to later&lt;br /&gt;
		if not is_valid_year(year2) then return false; end						-- no year farther in the future than next year&lt;br /&gt;
		month = get_season_number (month);										-- for metadata&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['My-My'][1]) then				-- month/season year - month/season year; separated by spaced endash&lt;br /&gt;
		month, year, month2, anchor_year, year2=mw.ustring.match(date_string, patterns['My-My'][1]);&lt;br /&gt;
		anchor_year=year..'–'..anchor_year;										-- assemble anchor_year from both years&lt;br /&gt;
		if tonumber(year) &amp;gt;= tonumber(year2) then return false; end				-- left to right, earlier to later, not the same&lt;br /&gt;
		if not is_valid_year(year2) then return false; end						-- no year farther in the future than next year&lt;br /&gt;
		if 0 ~= get_month_number(month) and 0 ~= get_month_number(month2) and is_valid_month_range_style(month, month2) then 	-- both must be month year, same month style&lt;br /&gt;
			month = get_month_number(month);&lt;br /&gt;
			month2 = get_month_number(month2);&lt;br /&gt;
		elseif 0 ~= get_season_number(month) and 0 ~= get_season_number(month2) then	-- both must be or season year, not mixed&lt;br /&gt;
			month = get_season_number(month);&lt;br /&gt;
			month2 = get_season_number(month2);&lt;br /&gt;
		else&lt;br /&gt;
			 return false;&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['M-My'][1]) then				-- month/season range year; months separated by endash &lt;br /&gt;
		month, month2, anchor_year, year=mw.ustring.match(date_string, patterns['M-My'][1]);&lt;br /&gt;
		if (not is_valid_month_season_range(month, month2)) or (not is_valid_year(year)) then return false; end&lt;br /&gt;
		if 0 ~= get_month_number(month) then									-- determined to be a valid range so just check this one to know if month or season&lt;br /&gt;
			month = get_month_number(month);&lt;br /&gt;
			month2 = get_month_number(month2);&lt;br /&gt;
		else&lt;br /&gt;
			month = get_season_number(month);&lt;br /&gt;
			month2 = get_season_number(month2);&lt;br /&gt;
		end&lt;br /&gt;
		year2=year;&lt;br /&gt;
		&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['My'][1]) then				-- month/season year or proper-name year&lt;br /&gt;
		month, anchor_year, year=mw.ustring.match(date_string, patterns['My'][1]);&lt;br /&gt;
		if not is_valid_year(year) then return false; end&lt;br /&gt;
		if not is_valid_month_or_season (month) and 0 == is_proper_name (month) then return false; end&lt;br /&gt;
		if 0 ~= get_month_number(month) then									-- determined to be a valid range so just check this one to know if month or season&lt;br /&gt;
			month = get_month_number(month);&lt;br /&gt;
		elseif 0 ~= get_season_number(month) then&lt;br /&gt;
			month = get_season_number(month);&lt;br /&gt;
		else&lt;br /&gt;
			month = is_proper_name (month);										-- must be proper name; not supported in COinS&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['y-y'][1]) then				-- Year range: YYY-YYY or YYY-YYYY or YYYY–YYYY; separated by unspaced endash; 100-9999&lt;br /&gt;
		year, anchor_year, year2=mw.ustring.match(date_string, patterns['y-y'][1]);&lt;br /&gt;
		anchor_year=year..'–'..anchor_year;										-- assemble anchor year from both years&lt;br /&gt;
		if tonumber(year) &amp;gt;= tonumber(year2) then return false; end				-- left to right, earlier to later, not the same&lt;br /&gt;
		if not is_valid_year(year2) then return false; end						-- no year farther in the future than next year&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, patterns['y4-y2'][1]) then				-- Year range: YYYY–YY; separated by unspaced endash&lt;br /&gt;
		local century;&lt;br /&gt;
		year, century, anchor_year, year2=mw.ustring.match(date_string, patterns['y4-y2'][1]);&lt;br /&gt;
		anchor_year=year..'–'..anchor_year;										-- assemble anchor year from both years&lt;br /&gt;
		if 13 &amp;gt; tonumber(year2) then return false; end							-- don't allow 2003-05 which might be May 2003&lt;br /&gt;
		year2 = century..year2;													-- add the century to year2 for comparisons&lt;br /&gt;
		if tonumber(year) &amp;gt;= tonumber(year2) then return false; end				-- left to right, earlier to later, not the same&lt;br /&gt;
		if not is_valid_year(year2) then return false; end						-- no year farther in the future than next year&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match (date_string, patterns['y'][1]) then				-- year; here accept either YYY or YYYY&lt;br /&gt;
		anchor_year, year=mw.ustring.match (date_string, patterns['y'][1]);&lt;br /&gt;
		if false == is_valid_year(year) then&lt;br /&gt;
			return false;&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
	else&lt;br /&gt;
		return false;															-- date format not one of the MOS:DATE approved formats&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if test_accessdate then														-- test accessdate here because we have numerical date parts&lt;br /&gt;
		if 0 ~= year and 0 ~= month and 0 ~= day and 							-- all parts of a single date required&lt;br /&gt;
			0 == year2 and 0 == month2 and 0 == day2 then						-- none of these; accessdate must not be a range&lt;br /&gt;
				if not is_valid_accessdate (year..'-'..month..'-'..day) then	&lt;br /&gt;
					return false;												-- return false when accessdate out of bounds&lt;br /&gt;
				end&lt;br /&gt;
		else&lt;br /&gt;
			return false;														-- return false when accessdate is a range of two dates&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local result=true;															-- check whole dates for validity; assume true because not all dates will go through this test&lt;br /&gt;
	if 0 ~= year and 0 ~= month and 0 ~= day and 0 == year2 and 0 == month2 and 0 == day2 then		-- YMD (simple whole date)&lt;br /&gt;
		result=is_valid_date(year,month,day);&lt;br /&gt;
&lt;br /&gt;
	elseif 0 ~= year and 0 ~= month and 0 ~= day and 0 == year2 and 0 == month2 and 0 ~= day2 then	-- YMD-d (day range)&lt;br /&gt;
		result=is_valid_date(year,month,day);&lt;br /&gt;
		result=result and is_valid_date(year,month,day2);&lt;br /&gt;
&lt;br /&gt;
	elseif 0 ~= year and 0 ~= month and 0 ~= day and 0 == year2 and 0 ~= month2 and 0 ~= day2 then	-- YMD-md (day month range)&lt;br /&gt;
		result=is_valid_date(year,month,day);&lt;br /&gt;
		result=result and is_valid_date(year,month2,day2);&lt;br /&gt;
&lt;br /&gt;
	elseif 0 ~= year and 0 ~= month and 0 ~= day and 0 ~= year2 and 0 ~= month2 and 0 ~= day2 then	-- YMD-ymd (day month year range)&lt;br /&gt;
		result=is_valid_date(year,month,day);&lt;br /&gt;
		result=result and is_valid_date(year2,month2,day2);&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if false == result then return false; end&lt;br /&gt;
&lt;br /&gt;
	if nil ~= tCOinS_date then													-- this table only passed into this function when testing |date= parameter values&lt;br /&gt;
		make_COinS_date ({year=year, month=month, day=day, year2=year2, month2=month2, day2=day2}, tCOinS_date);	-- make an ISO 8601 date string for COinS&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return true, anchor_year;													-- format is good and date string represents a real date&lt;br /&gt;
end	&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; D A T E S &amp;gt;--------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Cycle the date-holding parameters in passed table date_parameters_list through check_date() to check compliance with MOS:DATE. For all valid dates, check_date() returns&lt;br /&gt;
true. The |date= parameter test is unique, it is the only date holding parameter from which values for anchor_year (used in CITEREF identifiers) and COinS_date (used in&lt;br /&gt;
the COinS metadata) are derived.  The |date= parameter is the only date-holding parameter that is allowed to contain the no-date keywords &amp;quot;n.d.&amp;quot; or &amp;quot;nd&amp;quot; (without quotes).&lt;br /&gt;
&lt;br /&gt;
Unlike most error messages created in this module, only one error message is created by this function. Because all of the date holding parameters are processed serially,&lt;br /&gt;
a single error message is created as the dates are tested.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function dates(date_parameters_list, tCOinS_date)&lt;br /&gt;
	local anchor_year;		-- will return as nil if the date being tested is not |date=&lt;br /&gt;
	local COinS_date;		-- will return as nil if the date being tested is not |date=&lt;br /&gt;
	local embargo_date;															-- if embargo date is a good dmy, mdy, ymd date then holds original value else reset to 9999&lt;br /&gt;
	local error_message = &amp;quot;&amp;quot;;&lt;br /&gt;
	local good_date = false;&lt;br /&gt;
	&lt;br /&gt;
	for k, v in pairs(date_parameters_list) do									-- for each date-holding parameter in the list&lt;br /&gt;
		if is_set(v.val) then													-- if the parameter has a value&lt;br /&gt;
			v.val = mw.ustring.gsub (v.val, '%d', cfg.date_names.local_digits);	-- translate 'local' digits to Western 0-9&lt;br /&gt;
			if v.val:match(&amp;quot;^c%. [1-9]%d%d%d?%a?$&amp;quot;) then						-- special case for c. year or with or without CITEREF disambiguator - only |date= and |year=&lt;br /&gt;
				local year = v.val:match(&amp;quot;c%. ([1-9]%d%d%d?)%a?&amp;quot;);				-- get the year portion so it can be tested&lt;br /&gt;
				if 'date'==k then&lt;br /&gt;
					anchor_year, COinS_date = v.val:match(&amp;quot;((c%. [1-9]%d%d%d?)%a?)&amp;quot;);	-- anchor year and COinS_date only from |date= parameter&lt;br /&gt;
					good_date = is_valid_year(year);&lt;br /&gt;
				elseif 'year'==k then&lt;br /&gt;
					good_date = is_valid_year(year);&lt;br /&gt;
				end&lt;br /&gt;
			elseif 'date'==k then												-- if the parameter is |date=&lt;br /&gt;
				if v.val:match(&amp;quot;^n%.d%.%a?$&amp;quot;) then								-- if |date=n.d. with or without a CITEREF disambiguator&lt;br /&gt;
					good_date, anchor_year, COinS_date = true, v.val:match(&amp;quot;((n%.d%.)%a?)&amp;quot;);	--&amp;quot;n.d.&amp;quot;; no error when date parameter is set to no date&lt;br /&gt;
				elseif v.val:match(&amp;quot;^nd%a?$&amp;quot;) then								-- if |date=nd with or without a CITEREF disambiguator&lt;br /&gt;
					good_date, anchor_year, COinS_date = true, v.val:match(&amp;quot;((nd)%a?)&amp;quot;);	--&amp;quot;nd&amp;quot;;	no error when date parameter is set to no date&lt;br /&gt;
				else&lt;br /&gt;
					good_date, anchor_year, COinS_date = check_date (v.val, tCOinS_date);	-- go test the date&lt;br /&gt;
				end&lt;br /&gt;
			elseif 'year'==k then												-- if the parameter is |year= it should hold only a year value&lt;br /&gt;
				if v.val:match(&amp;quot;^[1-9]%d%d%d?%a?$&amp;quot;) then						-- if |year= 3 or 4 digits only with or without a CITEREF disambiguator&lt;br /&gt;
					good_date, anchor_year, COinS_date = true, v.val:match(&amp;quot;((%d+)%a?)&amp;quot;);&lt;br /&gt;
				end&lt;br /&gt;
			elseif 'access-date'==k then										-- if the parameter is |date=&lt;br /&gt;
				good_date = check_date (v.val, nil, true);						-- go test the date; nil is a placeholder; true is the test_accessdate flag&lt;br /&gt;
			elseif 'embargo'==k then											-- if the parameter is |embargo=&lt;br /&gt;
				good_date = check_date (v.val);									-- go test the date&lt;br /&gt;
				if true == good_date then										-- if the date is a valid date&lt;br /&gt;
					good_date, embargo_date = is_valid_embargo_date (v.val);	-- is |embargo= date a single dmy, mdy, or ymd formatted date? yes:returns embargo; no: returns 9999&lt;br /&gt;
				end&lt;br /&gt;
			else																-- any other date-holding parameter&lt;br /&gt;
				good_date = check_date (v.val);									-- go test the date&lt;br /&gt;
			end&lt;br /&gt;
			if false==good_date then											-- assemble one error message so we don't add the tracking category multiple times&lt;br /&gt;
				if is_set(error_message) then									-- once we've added the first portion of the error message ...&lt;br /&gt;
					error_message=error_message .. &amp;quot;, &amp;quot;;						-- ... add a comma space separator&lt;br /&gt;
				end&lt;br /&gt;
				error_message=error_message .. &amp;quot;&amp;amp;#124;&amp;quot; .. v.name .. &amp;quot;=&amp;quot;;		-- add the failed parameter&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return anchor_year, embargo_date, error_message;							-- and done&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; Y E A R _ D A T E _ C H E C K &amp;gt;------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Compare the value provided in |year= with the year value(s) provided in |date=.  This function returns a numeric value:&lt;br /&gt;
	0 - year value does not match the year value in date&lt;br /&gt;
	1 - (default) year value matches the year value in date or one of the year values when date contains two years&lt;br /&gt;
	2 - year value matches the year value in date when date is in the form YYYY-MM-DD and year is disambiguated (|year=YYYYx)&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function year_date_check (year_string, date_string)&lt;br /&gt;
	local year;&lt;br /&gt;
	local date1;&lt;br /&gt;
	local date2;&lt;br /&gt;
	local result = 1;															-- result of the test; assume that the test passes&lt;br /&gt;
	&lt;br /&gt;
	year = year_string:match ('(%d%d%d%d?)');&lt;br /&gt;
&lt;br /&gt;
	if date_string:match ('%d%d%d%d%-%d%d%-%d%d') and year_string:match ('%d%d%d%d%a') then	--special case where both date and year are required YYYY-MM-DD and YYYYx&lt;br /&gt;
		date1 = date_string:match ('(%d%d%d%d)');&lt;br /&gt;
		year = year_string:match ('(%d%d%d%d)');&lt;br /&gt;
		if year ~= date1 then&lt;br /&gt;
			result = 0;															-- years don't match&lt;br /&gt;
		else&lt;br /&gt;
			result = 2;															-- years match; but because disambiguated, don't add to maint cat&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
	elseif date_string:match (&amp;quot;%d%d%d%d?.-%d%d%d%d?&amp;quot;) then						-- any of the standard range formats of date with two three- or four-digit years&lt;br /&gt;
		date1, date2 = date_string:match (&amp;quot;(%d%d%d%d?).-(%d%d%d%d?)&amp;quot;);&lt;br /&gt;
		if year ~= date1 and year ~= date2 then&lt;br /&gt;
			result = 0;&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
	elseif mw.ustring.match(date_string, &amp;quot;%d%d%d%d[%-–]%d%d&amp;quot;) then				-- YYYY-YY date ranges&lt;br /&gt;
		local century;&lt;br /&gt;
		date1, century, date2 = mw.ustring.match(date_string, &amp;quot;((%d%d)%d%d)[%-–]+(%d%d)&amp;quot;);&lt;br /&gt;
		date2 = century..date2;													-- convert YY to YYYY&lt;br /&gt;
		if year ~= date1 and year ~= date2 then&lt;br /&gt;
			result = 0;&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
	elseif date_string:match (&amp;quot;%d%d%d%d?&amp;quot;) then									-- any of the standard formats of date with one year&lt;br /&gt;
		date1 = date_string:match (&amp;quot;(%d%d%d%d?)&amp;quot;);&lt;br /&gt;
		if year ~= date1 then&lt;br /&gt;
			result = 0;&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		result = 0;																-- no recognizable year in date&lt;br /&gt;
	end&lt;br /&gt;
	return result;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; R E F O R M A T T E R &amp;gt;--------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
reformat 'date' into new format specified by format_param if pattern_idx (the current format of 'date') can be&lt;br /&gt;
reformatted.  Does the grunt work for reformat_dates().&lt;br /&gt;
&lt;br /&gt;
The table re_formats maps patern_idx (current format) and format_param (desired format) to a table that holds:&lt;br /&gt;
	format string used by string.format()&lt;br /&gt;
	identifier letters ('d', 'm', 'y', 'd2', 'm2', 'y2') that serve as indexes into a table t{} that holds captures&lt;br /&gt;
		from mw.ustring.match() for the various date parts specified by  patterns[pattern_idx][1]&lt;br /&gt;
&lt;br /&gt;
Items in patterns{} have the general form:&lt;br /&gt;
	['ymd'] = {'^(%d%d%d%d)%-(%d%d)%-(%d%d)$', 'y', 'm', 'd'}, where:&lt;br /&gt;
		['ymd'] is pattern_idx&lt;br /&gt;
		patterns['ymd'][1] is the match pattern with captures for mw.ustring.match()&lt;br /&gt;
		patterns['ymd'][2] is an indicator letter identifying the content of the first capture&lt;br /&gt;
		patterns['ymd'][3] ... the second capture etc&lt;br /&gt;
&lt;br /&gt;
when a pattern matches a date, the captures are loaded into table t{} in capture order using the idemtifier&lt;br /&gt;
characters as indexes into t{}  For the above, a ymd date is in t{} as:&lt;br /&gt;
	t.y = first capture (year), t.m = second capture (month), t.d = third capture (day)&lt;br /&gt;
&lt;br /&gt;
To reformat, this function is called with the pattern_idx that matches the current format of the date and with&lt;br /&gt;
format_param set to the desired format.  This function loads table t{} as described and then calls string.format()&lt;br /&gt;
with the format string specified by re_format[pattern_idx][format_param][1] using values taken from t{} according&lt;br /&gt;
to the capture identifier letters specified by patterns[pattern_idx][format_param][n] where n is 2..&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local re_formats = {&lt;br /&gt;
	['ymd'] = {																	-- date format is ymd; reformat to:&lt;br /&gt;
		['mdy'] = {'%s %s, %s', 'm', 'd', 'y'},									-- |df=mdy&lt;br /&gt;
		['dmy'] = {'%s %s %s', 'd', 'm', 'y'},									-- |df=dmy&lt;br /&gt;
--		['yMd'] = {'%s %s %s', 'y', 'm', 'd'},									-- |df=yMd; not supported at en.wiki&lt;br /&gt;
		},&lt;br /&gt;
	['Mdy'] = {																	-- date format is Mdy; reformat to:&lt;br /&gt;
		['mdy'] = {'%s %s, %s', 'm', 'd', 'y'},									-- for long/short reformatting&lt;br /&gt;
		['dmy'] = {'%s %s %s', 'd', 'm', 'y'},									-- |df=dmy&lt;br /&gt;
		['ymd'] = {'%s-%s-%s', 'y', 'm', 'd'},									-- |df=ymd&lt;br /&gt;
--		['yMd'] = {'%s %s %s', 'y', 'm', 'd'},									-- |df=yMd; not supported at en.wiki&lt;br /&gt;
		},&lt;br /&gt;
	['dMy'] = {																	-- date format is dMy; reformat to:&lt;br /&gt;
		['dmy'] = {'%s %s %s', 'd', 'm', 'y'},									-- for long/short reformatting&lt;br /&gt;
		['mdy'] = {'%s %s, %s', 'm', 'd', 'y'},									-- |df=mdy&lt;br /&gt;
		['ymd'] = {'%s-%s-%s', 'y', 'm', 'd'},									-- |df=ymd&lt;br /&gt;
--		['yMd'] = {'%s %s %s', 'y', 'm', 'd'},									-- |df=yMd; not supported at en.wiki&lt;br /&gt;
		},&lt;br /&gt;
	['Md-dy'] = {																-- date format is Md-dy; reformat to:&lt;br /&gt;
		['mdy'] = {'%s %s–%s, %s', 'm', 'd', 'd2', 'y'},						-- for long/short reformatting&lt;br /&gt;
		['dmy'] = {'%s–%s %s %s', 'd', 'd2', 'm', 'y'},							-- |df=dmy -&amp;gt; d-dMy &lt;br /&gt;
		},&lt;br /&gt;
	['d-dMy'] = {																-- date format is d-d&amp;gt;y; reformat to:&lt;br /&gt;
		['dmy'] = {'%s–%s %s %s', 'd', 'd2', 'm', 'y'},							-- for long/short reformatting&lt;br /&gt;
		['mdy'] = {'%s %s–%s, %s', 'm', 'd', 'd2', 'y'},						-- |df=mdy -&amp;gt; Md-dy &lt;br /&gt;
		},&lt;br /&gt;
	['dM-dMy'] = {																-- date format is dM-dMy; reformat to:&lt;br /&gt;
		['dmy'] = {'%s %s – %s %s %s', 'd', 'm', 'd2', 'm2', 'y'},				-- for long/short reformatting&lt;br /&gt;
		['mdy'] = {'%s %s – %s %s, %s', 'm', 'd', 'm2', 'd2', 'y'},				-- |df=mdy -&amp;gt; Md-Mdy &lt;br /&gt;
		},&lt;br /&gt;
	['Md-Mdy'] = {																-- date format is Md-Mdy; reformat to:&lt;br /&gt;
		['mdy'] = {'%s %s – %s %s, %s', 'm', 'd',  'm2', 'd2', 'y'},			-- for long/short reformatting&lt;br /&gt;
		['dmy'] = {'%s %s – %s %s %s', 'd', 'm', 'd2', 'm2', 'y'},				-- |df=dmy -&amp;gt; dM-dMy &lt;br /&gt;
		},&lt;br /&gt;
	['dMy-dMy'] = {																-- date format is dMy-dMy; reformat to:&lt;br /&gt;
		['dmy'] = {'%s %s %s – %s %s %s', 'd', 'm', 'y', 'd2', 'm2', 'y2'},		-- for long/short reformatting&lt;br /&gt;
		['mdy'] = {'%s %s, %s – %s %s, %s', 'm', 'd', 'y', 'm2', 'd2', 'y2'},	-- |df=mdy -&amp;gt; Mdy-Mdy &lt;br /&gt;
		},&lt;br /&gt;
	['Mdy-Mdy'] = {																-- date format is Mdy-Mdy; reformat to:&lt;br /&gt;
		['mdy'] = {'%s %s, %s – %s %s, %s', 'm', 'd', 'y', 'm2', 'd2', 'y2'},	-- for long/short reformatting&lt;br /&gt;
		['dmy'] = {'%s %s %s – %s %s %s', 'd', 'm', 'y', 'd2', 'm2', 'y2'},		-- |df=dmy -&amp;gt; dMy-dMy &lt;br /&gt;
		},&lt;br /&gt;
	['My-My'] = {																-- these for long/short reformatting&lt;br /&gt;
		['any'] = {'%s %s – %s %s', 'm', 'y', 'm2', 'y2'},						-- dmy/mdy agnostic&lt;br /&gt;
		},&lt;br /&gt;
	['M-My'] = {																-- these for long/short reformatting&lt;br /&gt;
		['any'] = {'%s–%s %s', 'm', 'm2', 'y'},									-- dmy/mdy agnostic&lt;br /&gt;
		},&lt;br /&gt;
	['My'] = {																	-- these for long/short reformatting&lt;br /&gt;
		['any'] = {'%s %s', 'm', 'y'},											-- dmy/mdy agnostic&lt;br /&gt;
		},&lt;br /&gt;
--	['yMd'] = {																	-- not supported at en.wiki&lt;br /&gt;
--		['mdy'] = {'%s %s, %s', 'm', 'd', 'y'},									-- |df=mdy&lt;br /&gt;
--		['dmy'] = {'%s %s %s', 'd', 'm', 'y'},									-- |df=dmy&lt;br /&gt;
--		['ymd'] = {'%s-%s-%s', 'y', 'm', 'd'},									-- |df=ymd&lt;br /&gt;
--		},&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function reformatter (date, pattern_idx, format_param, mon_len)&lt;br /&gt;
	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&lt;br /&gt;
		return;																	-- not in this set of date format patterns then not a reformattable date&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	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&lt;br /&gt;
		return;																	-- ymd date ranges not supported at en.wiki; no point in reformatting ymd to ymd&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if in_array (pattern_idx, {'My', 'M-My', 'My-My'}) then						-- these are not dmy/mdy so can't be 'reformatted' into either&lt;br /&gt;
		format_param = 'any';													-- so format-agnostic &lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
--	if 'yMd' == format_param and in_array (pattern_idx, {'yMd', 'Md-dy', 'd-dMy', 'dM-dMy', 'Md-Mdy', 'dMy-dMy', 'Mdy-Mdy'}) then	-- not supported at en.wiki&lt;br /&gt;
	if 'yMd' == format_param then												-- not supported at en.wiki&lt;br /&gt;
		return;																	-- not a reformattable date&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local c1, c2, c3, c4, c5, c6, c7;											-- these hold the captures specified in patterns[pattern_idx][1]&lt;br /&gt;
	c1, c2, c3, c4, c5, c6, c7 = mw.ustring.match (date, patterns[pattern_idx][1]);	-- get the captures&lt;br /&gt;
&lt;br /&gt;
	local t = {																	-- table that holds k/v pairs of date parts from the captures and patterns[pattern_idx][2..]&lt;br /&gt;
		[patterns[pattern_idx][2]] = c1;										-- at minimum there is always one capture with a matching indicator letter&lt;br /&gt;
		[patterns[pattern_idx][3] or 'x'] = c2;									-- patterns can have a variable number of captures; each capture requires an indicator letter;&lt;br /&gt;
		[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;&lt;br /&gt;
		[patterns[pattern_idx][5] or 'x'] = c4;									-- the 'x' here spoofs an indicator letter to prevent 'table index is nil' error&lt;br /&gt;
		[patterns[pattern_idx][6] or 'x'] = c5;&lt;br /&gt;
		[patterns[pattern_idx][7] or 'x'] = c6;&lt;br /&gt;
		[patterns[pattern_idx][8] or 'x'] = c7;&lt;br /&gt;
		};&lt;br /&gt;
&lt;br /&gt;
	if tonumber(t.m) then														-- if raw month is a number (converting from ymd)&lt;br /&gt;
		if 's' == mon_len then													-- if we are to use abbreviated month names&lt;br /&gt;
			t.m = cfg.date_names['inv_local_s'][tonumber(t.m)];					-- convert it to a month name&lt;br /&gt;
		else&lt;br /&gt;
			t.m = cfg.date_names['inv_local_l'][tonumber(t.m)];					-- convert it to a month name&lt;br /&gt;
		end&lt;br /&gt;
		t.d = t.d:gsub ('0(%d)', '%1');											-- strip leading '0' from day if present&lt;br /&gt;
	elseif 'ymd' == format_param then											-- when converting to ymd&lt;br /&gt;
		if 1582 &amp;gt; tonumber(t.y) then											-- ymd format dates not allowed before 1582&lt;br /&gt;
			return;&lt;br /&gt;
		end&lt;br /&gt;
		t.m = string.format ('%02d', get_month_number (t.m));					-- make sure that month and day are two digits&lt;br /&gt;
		t.d = string.format ('%02d', t.d);&lt;br /&gt;
	elseif mon_len then															-- if mon_len is set to either 'short' or 'long'&lt;br /&gt;
		for _, mon in ipairs ({'m', 'm2'}) do									-- because there can be two month names, check both &lt;br /&gt;
			if t[mon] then&lt;br /&gt;
				t[mon] = get_month_number (t[mon]);								-- get the month number for this month (is length agnostic)&lt;br /&gt;
				if 0 == t[mon] then return; end									-- seasons and named dates can't be converted&lt;br /&gt;
				t[mon] = (('s' == mon_len) and cfg.date_names['inv_local_s'][t[mon]]) or cfg.date_names['inv_local_l'][t[mon]];	-- fetch month name according to length&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local new_date = string.format (re_formats[pattern_idx][format_param][1],	-- format string&lt;br /&gt;
		t[re_formats[pattern_idx][format_param][2]],							-- named captures from t{}&lt;br /&gt;
		t[re_formats[pattern_idx][format_param][3]],&lt;br /&gt;
		t[re_formats[pattern_idx][format_param][4]],&lt;br /&gt;
		t[re_formats[pattern_idx][format_param][5]],&lt;br /&gt;
		t[re_formats[pattern_idx][format_param][6]],&lt;br /&gt;
		t[re_formats[pattern_idx][format_param][7]],&lt;br /&gt;
		t[re_formats[pattern_idx][format_param][8]]&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
	return new_date;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[-------------------------&amp;lt; R E F O R M A T _ D A T E S &amp;gt;--------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Reformats existing dates into the format specified by format.&lt;br /&gt;
&lt;br /&gt;
format is one of several manual keywords: dmy, dmy-all, mdy, mdy-all, ymd, ymd-all.  The -all version includes&lt;br /&gt;
access- and archive-dates; otherwise these dates are not reformatted.&lt;br /&gt;
&lt;br /&gt;
This function allows automatic date formatting.  In ~/Configuration, the article source is searched for one of&lt;br /&gt;
the {{use xxx dates}} templates.  If found, xxx becomes the global date format as xxx-all.  If |cs1-dates= in&lt;br /&gt;
{{use xxx dates}} has legitimate value then that value determines how cs1|2 dates will be rendered.  Legitimate&lt;br /&gt;
values for |cs1-dates= are:&lt;br /&gt;
	l - all dates are rendered with long month names&lt;br /&gt;
	ls - publication dates use long month names; access-/archive-dates use abbreviated month names&lt;br /&gt;
	ly - publication dates use long month names; access-/archive-dates rendered in ymd format&lt;br /&gt;
	s - all dates are rendered with abbreviated (short) month names&lt;br /&gt;
	sy - publication dates use abbreviated month names; access-/archive-dates rendered in ymd format&lt;br /&gt;
	y - all dates are rendered in ymd format&lt;br /&gt;
&lt;br /&gt;
the format argument for automatic date formatting will be the format specified by {{use xxx dates}} with the&lt;br /&gt;
value supplied by |cs1-dates so one of: xxx-l, xxx-ls, xxx-ly, xxx-s, xxx-sy, xxx-y, or simply xxx (|cs1-dates=&lt;br /&gt;
empty, omitted, or invalid) where xxx shall be either of dmy or mdy.&lt;br /&gt;
&lt;br /&gt;
dates are extracted from date_parameters_list, reformatted (if appropriate), and then written back into the&lt;br /&gt;
list in the new format.  Dates in date_parameters_list are presumed here to be valid (no errors).  This function&lt;br /&gt;
returns true when a date has been reformatted, false else.  Actual reformatting is done by reformatter().&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function reformat_dates (date_parameters_list, format)&lt;br /&gt;
	local all = false;															-- set to false to skip access- and archive-dates&lt;br /&gt;
	local len_p = 'l';															-- default publication date length shall be long&lt;br /&gt;
	local len_a = 'l';															-- default access-/archive-date length shall be long&lt;br /&gt;
	local result = false;&lt;br /&gt;
	local new_date;																&lt;br /&gt;
	&lt;br /&gt;
	if format:match('%a+%-all') then											-- manual df keyword; auto df keyword when length not specified in {{use xxx dates}}; &lt;br /&gt;
		format = format:match('(%a+)%-all');									-- extract the format&lt;br /&gt;
		all = true;																-- all dates are long format dates because this keyword doesn't specify length&lt;br /&gt;
	elseif format:match('%a+%-[lsy][sy]?') then									-- auto df keywords; internal only&lt;br /&gt;
		all = true;																-- auto df applies to all dates; use length specified by capture len_p for all dates&lt;br /&gt;
		format, len_p, len_a = format:match('(%a+)%-([lsy])([sy]?)');			-- extract the format and length keywords&lt;br /&gt;
		if 'y' == len_p then													-- because allowed by MOS:DATEUNIFY (sort of) range dates and My dates not reformatted&lt;br /&gt;
			format = 'ymd';														-- override {{use xxx dates}}&lt;br /&gt;
		elseif (not is_set(len_a)) or (len_p == len_a) then						-- no access-/archive-date length specified or same length as publication dates then&lt;br /&gt;
			len_a = len_p;														-- in case len_a not set&lt;br /&gt;
		end&lt;br /&gt;
	end																			-- else only publication dates and they are long&lt;br /&gt;
&lt;br /&gt;
	for param_name, param_val in pairs (date_parameters_list) do				-- for each date-holding parameter in the list&lt;br /&gt;
		if is_set (param_val.val) then											-- if the parameter has a value&lt;br /&gt;
			if not (not all and in_array (param_name, {'access-date', 'archive-date'})) then	-- skip access- or archive-date unless format is xxx-all; yeah, ugly; TODO: find a better way&lt;br /&gt;
				for pattern_idx, pattern in pairs (patterns) do&lt;br /&gt;
					if mw.ustring.match (param_val.val, pattern[1]) then&lt;br /&gt;
						if all and in_array (param_name, {'access-date', 'archive-date'}) then	-- if this date is an access- or archive-date&lt;br /&gt;
							new_date = reformatter (param_val.val, pattern_idx, (('y' == len_a) and 'ymd') or format, len_a);	-- choose ymd or dmy/mdy according to len_a setting&lt;br /&gt;
						else													-- all other dates&lt;br /&gt;
							new_date = reformatter (param_val.val, pattern_idx, format, len_p);&lt;br /&gt;
						end&lt;br /&gt;
						&lt;br /&gt;
						if new_date then										-- set when date was reformatted&lt;br /&gt;
							date_parameters_list[param_name].val = new_date;	-- update date in date list&lt;br /&gt;
							result = true;										-- and announce that changes have been made&lt;br /&gt;
						end&lt;br /&gt;
					end	-- if&lt;br /&gt;
				end		-- for&lt;br /&gt;
			end			-- if&lt;br /&gt;
		end				-- if&lt;br /&gt;
	end					-- for&lt;br /&gt;
return result;																	-- declare boolean result and done&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; D A T E _ H Y P H E N _ T O _ D A S H &amp;gt;----------------------------------------&lt;br /&gt;
&lt;br /&gt;
Loops through the list of date-holding parameters and converts any hyphen to an ndash.  Not called if the cs1|2&lt;br /&gt;
template has any date errors.&lt;br /&gt;
&lt;br /&gt;
Modifies the date_parameters_list and returns true if hyphens are replaced, else returns false.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function date_hyphen_to_dash (date_parameters_list)&lt;br /&gt;
	local result = false;&lt;br /&gt;
	local n;&lt;br /&gt;
	for param_name, param_val in pairs(date_parameters_list) do					-- for each date-holding parameter in the list&lt;br /&gt;
		if is_set (param_val.val) then&lt;br /&gt;
			if not mw.ustring.match (param_val.val, '%d%d%d%d%-%d%d%-%d%d') then	-- for those that are not ymd dates (ustring because here digits may not be western)&lt;br /&gt;
				param_val.val, n = param_val.val:gsub ('%-', '–');				-- replace any hyphen with ndash&lt;br /&gt;
				if 0 ~= n then&lt;br /&gt;
					date_parameters_list[param_name].val = param_val.val;		-- update the list&lt;br /&gt;
					result = true;&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return result;																-- so we know if any hyphens were replaced&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[-------------------------&amp;lt; D A T E _ N A M E _ X L A T E &amp;gt;------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Attempts to translate English month names to local-language month names using names supplied by MediaWiki's&lt;br /&gt;
date parser function.  This is simple name-for-name replacement and may not work for all languages.&lt;br /&gt;
&lt;br /&gt;
if xlat_dig is true, this function will also translate western (English) digits to the local language's digits.&lt;br /&gt;
This will also translate ymd dates.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function	date_name_xlate (date_parameters_list, xlt_dig)&lt;br /&gt;
	local xlate;&lt;br /&gt;
	local mode;																	-- long or short month names&lt;br /&gt;
	local modified = false;&lt;br /&gt;
	local date;&lt;br /&gt;
	&lt;br /&gt;
	for param_name, param_val in pairs(date_parameters_list) do					-- for each date-holding parameter in the list&lt;br /&gt;
		if is_set(param_val.val) then											-- if the parameter has a value&lt;br /&gt;
			date = param_val.val;&lt;br /&gt;
			for month in mw.ustring.gmatch (date, '%a+') do						-- iterate through all dates in the date (single date or date range)&lt;br /&gt;
				if cfg.date_names.en.long[month] then&lt;br /&gt;
					mode = 'F';													-- English name is long so use long local name&lt;br /&gt;
				elseif cfg.date_names.en.short[month] then&lt;br /&gt;
					mode = 'M';													-- English name is short so use short local name&lt;br /&gt;
				else&lt;br /&gt;
					mode = nil;													-- not an English month name; could be local language month name or an English season name&lt;br /&gt;
				end&lt;br /&gt;
		&lt;br /&gt;
				if mode then													-- might be a season&lt;br /&gt;
					xlate = mw.getContentLanguage():formatDate(mode, '1' .. month);	-- translate the month name to this local language&lt;br /&gt;
					date = mw.ustring.gsub (date, month, xlate);				-- replace the English with the translation&lt;br /&gt;
					date_parameters_list[param_name].val = date;				-- save the translated date&lt;br /&gt;
					modified = true;&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			if xlt_dig then														-- shall we also translate digits?&lt;br /&gt;
				date = date:gsub ('%d', cfg.date_names.xlate_digits);			-- translate digits from western to 'local digits'&lt;br /&gt;
				date_parameters_list[param_name].val = date;					-- save the translated date&lt;br /&gt;
				modified = true;&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return modified;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; S E T _ S E L E C T E D _ M O D U L E S &amp;gt;--------------------------------------&lt;br /&gt;
&lt;br /&gt;
Sets local imported functions table to same (live or sandbox) as that used by the other modules.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function set_selected_modules (cfg_table_ptr, utilities_page_ptr)&lt;br /&gt;
	is_set = utilities_page_ptr.is_set;											-- import functions from selected Module:Citation/CS1/Utilities module&lt;br /&gt;
	in_array = utilities_page_ptr.in_array;										-- import functions from selected Module:Citation/CS1/Utilities module&lt;br /&gt;
	cfg = cfg_table_ptr;														-- import tables from selected Module:Citation/CS1/Configuration&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; E X P O R T E D   F U N C T I O N S &amp;gt;------------------------------------------&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
return {																		-- return exported functions&lt;br /&gt;
	dates = dates,&lt;br /&gt;
	year_date_check = year_date_check,&lt;br /&gt;
	reformat_dates = reformat_dates,&lt;br /&gt;
	date_hyphen_to_dash = date_hyphen_to_dash,&lt;br /&gt;
	date_name_xlate = date_name_xlate,&lt;br /&gt;
	set_selected_modules = set_selected_modules&lt;br /&gt;
	}&lt;/div&gt;</summary>
		<author><name>103.7.131.178</name></author>
		
	</entry>
</feed>