Module:Citation/CS1/Date validation: Difference between revisions
Content added Content deleted
(Synch from sandbox;) |
m (1 revision imported: Modules) |
||
Line 2: | Line 2: | ||
local p = {} |
local p = {} |
||
--[[--------------------------< I S _ V A L I D _ A C C E S S D A T E >---------------------------------------- |
|||
--[[--------------------------< F O R W A R D D E C L A R A T I O N S >-------------------------------------- |
|||
]] |
|||
local is_set, in_array; -- imported function from selected Module:Citation/CS1/Utilities |
|||
--[=[-------------------------< I S _ V A L I D _ A C C E S S D A T E >---------------------------------------- |
|||
returns true if: |
returns true if: |
||
Line 21: | Line 14: | ||
adding 24 hours gives 2015-01-03T00:00:00 – one second more than tomorrow |
adding 24 hours gives 2015-01-03T00:00:00 – one second more than tomorrow |
||
]] |
|||
This function does not work for languages other than English. Wikimedia #time: parser apparently doesn't understand |
|||
non-Engish date month names. This function will always return false when the date contains a non-English month name |
|||
because good1 is false after the call to lang.formatDate. |
|||
For the time being, to use this function, supply it with YYYY-MM-DD format dates. See |
|||
[[bs:Modul:Citation/CS1/Date validation/igralište]] for one possible solution. |
|||
]=] |
|||
local function is_valid_accessdate (accessdate) |
local function is_valid_accessdate (accessdate) |
||
Line 61: | Line 47: | ||
local long_months = {['January']=1, ['February']=2, ['March']=3, ['April']=4, ['May']=5, ['June']=6, ['July']=7, ['August']=8, ['September']=9, ['October']=10, ['November']=11, ['December']=12}; |
local long_months = {['January']=1, ['February']=2, ['March']=3, ['April']=4, ['May']=5, ['June']=6, ['July']=7, ['August']=8, ['September']=9, ['October']=10, ['November']=11, ['December']=12}; |
||
local short_months = {['Jan']=1, ['Feb']=2, ['Mar']=3, ['Apr']=4, ['May']=5, ['Jun']=6, ['Jul']=7, ['Aug']=8, ['Sep']=9, ['Oct']=10, ['Nov']=11, ['Dec']=12}; |
local short_months = {['Jan']=1, ['Feb']=2, ['Mar']=3, ['Apr']=4, ['May']=5, ['Jun']=6, ['Jul']=7, ['Aug']=8, ['Sep']=9, ['Oct']=10, ['Nov']=11, ['Dec']=12}; |
||
local temp; |
|||
return long_months[month] or -- if month is the long-form name |
|||
temp=long_months[month]; |
|||
short_months[month] or -- if month is the short-form name |
|||
if temp then return temp; end -- if month is the long-form name |
|||
0; -- misspelled, improper case, or not a month name |
|||
temp=short_months[month]; |
|||
if temp then return temp; end -- if month is the short-form name |
|||
return 0; -- misspelled, improper case, or not a month name |
|||
end |
end |
||
Line 73: | Line 62: | ||
local function get_season_number (season) |
local function get_season_number (season) |
||
local season_list = {['Winter']= |
local season_list = {['Winter']=1, ['Spring']=2, ['Summer']=3, ['Fall']=4, ['Autumn']=4} |
||
local temp; |
local temp; |
||
temp=season_list[season]; |
temp=season_list[season]; |
||
if temp then return temp; end |
if temp then return temp; end -- if season is a valid name return its number |
||
return 0; |
return 0; -- misspelled, improper case, or not a season name |
||
end |
end |
||
Line 87: | Line 76: | ||
local function is_proper_name (name) |
local function is_proper_name (name) |
||
local name_list = {['Christmas']= |
local name_list = {['Christmas']=1} |
||
local temp; |
local temp; |
||
temp=name_list[name]; |
temp=name_list[name]; |
||
Line 108: | Line 97: | ||
return true; |
return true; |
||
end |
end |
||
--[[--------------------------< I S _ V A L I D _ Y E A R >---------------------------------------------------- |
--[[--------------------------< I S _ V A L I D _ Y E A R >---------------------------------------------------- |
||
Line 114: | Line 104: | ||
]] |
]] |
||
local year_limit; |
|||
local function is_valid_year(year) |
local function is_valid_year(year) |
||
if not is_set(year_limit) then |
if not is_set(year_limit) then |
||
Line 122: | Line 112: | ||
end |
end |
||
--[[ |
|||
--[[--------------------------< I S _ V A L I D _ D A T E >---------------------------------------------------- |
|||
Returns true if day is less than or equal to the number of days in month and year is no farther into the future |
Returns true if day is less than or equal to the number of days in month and year is no farther into the future than next year; else returns false. |
||
than next year; else returns false. |
|||
Assumes Julian calendar prior to year 1582 and Gregorian calendar thereafter. Accounts for Julian calendar leap |
|||
years before 1582 and Gregorian leap years after 1582. Where the two calendars overlap (1582 to approximately |
|||
1923) dates are assumed to be Gregorian. |
|||
Assumes Julian calendar prior to year 1582 and Gregorian calendar thereafter. Accounts for Julian calendar leap years before 1582 and Gregorian leap years after 1582. |
|||
Where the two calendars overlap (1582 to approximately 1923) dates are assumed to be Gregorian. |
|||
]] |
]] |
||
local function is_valid_date (year, month, day) |
local function is_valid_date (year, month, day) |
||
local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
||
local month_length; |
local month_length; |
||
if not is_valid_year(year) then |
if not is_valid_year(year) then -- no farther into the future than next year |
||
return false; |
return false; |
||
end |
end |
||
if (2==month) then -- if February |
|||
month_length = 28; -- then 28 days unless |
|||
if 1582 > tonumber(year) then -- Julian calendar |
|||
month_length = 28; -- then 28 days unless |
|||
if 1582 > tonumber(year) then -- Julian calendar |
|||
if 0==(year%4) then |
if 0==(year%4) then |
||
month_length = 29; |
month_length = 29; |
||
end |
end |
||
else |
else -- Gregorian calendar |
||
if (0==(year%4) and (0~=(year%100) or 0==(year%400))) then |
if (0==(year%4) and (0~=(year%100) or 0==(year%400))) then -- is a leap year? |
||
month_length = 29; |
month_length = 29; -- if leap year then 29 days in February |
||
end |
end |
||
end |
end |
||
Line 199: | Line 183: | ||
local function is_valid_month_season_range(range_start, range_end) |
local function is_valid_month_season_range(range_start, range_end) |
||
local range_start_number = get_month_number (range_start); |
local range_start_number = get_month_number (range_start); |
||
local range_end_number; |
|||
if 0 == range_start_number then |
if 0 == range_start_number then -- is this a month range? |
||
local range_start_number = get_season_number (range_start); |
local range_start_number = get_season_number (range_start); -- not a month; is it a season? get start season number |
||
range_end_number = get_season_number (range_end); |
local range_end_number = get_season_number (range_end); -- get end season number |
||
if 0 ~= range_start_number then |
if 0 ~= range_start_number then -- is start of range a season? |
||
if range_start_number < range_end_number then |
if range_start_number < range_end_number then -- range_start is a season |
||
return true; |
return true; -- return true when range_end is also a season and follows start season; else false |
||
end |
end |
||
if |
if 4 == range_start_number and 1 == range_end_number then -- special case when range is Fall-Winter or Autumn-Winter |
||
return true; |
return true; |
||
end |
end |
||
Line 216: | Line 199: | ||
end |
end |
||
range_end_number = get_month_number (range_end); |
local range_end_number = get_month_number (range_end); -- get end month number |
||
if range_start_number < range_end_number then |
if range_start_number < range_end_number then -- range_start is a month; does range_start precede range_end? |
||
if is_valid_month_range_style (range_start, range_end) then -- do months have the same style? |
if is_valid_month_range_style (range_start, range_end) then -- do months have the same style? |
||
return true; -- proper order and same style |
return true; -- proper order and same style |
||
end |
end |
||
end |
end |
||
return false; |
return false; -- range_start month number is greater than or equal to range end number; or range end isn't a month |
||
end |
end |
||
--[[--------------------------< M A K E _ C O I N S _ D A T E >------------------------------------------------ |
|||
This function receives a table of date parts for one or two dates and an empty table reference declared in |
|||
Module:Citation/CS1. The function is called only for |date= parameters and only if the |date=<value> is |
|||
determined to be a valid date format. The question of what to do with invalid date formats is not answered here. |
|||
The date parts in the input table are converted to an ISO 8601 conforming date string: |
|||
single whole dates: yyyy-mm-dd |
|||
month and year dates: yyyy-mm |
|||
year dates: yyyy |
|||
ranges: yyyy-mm-dd/yyyy-mm-dd |
|||
yyyy-mm/yyyy-mm |
|||
yyyy/yyyy |
|||
Dates in the Julian calendar are reduced to year or year/year so that we don't have to do calendar conversion from |
|||
Julian to Proleptic Gregorian. |
|||
The input table has: |
|||
year, year2 – always present; if before 1582, ignore months and days if present |
|||
month, month2 – 0 if not provided, 1-12 for months, 21-24 for seasons; 31– proper name dates |
|||
day, day2 – 0 if not provided, 1-31 for days |
|||
the output table receives: |
|||
rftdate: an IS8601 formatted date |
|||
rftchron: a free-form version of the date, usually without year which is in rftdate (season ranges and propername dates) |
|||
rftssn: one of four season keywords: winter, spring, summer, fall (lowercase) |
|||
]] |
|||
local function make_COinS_date (input, tCOinS_date) |
|||
local date; -- one date or first date in a range |
|||
local date2 = ''; -- end of range date |
|||
if 1582 > tonumber(input.year) or 20 < tonumber(input.month) then -- Julian calendar or season so &rft.date gets year only |
|||
date = input.year; |
|||
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 |
|||
date = string.format ('%.4d/%.4d', tonumber(input.year), tonumber(input.year2)) -- assemble the date range |
|||
end |
|||
if 20 < tonumber(input.month) then -- if season or propername date |
|||
local season = {[21]='winter', [22]='spring', [23]='summer', [24]='fall', [31]='Christmas'}; -- seasons lowercase, no autumn; proper names use title case |
|||
if 0 == input.month2 then -- single season date |
|||
if 30 <tonumber(input.month) then |
|||
tCOinS_date.rftchron = season[input.month]; -- proper name dates |
|||
else |
|||
tCOinS_date.rftssn = season[input.month]; -- seasons |
|||
end |
|||
else -- season range with a second season specified |
|||
if input.year ~= input.year2 then -- season year – season year range or season year–year |
|||
tCOinS_date.rftssn = season[input.month]; -- start of range season; keep this? |
|||
if 0~= input.month2 then |
|||
tCOinS_date.rftchron = string.format ('%s %s – %s %s', season[input.month], input.year, season[input.month2], input.year2); |
|||
end |
|||
else -- season–season year range |
|||
tCOinS_date.rftssn = season[input.month]; -- start of range season; keep this? |
|||
tCOinS_date.rftchron = season[input.month] .. '–' .. season[input.month2]; -- season–season year range |
|||
end |
|||
end |
|||
end |
|||
tCOinS_date.rftdate = date; |
|||
return; -- done |
|||
end |
|||
if 0 ~= input.day then |
|||
date = string.format ('%s-%.2d-%.2d', input.year, tonumber(input.month), tonumber(input.day)); -- whole date |
|||
elseif 0 ~= input.month then |
|||
date = string.format ('%s-%.2d', input.year, tonumber(input.month)); -- year and month |
|||
else |
|||
date = string.format ('%s', input.year); -- just year |
|||
end |
|||
if 0 ~= input.year2 then |
|||
if 0 ~= input.day2 then |
|||
date2 = string.format ('/%s-%.2d-%.2d', input.year2, tonumber(input.month2), tonumber(input.day2)); -- whole date |
|||
elseif 0 ~= input.month2 then |
|||
date2 = string.format ('/%s-%.2d', input.year2, tonumber(input.month2)); -- year and month |
|||
else |
|||
date2 = string.format ('/%s', input.year2); -- just year |
|||
end |
|||
end |
|||
tCOinS_date.rftdate = date .. date2; -- date2 has the '/' separator |
|||
return; |
|||
end |
|||
--[[--------------------------< C H E C K _ D A T E >---------------------------------------------------------- |
--[[--------------------------< C H E C K _ D A T E >---------------------------------------------------------- |
||
Check date format to see that it is one of the formats approved by WP:DATESNO or WP:DATERANGE. Exception: only |
Check date format to see that it is one of the formats approved by WP:DATESNO or WP:DATERANGE. Exception: only allowed range separator is endash. |
||
Additionally, check the date to see that it is a real date: no 31 in 30-day months; no 29 February when not a leap year. Months, both long-form and three |
|||
character abbreviations, and seasons must be spelled correctly. Future years beyond next year are not allowed. |
|||
months; no 29 February when not a leap year. Months, both long-form and three character abbreviations, and seasons |
|||
must be spelled correctly. Future years beyond next year are not allowed. |
|||
If the date fails the format tests, this function returns false and does not return values for anchor_year and |
If the date fails the format tests, this function returns false and does not return values for anchor_year and COinS_date. When this happens, the date parameter is |
||
used in the COinS metadata and the CITEREF identifier gets its year from the year parameter if present otherwise CITEREF does not get a date value. |
|||
COinS_date. When this happens, the date parameter is used in the COinS metadata and the CITEREF identifier gets |
|||
its year from the year parameter if present otherwise CITEREF does not get a date value. |
|||
Inputs: |
Inputs: |
||
Line 329: | Line 224: | ||
true, anchor_year, COinS_date |
true, anchor_year, COinS_date |
||
anchor_year can be used in CITEREF anchors |
anchor_year can be used in CITEREF anchors |
||
COinS_date is |
COinS_date is date_string without anchor_year disambiguator if any |
||
]] |
]] |
||
local function check_date (date_string) |
|||
local function check_date (date_string, tCOinS_date) |
|||
local year; -- assume that year2, months, and days are not used; |
local year; -- assume that year2, months, and days are not used; |
||
local year2=0; -- second year in a year range |
local year2=0; -- second year in a year range |
||
Line 345: | Line 238: | ||
if date_string:match("^%d%d%d%d%-%d%d%-%d%d$") then -- year-initial numerical year month day format |
if date_string:match("^%d%d%d%d%-%d%d%-%d%d$") then -- year-initial numerical year month day format |
||
year, month, day=string.match(date_string, "(%d%d%d%d)%-(%d%d)%-(%d%d)"); |
year, month, day=string.match(date_string, "(%d%d%d%d)%-(%d%d)%-(%d%d)"); |
||
month=tonumber(month); |
|||
if 12 < tonumber(month) or 1 > tonumber(month) or 1582 > tonumber(year) or 0 == tonumber(day) then return false; end -- month or day number not valid or not Gregorian calendar |
|||
if 12 < month or 1 > month or 1583 > tonumber(year) then return false; end -- month number not valid or not Gregorian calendar |
|||
anchor_year = year; |
anchor_year = year; |
||
Line 358: | Line 252: | ||
month = get_month_number (month); |
month = get_month_number (month); |
||
if 0 == month then return false; end -- return false if month text isn't one of the twelve months |
if 0 == month then return false; end -- return false if month text isn't one of the twelve months |
||
month2=month; -- for metadata |
|||
year2=year; |
|||
elseif date_string:match("^[1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day-initial: day month year |
elseif date_string:match("^[1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day-initial: day month year |
||
Line 371: | Line 263: | ||
month = get_month_number (month); |
month = get_month_number (month); |
||
if 0 == month then return false; end -- return false if month text isn't one of the twelve months |
if 0 == month then return false; end -- return false if month text isn't one of the twelve months |
||
month2=month; -- for metadata |
|||
year2=year; |
|||
elseif date_string:match("^[1-9]%d? +%a+ – [1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day initial month-day-range: day month - day month year; uses spaced endash |
elseif date_string:match("^[1-9]%d? +%a+ – [1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day initial month-day-range: day month - day month year; uses spaced endash |
||
day, month, day2, month2, anchor_year, year=date_string:match("(%d%d?) +(%a+) – (%d%d?) +(%a+) +((%d%d%d%d)%a?)"); |
day, month, day2, month2, anchor_year, year=date_string:match("(%d%d?) +(%a+) – (%d%d?) +(%a+) +((%d%d%d%d)%a?)"); |
||
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; |
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; |
||
month = get_month_number (month); |
month = get_month_number (month); |
||
month2 = get_month_number (month2); |
month2 = get_month_number (month2); |
||
year2=year; |
|||
elseif date_string:match("^%a+ +[1-9]%d? – %a+ +[1-9]%d?, +[1-9]%d%d%d?%a?$") then -- month initial month-day-range: month day – month day, year; uses spaced endash |
elseif date_string:match("^%a+ +[1-9]%d? – %a+ +[1-9]%d?, +[1-9]%d%d%d?%a?$") then -- month initial month-day-range: month day – month day, year; uses spaced endash |
||
month, day, month2, day2, anchor_year, year=date_string:match("(%a+) +(%d%d?) – (%a+) +(%d%d?), +((%d%d%d%d)%a?)"); |
month, day, month2, day2, anchor_year, year=date_string:match("(%a+) +(%d%d?) – (%a+) +(%d%d?), +((%d%d%d%d)%a?)"); |
||
if (not is_valid_month_season_range(month, month2)) or not is_valid_year(year) then return false; end |
if (not is_valid_month_season_range(month, month2)) or not is_valid_year(year) then return false; end |
||
month = get_month_number (month); |
month = get_month_number (month); |
||
month2 = get_month_number (month2); |
month2 = get_month_number (month2); |
||
year2=year; |
|||
elseif date_string:match("^[1-9]%d? +%a+ +[1-9]%d%d%d – [1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day initial month-day-year-range: day month year - day month year; uses spaced endash |
elseif date_string:match("^[1-9]%d? +%a+ +[1-9]%d%d%d – [1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day initial month-day-year-range: day month year - day month year; uses spaced endash |
||
day, month, year, day2, month2, anchor_year, year2=date_string:match("(%d%d?) +(%a+) +(%d%d%d%d?) – (%d%d?) +(%a+) +((%d%d%d%d?)%a?)"); |
day, month, year, day2, month2, anchor_year, year2=date_string:match("(%d%d?) +(%a+) +(%d%d%d%d?) – (%d%d?) +(%a+) +((%d%d%d%d?)%a?)"); |
||
if tonumber(year2) <= tonumber(year) then return false; end |
if tonumber(year2) <= tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later |
||
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 |
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 |
||
month = get_month_number (month); |
month = get_month_number (month); |
||
month2 = get_month_number (month2); |
month2 = get_month_number (month2); |
||
elseif date_string:match("^%a+ +[1-9]%d?, +[1-9]%d%d%d – %a+ +[1-9]%d?, +[1-9]%d%d%d%a?$") then -- month initial month-day-year-range: month day, year – month day, year; uses spaced endash |
elseif date_string:match("^%a+ +[1-9]%d?, +[1-9]%d%d%d – %a+ +[1-9]%d?, +[1-9]%d%d%d%a?$") then -- month initial month-day-year-range: month day, year – month day, year; uses spaced endash |
||
month, day, year, month2, day2, anchor_year, year2=date_string:match("(%a+) +(%d%d?), +(%d%d%d%d) – (%a+) +(%d%d?), +((%d%d%d%d)%a?)"); |
month, day, year, month2, day2, anchor_year, year2=date_string:match("(%a+) +(%d%d?), +(%d%d%d%d) – (%a+) +(%d%d?), +((%d%d%d%d)%a?)"); |
||
if tonumber(year2) <= tonumber(year) then return false; end |
if tonumber(year2) <= tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later |
||
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 |
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 |
||
month = get_month_number (month); |
month = get_month_number (month); |
||
month2 = get_month_number (month2); |
month2 = get_month_number (month2); |
||
elseif date_string:match("^%a+ +[1-9]%d%d%d–%d%d%a?$") then -- special case Winter/Summer year-year (YYYY-YY); year separated with unspaced endash |
elseif date_string:match("^%a+ +[1-9]%d%d%d–%d%d%a?$") then -- special case Winter/Summer year-year (YYYY-YY); year separated with unspaced endash |
||
if nil == date_string:match("^Winter") and nil == date_string:match("^Summer") then return false end; -- 'month' can only be Winter or Summer |
|||
local century; |
local century; |
||
year, century, anchor_year, year2=date_string:match("%a+ +((%d%d)%d%d)–((%d%d)%a?)"); |
|||
if 'Winter' ~= month and 'Summer' ~= month then return false end; -- 'month' can only be Winter or Summer |
|||
anchor_year=year..'–'..anchor_year; -- assemble anchor_year from both years |
anchor_year=year..'–'..anchor_year; -- assemble anchor_year from both years |
||
year2 = century..year2; -- add the century to year2 for comparisons |
year2 = century..year2; -- add the century to year2 for comparisons |
||
if 1 ~= tonumber(year2) - tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later |
if 1 ~= tonumber(year2) - tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later |
||
if not is_valid_year(year2) then return false; end -- no year farther in the future than next year |
if not is_valid_year(year2) then return false; end -- no year farther in the future than next year |
||
month = get_season_number (month); |
|||
elseif date_string:match("^%a+ +[1-9]%d%d%d–[1-9]%d%d%d%a?$") then -- special case Winter/Summer year-year; year separated with unspaced endash |
elseif date_string:match("^%a+ +[1-9]%d%d%d–[1-9]%d%d%d%a?$") then -- special case Winter/Summer year-year; year separated with unspaced endash |
||
if nil == date_string:match("^Winter") and nil == date_string:match("^Summer") then return false end; -- 'month' can only be Winter or Summer |
|||
month, year, anchor_year, year2=date_string:match("(%a+) +(%d%d%d%d)–((%d%d%d%d)%a?)"); |
|||
year, anchor_year, year2=date_string:match("%a+ +(%d%d%d%d)–((%d%d%d%d)%a?)"); |
|||
if 'Winter' ~= month and 'Summer' ~= month then return false end; -- 'month' can only be Winter or Summer |
|||
anchor_year=year..'–'..anchor_year; -- assemble anchor_year from both years |
anchor_year=year..'–'..anchor_year; -- assemble anchor_year from both years |
||
if 1 ~= tonumber(year2) - tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later |
if 1 ~= tonumber(year2) - tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later |
||
if not is_valid_year(year2) then return false; end -- no year farther in the future than next year |
if not is_valid_year(year2) then return false; end -- no year farther in the future than next year |
||
month = get_season_number (month); -- for metadata |
|||
elseif date_string:match("^%a+ +[1-9]%d%d%d% – %a+ +[1-9]%d%d%d%a?$") then -- month/season year - month/season year; separated by spaced endash |
elseif date_string:match("^%a+ +[1-9]%d%d%d% – %a+ +[1-9]%d%d%d%a?$") then -- month/season year - month/season year; separated by spaced endash |
||
Line 425: | Line 311: | ||
if tonumber(year) >= tonumber(year2) then return false; end -- left to right, earlier to later, not the same |
if tonumber(year) >= tonumber(year2) then return false; end -- left to right, earlier to later, not the same |
||
if not is_valid_year(year2) then return false; end -- no year farther in the future than next year |
if not is_valid_year(year2) then return false; end -- no year farther in the future than next year |
||
if 0 ~= get_month_number(month) and 0 ~= get_month_number(month2) and is_valid_month_range_style(month, month2) |
if not((0 ~= get_month_number(month) and 0 ~= get_month_number(month2) and is_valid_month_range_style(month, month2)) or -- both must be month year, same month style |
||
(0 ~= get_season_number(month) and 0 ~= get_season_number(month2))) then return false; end -- or season year, not mixed |
|||
month = get_month_number(month); |
|||
month2 = get_month_number(month2); |
|||
elseif 0 ~= get_season_number(month) and 0 ~= get_season_number(month2) then -- both must be or season year, not mixed |
|||
month = get_season_number(month); |
|||
month2 = get_season_number(month2); |
|||
else |
|||
return false; |
|||
end |
|||
elseif date_string:match ("^%a+–%a+ +[1-9]%d%d%d%a?$") then -- month/season range year; months separated by endash |
elseif date_string:match ("^%a+–%a+ +[1-9]%d%d%d%a?$") then -- month/season range year; months separated by endash |
||
month, month2, anchor_year, year=date_string:match ("(%a+)–(%a+)%s*((%d%d%d%d)%a?)"); |
month, month2, anchor_year, year=date_string:match ("(%a+)–(%a+)%s*((%d%d%d%d)%a?)"); |
||
if (not is_valid_month_season_range(month, month2)) or (not is_valid_year(year)) then |
if (not is_valid_month_season_range(month, month2)) or (not is_valid_year(year)) then |
||
return false; |
|||
if 0 ~= get_month_number(month) then -- determined to be a valid range so just check this one to know if month or season |
|||
month = get_month_number(month); |
|||
month2 = get_month_number(month2); |
|||
else |
|||
month = get_season_number(month); |
|||
month2 = get_season_number(month2); |
|||
end |
end |
||
year2=year; |
|||
elseif date_string:match("^%a+ +%d%d%d%d%a?$") then -- month/season year or proper-name year |
elseif date_string:match("^%a+ +%d%d%d%d%a?$") then -- month/season year or proper-name year |
||
Line 451: | Line 324: | ||
if not is_valid_year(year) then return false; end |
if not is_valid_year(year) then return false; end |
||
if not is_valid_month_or_season (month) and 0 == is_proper_name (month) then return false; end |
if not is_valid_month_or_season (month) and 0 == is_proper_name (month) then return false; end |
||
if 0 ~= get_month_number(month) then -- determined to be a valid range so just check this one to know if month or season |
|||
month = get_month_number(month); |
|||
elseif 0 ~= get_season_number(month) then |
|||
month = get_season_number(month); |
|||
else |
|||
month = is_proper_name (month); -- must be proper name; not supported in COinS |
|||
end |
|||
elseif date_string:match("^[1-9]%d%d%d?–[1-9]%d%d%d?%a?$") then -- Year range: YYY-YYY or YYY-YYYY or YYYY–YYYY; separated by unspaced endash; 100-9999 |
elseif date_string:match("^[1-9]%d%d%d?–[1-9]%d%d%d?%a?$") then -- Year range: YYY-YYY or YYY-YYYY or YYYY–YYYY; separated by unspaced endash; 100-9999 |
||
Line 481: | Line 347: | ||
else |
else |
||
return false; |
return false; -- date format not one of the MOS:DATE approved formats |
||
end |
end |
||
local result=true; |
local result=true; -- check whole dates for validity; assume true because not all dates will go through this test |
||
if 0 ~= year and 0 ~= month and 0 ~= day and 0 == year2 and 0 == month2 and 0 == day2 then -- YMD (simple whole date) |
if 0 ~= year and 0 ~= month and 0 ~= day and 0 == year2 and 0 == month2 and 0 == day2 then -- YMD (simple whole date) |
||
result=is_valid_date(year,month,day); |
result=is_valid_date(year,month,day); |
||
Line 502: | Line 368: | ||
if false == result then return false; end |
if false == result then return false; end |
||
-- if here, then date_string is valid; get coins_date from date_string (leave CITEREF disambiguator) ... |
|||
coins_date=date_string:match("^(.+%d)%a?$"); -- last character of valid disambiguatable date is always a digit |
|||
if nil ~= tCOinS_date then -- this table only passed into this function when testing |date= parameter values |
|||
coins_date= mw.ustring.gsub(coins_date, "–", "-" ); -- ... and replace any ndash with a hyphen |
|||
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 |
|||
end |
|||
return true, anchor_year; |
return true, anchor_year, coins_date; -- format is good and date string represents a real date |
||
end |
end |
||
--[[--------------------------< D A T E S >-------------------------------------------------------------------- |
--[[--------------------------< D A T E S >-------------------------------------------------------------------- |
||
Line 522: | Line 386: | ||
]] |
]] |
||
--function p.dates(date_parameters_list) |
|||
local function dates(date_parameters_list) |
|||
local anchor_year; -- will return as nil if the date being tested is not |date= |
local anchor_year; -- will return as nil if the date being tested is not |date= |
||
local COinS_date; -- will return as nil if the date being tested is not |date= |
local COinS_date; -- will return as nil if the date being tested is not |date= |
||
local error_message = ""; |
local error_message = ""; |
||
local mismatch = 0; |
|||
local good_date = false; |
local good_date = false; |
||
for k, v in pairs(date_parameters_list) do -- for each date-holding parameter in the list |
for k, v in pairs(date_parameters_list) do -- for each date-holding parameter in the list |
||
if is_set(v) then -- if the parameter has a value |
if is_set(v) then -- if the parameter has a value |
||
if v:match("^c%. [1-9]%d%d%d?%a?$") then -- special case for c. year or with or without CITEREF disambiguator - only |date= and |year= |
if v:match("^c%. [1-9]%d%d%d?%a?$") then -- special case for c. year or with or without CITEREF disambiguator - only |date= and |year= |
||
local year = v:match("c%. ([1-9]%d%d%d?)%a?"); -- get the year portion so it can be tested |
local year = v:match("c%. ([1-9]%d%d%d?)%a?"); -- get the year portion so it can be tested |
||
if 'date'==k then |
if 'date'==k then |
||
anchor_year, COinS_date = v:match("((c%. [1-9]%d%d%d?)%a?)"); -- anchor year and COinS_date only from |date= parameter |
anchor_year, COinS_date = v:match("((c%. [1-9]%d%d%d?)%a?)"); -- anchor year and COinS_date only from |date= parameter |
||
Line 538: | Line 404: | ||
good_date = is_valid_year(year); |
good_date = is_valid_year(year); |
||
end |
end |
||
elseif 'date'==k then -- if the parameter is |date= |
elseif 'date'==k then -- if the parameter is |date= |
||
if v:match("^n%.d%.%a?") then -- if |date=n.d. with or without a CITEREF disambiguator |
if v:match("^n%.d%.%a?") then -- if |date=n.d. with or without a CITEREF disambiguator |
||
good_date, anchor_year, COinS_date = true, v:match("((n%.d%.)%a?)"); --"n.d."; no error when date parameter is set to no date |
good_date, anchor_year, COinS_date = true, v:match("((n%.d%.)%a?)"); --"n.d."; no error when date parameter is set to no date |
||
elseif v:match("^nd%a?$") then -- if |date=nd with or without a CITEREF disambiguator |
elseif v:match("^nd%a?$") then -- if |date=nd with or without a CITEREF disambiguator |
||
good_date, anchor_year, COinS_date = true, v:match("((nd)%a?)"); --"nd"; no error when date parameter is set to no date |
good_date, anchor_year, COinS_date = true, v:match("((nd)%a?)"); --"nd"; no error when date parameter is set to no date |
||
else |
else |
||
good_date, anchor_year, COinS_date = check_date (v |
good_date, anchor_year, COinS_date = check_date (v); -- go test the date |
||
end |
end |
||
elseif ' |
elseif 'accessdate'==k then -- if the parameter is |date= |
||
good_date = check_date (v); -- go test the date |
good_date = check_date (v); -- go test the date |
||
if true == good_date then -- if the date is a valid date |
if true == good_date then -- if the date is a valid date |
||
good_date = is_valid_accessdate (v); -- is Wikipedia start date < accessdate < tomorrow's date? |
good_date = is_valid_accessdate (v); -- is Wikipedia start date < accessdate < tomorrow's date? |
||
end |
end |
||
else -- any other date-holding parameter |
else -- any other date-holding parameter |
||
good_date = check_date (v); -- go test the date |
good_date = check_date (v); -- go test the date |
||
end |
end |
||
if false==good_date then -- assemble one error message so we don't add the tracking category multiple times |
if false==good_date then -- assemble one error message so we don't add the tracking category multiple times |
||
if is_set(error_message) then -- once we've added the first portion of the error message ... |
if is_set(error_message) then -- once we've added the first portion of the error message ... |
||
error_message=error_message .. ", "; -- ... add a comma space separator |
error_message=error_message .. ", "; -- ... add a comma space separator |
||
end |
end |
||
error_message=error_message .. "|" .. k .. "="; -- add the failed parameter |
error_message=error_message .. "|" .. k .. "="; -- add the failed parameter |
||
end |
end |
||
end |
end |
||
end |
end |
||
return anchor_year, error_message; |
return anchor_year, COinS_date, error_message, mismatch; -- and done |
||
end |
end |
||
--[[--------------------------< Y E A R _ D A T E _ C H E C K >------------------------------------------------ |
--[[--------------------------< Y E A R _ D A T E _ C H E C K >------------------------------------------------ |
||
Line 583: | Line 448: | ||
year = year_string:match ('(%d%d%d%d?)'); |
year = year_string:match ('(%d%d%d%d?)'); |
||
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 |
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 date and year required YYYY-MM-DD and YYYYx |
||
date1 = date_string:match ('(%d%d%d%d)'); |
date1 = date_string:match ('(%d%d%d%d)'); |
||
year = year_string:match ('(%d%d%d%d)'); |
year = year_string:match ('(%d%d%d%d)'); |
||
Line 592: | Line 457: | ||
end |
end |
||
elseif date_string:match ("%d%d%d%d?.-%d%d%d%d?") then -- any of the standard |
elseif date_string:match ("%d%d%d%d?.-%d%d%d%d?") then -- any of the standard formats of date with two three- or four-digit years |
||
date1, date2 = date_string:match ("(%d%d%d%d?).-(%d%d%d%d?)"); |
date1, date2 = date_string:match ("(%d%d%d%d?).-(%d%d%d%d?)"); |
||
if year ~= date1 and year ~= date2 then |
if year ~= date1 and year ~= date2 then |
||
Line 598: | Line 463: | ||
end |
end |
||
elseif date_string:match ("%d%d%d% |
elseif date_string:match ("%d%d%d%d[%s%-–]+%d%d") then -- YYYY-YY date ranges |
||
local century; |
local century; |
||
date1, century, date2 = date_string:match ("((%d%d)%d%d)[%s%-–]+(%d%d)"); |
date1, century, date2 = date_string:match ("((%d%d)%d%d)[%s%-–]+(%d%d)"); |
||
Line 611: | Line 476: | ||
result = 0; |
result = 0; |
||
end |
end |
||
else |
|||
result = 0; -- no recognizable year in date |
|||
end |
end |
||
return result; |
return result; |
||
end |
end |
||
return {dates = dates, year_date_check = year_date_check} -- return exported functions |
|||
--[[-------------------------< R E F O R M A T T A B L E S >------------------------------------------------ |
|||
These table are used exclusively for reformatting dates |
|||
]] |
|||
local source_patterns = { -- this table holds patterns that match allowed date formats used to extract date components |
|||
['dmy'] = '(%d%d?)%s+(%a+)%s+(%d%d%d%d)', |
|||
['mdy'] = '(%a+)%s+(%d%d?),%s+(%d%d%d%d)', |
|||
['ymd'] = '(%d%d%d%d)%-(%d%d)-(%d%d)', |
|||
} |
|||
local short_formats = { -- this table holds format strings used by os.date() for short month names |
|||
['dmy'] = '%e %b %Y', |
|||
['mdy'] = '%b %e, %Y', |
|||
['ymd'] = '%F', |
|||
} |
|||
local long_formats = { -- this table holds format strings used by os.date() for long month names |
|||
['dmy'] = '%e %B %Y', |
|||
['mdy'] = '%B %e, %Y', |
|||
['ymd'] = '%F', |
|||
} |
|||
--[[-------------------------< G E T _ D M Y _ D A T E _ P A R T S >------------------------------------------ |
|||
extracts year, month and day from DMY formatted date, places them in the source_date table, and returns. |
|||
]] |
|||
local function get_dmy_date_parts (date, source_date) |
|||
source_date.day, source_date.month, source_date.year = date:match (source_patterns['dmy']); -- get date components as strings |
|||
source_date.month = get_month_number (source_date.month); -- get month number |
|||
end |
|||
--[[-------------------------< G E T _ M D Y _ D A T E _ P A R T S >------------------------------------------ |
|||
extracts year, month and day from MDY formatted date, places them in the source_date table, and returns. |
|||
]] |
|||
local function get_mdy_date_parts (date, source_date) |
|||
source_date.month, source_date.day, source_date.year = date:match (source_patterns['mdy']); -- get date components as strings |
|||
source_date.month = get_month_number (source_date.month); -- get month number |
|||
end |
|||
--[[-------------------------< G E T _ Y M D _ D A T E _ P A R T S >------------------------------------------ |
|||
extracts year, month and day from YMD formatted date, places them in the source_date table, and returns. |
|||
]] |
|||
local function get_ymd_date_parts (date, source_date) |
|||
source_date.year, source_date.month, source_date.day = date:match (source_patterns['ymd']); -- get date components as strings |
|||
end |
|||
--[[-------------------------< R E F O R M A T _ D A T E S >-------------------------------------------------- |
|||
Reformats existing dates into the format specified by format and short. |
|||
format is one of several keywords: dmy, dmy-all, mdy, mdy-all, ymd, ymd-all. The all version includes access- and |
|||
archive-dates; otherwise these dates are not reformatted |
|||
Date ranges, season dates, proper name dates are not currently supported. |
|||
]] |
|||
local function reformat_dates (date_parameters_list, format, short) |
|||
local all = false; -- set to false to skip access- and archive-dates |
|||
local format_str; |
|||
local source_date = {}; |
|||
if format:match('%a+%-all') then |
|||
format = format:match('(%a+)%-all'); -- extract the format |
|||
all = true; -- set to true to format access- and archive-dates |
|||
end |
|||
for param_name, param_val in pairs(date_parameters_list) do -- for each date-holding parameter in the list |
|||
if is_set(param_val) then -- if the parameter has a value |
|||
if not all and in_array (param_name, {'access-date', 'archive-date'}) then -- if access- or archive-date and format not xxx-all |
|||
param_val = ''; -- set to empty string so we don't process this date |
|||
end |
|||
for source, pattern in pairs(source_patterns) do |
|||
if param_val:match(pattern) then |
|||
if 'ymd' == source then |
|||
get_ymd_date_parts (param_val, source_date); -- get the date parts into the source_date table |
|||
elseif 'dmy' == source then |
|||
get_dmy_date_parts (param_val, source_date); -- get the date parts into the source_date table |
|||
elseif 'mdy' == source then |
|||
get_mdy_date_parts (param_val, source_date); -- get the date parts into the source_date table |
|||
end |
|||
if 'ymd' == format and 1582 > tonumber(source_date.year) then -- ymd format dates not allowed before 1582 |
|||
return false; -- abandon reformatting |
|||
end |
|||
if short then |
|||
format_str = short_formats[format]; |
|||
else |
|||
format_str = long_formats[format]; |
|||
end |
|||
date_parameters_list[param_name] = os.date (format_str, os.time(source_date)); -- convert date and save |
|||
end |
|||
end |
|||
end |
|||
end |
|||
return true; -- declare success and done |
|||
end |
|||
--[[--------------------------< S E T _ S E L E C T E D _ M O D U L E S >-------------------------------------- |
|||
Sets local imported functions table to same (live or sandbox) as that used by the other modules. |
|||
]] |
|||
local function set_selected_modules (utilities_page_ptr) |
|||
is_set = utilities_page_ptr.is_set; -- import functions from select Module:Citation/CS1/Utilities module |
|||
in_array = utilities_page_ptr.in_array; -- import functions from select Module:Citation/CS1/Utilities module |
|||
end |
|||
return { -- return exported functions |
|||
dates = dates, |
|||
year_date_check = year_date_check, |
|||
reformat_dates = reformat_dates, |
|||
set_selected_modules = set_selected_modules |
|||
} |