Changes

Jump to navigation Jump to search
m
1 revision imported
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 functions from selected Module:Citation/CS1/Utilities
 +
local cfg; -- table of tables imported from slected Module:Citation/CS1/Configuration
 +
 
 +
--[=[-------------------------< I S _ V A L I D _ A C C E S S D A T E >----------------------------------------
    
returns true if:
 
returns true if:
Line 14: Line 21:  
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 if it is fed month names 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().  To get around that
 +
call this function with YYYY-MM-DD format dates.
 +
 
 +
]=]
    
local function is_valid_accessdate (accessdate)
 
local function is_valid_accessdate (accessdate)
Line 24: Line 36:  
good2, tomorrow_ts = pcall( lang.formatDate, lang, 'U', 'today + 2 days' ); -- today midnight + 2 days is one second more than all day tomorrow
 
good2, tomorrow_ts = pcall( lang.formatDate, lang, 'U', 'today + 2 days' ); -- today midnight + 2 days is one second more than all day tomorrow
 
 
if good1 and good2 then
+
if good1 and good2 then -- lang.formatDate() returns a timestamp in the local script which which tonumber() may not understand
access_ts = tonumber (access_ts); -- convert to numbers for the comparison
+
access_ts = tonumber (access_ts) or lang:parseFormattedNumber (access_ts); -- convert to numbers for the comparison;
tomorrow_ts = tonumber (tomorrow_ts);
+
tomorrow_ts = tonumber (tomorrow_ts) or lang:parseFormattedNumber (tomorrow_ts);
 
else
 
else
 
return false; -- one or both failed to convert to unix time stamp
 
return false; -- one or both failed to convert to unix time stamp
 
end
 
end
+
 
 
if 979516800 <= access_ts and access_ts < tomorrow_ts then -- Wikipedia start date <= accessdate < tomorrow's date
 
if 979516800 <= access_ts and access_ts < tomorrow_ts then -- Wikipedia start date <= accessdate < tomorrow's date
 
return true;
 
return true;
Line 37: Line 49:  
end
 
end
 
end
 
end
 +
    
--[[--------------------------< G E T _ M O N T H _ N U M B E R >----------------------------------------------
 
--[[--------------------------< G E T _ M O N T H _ N U M B E R >----------------------------------------------
Line 45: Line 58:     
local function get_month_number (month)
 
local function get_month_number (month)
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};
+
return cfg.date_names['local'].long[month] or cfg.date_names['local'].short[month] or -- look for local names first
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};
+
cfg.date_names['en'].long[month] or cfg.date_names['en'].short[month] or -- failing that, look for English names
local temp;
+
0; -- not a recognized month name
temp=long_months[month];
+
end
if temp then return temp; end -- if month is the long-form name
+
 
temp=short_months[month];
+
 
if temp then return temp; end -- if month is the short-form name
+
--[[--------------------------< I S _ V A L I D _ E M B A R G O _ D A T E >------------------------------------
return 0; -- misspelled, improper case, or not a month name
+
 
 +
returns true and date value if that value has proper dmy, mdy, ymd format.
 +
 
 +
returns false and 9999 (embargoed forever) when date value is not proper format; assumes that when |embargo= is
 +
set, the editor intended to embargo a pmc but |embargo= does not hold a single date.
 +
 
 +
]]
 +
 
 +
local function is_valid_embargo_date (v)
 +
if v:match ('^%d%d%d%d%-%d%d%-%d%d$') or -- ymd
 +
v:match ('^%d%d?%s+%a+%s+%d%d%d%d$') or -- dmy
 +
v:match ('^%a+%s+%d%d?%s*,%s*%d%d%d%d$') then -- mdy
 +
return true, v;
 +
end
 +
return false, '9999'; -- if here not good date so return false and set embargo date to long time in future
 
end
 
end
 +
    
--[[--------------------------< G E T _ S E A S O N _ N U M B E R >--------------------------------------------
 
--[[--------------------------< G E T _ S E A S O N _ N U M B E R >--------------------------------------------
    
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
 
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
 +
 +
Uses ISO DIS 8601 2016 part 2 §4.7  Divisions of a year for hemishpere-independent seasons:
 +
21-24 = Spring, Summer, Autumn, Winter, independent of “Hemisphere”
 +
 +
These additional divisions not currently supported:
 +
25-28 = Spring - Northern Hemisphere, Summer- Northern Hemisphere, Autumn - Northern Hemisphere, Winter - Northern Hemisphere
 +
29-32 = Spring – Southern Hemisphere, Summer– Southern Hemisphere, Autumn – Southern Hemisphere, Winter - Southern Hemisphere
 +
33-36 = Quarter 1, Quarter 2, Quarter 3, Quarter 4 (3 months each)
 +
37-39 = Quadrimester 1, Quadrimester 2, Quadrimester 3 (4 months each)
 +
40-41 = Semestral 1, Semestral-2 (6 months each)
 +
    
]]
 
]]
    
local function get_season_number (season)
 
local function get_season_number (season)
local season_list = {['Winter']=21, ['Spring']=22, ['Summer']=23, ['Fall']=24, ['Autumn']=24}; -- make sure these numbers do not overlap month numbers
+
return cfg.date_names['local'].season[season] or -- look for local names first
local temp;
+
cfg.date_names['en'].season[season] or -- failing that, look for English names
temp=season_list[season];
+
0; -- not a recognized season name
if temp then return temp; end -- if season is a valid name return its number
  −
return 0; -- misspelled, improper case, or not a season name
   
end
 
end
   Line 76: Line 113:     
local function is_proper_name (name)
 
local function is_proper_name (name)
local name_list = {['Christmas']=31}
+
return cfg.date_names['local'].named[name] or -- look for local names dates first
local temp;
+
cfg.date_names['en'].named[name] or -- failing that, look for English names
temp=name_list[name];
+
0; -- not a recognized named date
if temp then return temp; end -- if name is a valid name return its number
  −
return 0; -- misspelled, improper case, or not a proper name
   
end
 
end
   Line 103: Line 138:     
]]
 
]]
 
+
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 181: Line 216:  
Month pairs are expected to be left to right, earliest to latest in time.
 
Month pairs are expected to be left to right, earliest to latest in time.
   −
Similarly, seasons are also left to right, earliest to latest in time.  There is an oddity with seasons: winter is assigned a value of 1, spring 2, ...,
+
All season ranges are accepted as valid because there are publishers out there who have published a Summer–Spring YYYY issue so ... ok
fall and autumn 4. Because winter can follow fall/autumn at the end of a calender year, a special test is made to see if |date=Fall-Winter yyyy (4-1) is the date.
      
]]
 
]]
Line 188: Line 222:  
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 -- is this a month range?
 
if 0 == range_start_number then -- is this a month range?
local range_start_number = get_season_number (range_start); -- not a month; is it a season? get start season number
+
range_start_number = get_season_number (range_start); -- not a month; is it a season? get start season number
local range_end_number = get_season_number (range_end); -- get end season number
+
range_end_number = get_season_number (range_end); -- get end season number
   −
if 0 ~= range_start_number then -- is start of range a season?
+
if (0 ~= range_start_number) and (0 ~= range_end_number) then
if range_start_number < range_end_number then -- range_start is a season
+
return true; -- any season pairing is accepted
return true; -- return true when range_end is also a season and follows start season; else false
  −
end
  −
if 24 == range_start_number and 21 == range_end_number then -- special case when season range is Fall-Winter or Autumn-Winter
  −
return true;
  −
end
   
end
 
end
return false; -- range_start is not a month or a season; or range_start is a season and range_end is not; or improper season sequence
+
return false; -- range_start and/or range_end is not a season
 
end
 
end
 
+
-- here when range_start is a month
local range_end_number = get_month_number (range_end); -- get end month number
+
range_end_number = get_month_number (range_end); -- get end month number
 
if range_start_number < range_end_number then -- range_start is a month; does range_start precede range_end?
 
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?
Line 233: Line 263:  
The input table has:
 
The input table has:
 
year, year2 – always present; if before 1582, ignore months and days if present
 
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
+
month, month2 – 0 if not provided, 1-12 for months, 21-24 for seasons; 99 Christmas
 
day, day2 –  0 if not provided, 1-31 for days
 
day, day2 –  0 if not provided, 1-31 for days
 
 
Line 246: Line 276:  
local date; -- one date or first date in a range
 
local date; -- one date or first date in a range
 
local date2 = ''; -- end of range date
 
local date2 = ''; -- end of range date
 +
-- start temporary Julian / Gregorian calendar uncertainty detection
 +
local year = tonumber(input.year); -- this temporary code to determine the extent of sources dated to the Julian/Gregorian
 +
local month = tonumber(input.month); -- interstice 1 October 1582 – 1 January 1926
 +
local day = tonumber (input.day);
 +
if (0 ~= day) and -- day must have a value for this to be a whole date
 +
(((1582 == year) and (10 <= month) and (12 >= month)) or -- any whole 1582 date from 1 october to 31 December or
 +
((1926 == year) and (1 == month) and (1 == input.day)) or -- 1 January 1926 or
 +
((1582 < year) and (1925 >= year))) then -- any date 1 January 1583 – 31 December 1925
 +
tCOinS_date.inter_cal_cat = true; -- set category flag true
 +
end
 +
-- end temporary Julian / Gergorian calendar uncertainty detection
 
 
 
if 1582 > tonumber(input.year) or 20 < tonumber(input.month) then -- Julian calendar or season so &rft.date gets year only
 
if 1582 > tonumber(input.year) or 20 < tonumber(input.month) then -- Julian calendar or season so &rft.date gets year only
Line 253: Line 294:  
end
 
end
 
if 20 < tonumber(input.month) then -- if season or propername date
 
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
+
local season = {[24]='winter', [21]='spring', [22]='summer', [23]='fall', [99]='Christmas'}; -- seasons lowercase, no autumn; proper names use title case
 
if 0 == input.month2 then -- single season date
 
if 0 == input.month2 then -- single season date
 
if 30 <tonumber(input.month) then
 
if 30 <tonumber(input.month) then
Line 263: Line 304:  
if input.year ~= input.year2 then -- season year – season year range or season year–year
 
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?
 
tCOinS_date.rftssn = season[input.month]; -- start of range season; keep this?
if 0~= month2 then
+
if 0~= input.month2 then
 
tCOinS_date.rftchron = string.format ('%s %s – %s %s', season[input.month], input.year, season[input.month2], input.year2);
 
tCOinS_date.rftchron = string.format ('%s %s – %s %s', season[input.month], input.year, season[input.month2], input.year2);
 
end
 
end
Line 321: Line 362:  
]]
 
]]
   −
local function check_date (date_string, tCOinS_date)
+
local function check_date (date_string, tCOinS_date, test_accessdate)
 
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 333: Line 374:  
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)");
if 12 < tonumber(month) or 1 > tonumber(month) or 1583 > tonumber(year) then return false; end -- month number not valid or not Gregorian calendar
+
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
 
anchor_year = year;
 
anchor_year = year;
 
+
elseif mw.ustring.match(date_string, "^%D- +[1-9]%d?, +[1-9]%d%d%d%a?$") then -- month-initial: month day, year
elseif date_string:match("^%a+ +[1-9]%d?, +[1-9]%d%d%d%a?$") then -- month-initial: month day, year
+
month, day, anchor_year, year=mw.ustring.match(date_string, "(%D-) +(%d%d?),%s*((%d%d%d%d?)%a?)");
month, day, anchor_year, year=string.match(date_string, "(%a+)%s*(%d%d?),%s*((%d%d%d%d)%a?)");
   
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
 
 
elseif date_string:match("^%a+ +[1-9]%d?–[1-9]%d?, +[1-9]%d%d%d%a?$") then -- month-initial day range: month day–day, year; days are separated by endash
+
elseif mw.ustring.match(date_string, "^%D- +[1-9]%d?[%-][1-9]%d?, +[1-9]%d%d%d%a?$") then -- month-initial day range: month day–day, year; days are separated by endash
month, day, day2, anchor_year, year=string.match(date_string, "(%a+) +(%d%d?)–(%d%d?), +((%d%d%d%d)%a?)");
+
month, day, day2, anchor_year, year=mw.ustring.match(date_string, "(%D-) +(%d%d?)[%-](%d%d?), +((%d%d%d%d)%a?)");
 
if tonumber(day) >= tonumber(day2) then return false; end -- date range order is left to right: earlier to later; dates may not be the same;
 
if tonumber(day) >= tonumber(day2) then return false; end -- date range order is left to right: earlier to later; dates may not be the same;
 
month = get_month_number (month);
 
month = get_month_number (month);
Line 349: Line 389:  
year2=year;
 
year2=year;
   −
elseif date_string:match("^[1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day-initial: day month year
+
elseif mw.ustring.match(date_string, "^[1-9]%d? +%D- +[1-9]%d%d%d%a?$") then -- day-initial: day month year
day, month, anchor_year, year=string.match(date_string, "(%d%d*)%s*(%a+)%s*((%d%d%d%d)%a?)");
+
day, month, anchor_year, year=mw.ustring.match(date_string, "(%d%d*)%s*(%D-) +((%d%d%d%d?)%a?)");
 
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
   −
elseif date_string:match("^[1-9]%d?–[1-9]%d? +%a+ +[1-9]%d%d%d%a?$") then -- day-range-initial: day–day month year; days are separated by endash
+
elseif mw.ustring.match(date_string, "^[1-9]%d?[%-][1-9]%d? +%D- +[1-9]%d%d%d%a?$") then -- day-range-initial: day–day month year; days are separated by endash
day, day2, month, anchor_year, year=string.match(date_string, "(%d%d?)–(%d%d?) +(%a+) +((%d%d%d%d)%a?)");
+
day, day2, month, anchor_year, year=mw.ustring.match(date_string, "(%d%d?)[%-](%d%d?) +(%D-) +((%d%d%d%d)%a?)");
 
if tonumber(day) >= tonumber(day2) then return false; end -- date range order is left to right: earlier to later; dates may not be the same;
 
if tonumber(day) >= tonumber(day2) then return false; end -- date range order is left to right: earlier to later; dates may not be the same;
 
month = get_month_number (month);
 
month = get_month_number (month);
Line 362: Line 402:  
year2=year;
 
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 mw.ustring.match(date_string, "^[1-9]%d? +%D- +[%-] +[1-9]%d? +%D- +[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=mw.ustring.match(date_string, "(%d%d?) +(%D-) +[%-] +(%d%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 -- 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); -- for metadata
 
month = get_month_number (month); -- for metadata
Line 369: Line 409:  
year2=year;
 
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 mw.ustring.match(date_string, "^%D- +[1-9]%d? +[%-] +%D- +[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=mw.ustring.match(date_string, "(%D-) +(%d%d?) +[%-] +(%D-) +(%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); -- for metadata
 
month = get_month_number (month); -- for metadata
Line 376: Line 416:  
year2=year;
 
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 mw.ustring.match(date_string, "^[1-9]%d? +%D- +[1-9]%d%d%d +[%-] +[1-9]%d? +%D- +[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=mw.ustring.match(date_string, "(%d%d?) +(%D-) +(%d%d%d%d) +[%-] +(%d%d?) +(%D-) +((%d%d%d%d)%a?)");
 
if tonumber(year2) <= tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later
 
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
Line 383: Line 423:  
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 mw.ustring.match(date_string, "^%D- +[1-9]%d?, +[1-9]%d%d%d +[%-] +%D- +[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=mw.ustring.match(date_string, "(%D-) +(%d%d?), +(%d%d%d%d) +[%-] +(%D-) +(%d%d?), +((%d%d%d%d)%a?)");
 
if tonumber(year2) <= tonumber(year) then return false; end -- must be sequential years, left to right, earlier to later
 
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
Line 390: Line 430:  
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 mw.ustring.match(date_string, "^%D- +[1-9]%d%d%d[%-–]%d%d%a?$") then -- special case Winter/Summer year-year (YYYY-YY); year separated with unspaced endash
 
local century;
 
local century;
month, year, century, anchor_year, year2=date_string:match("(%a+) +((%d%d)%d%d)–((%d%d)%a?)");
+
month, year, century, anchor_year, year2=mw.ustring.match(date_string, "(%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
 
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
Line 400: Line 440:  
month = get_season_number (month);
 
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 mw.ustring.match(date_string, "^%D- +[1-9]%d%d%d[%-–][1-9]%d%d%d%a?$") then -- special case Winter/Summer year-year; year separated with unspaced endash
month, year, anchor_year, year2=date_string:match("(%a+) +(%d%d%d%d)–((%d%d%d%d)%a?)");
+
month, year, anchor_year, year2=mw.ustring.match(date_string, "(%D-) +(%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
 
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
Line 408: Line 448:  
month = get_season_number (month); -- for metadata
 
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 mw.ustring.match(date_string, "^%D- +[1-9]%d%d%d +[%-] +%D- +[1-9]%d%d%d%a?$") then -- month/season year - month/season year; separated by spaced endash
month, year, month2, anchor_year, year2=date_string:match("(%a+) +(%d%d%d%d) – (%a+) +((%d%d%d%d)%a?)");
+
month, year, month2, anchor_year, year2=mw.ustring.match(date_string, "(%D-) +(%d%d%d%d) +[%-] +(%D-) +((%d%d%d%d)%a?)");
 
anchor_year=year..'–'..anchor_year; -- assemble anchor_year from both years
 
anchor_year=year..'–'..anchor_year; -- assemble anchor_year from both years
 
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
Line 423: Line 463:  
end
 
end
   −
elseif date_string:match ("^%a+–%a+ +[1-9]%d%d%d%a?$") then -- month/season range year; months separated by endash  
+
elseif mw.ustring.match(date_string, "^%D-[%-]%D- +[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=mw.ustring.match(date_string, "(%D-)[%-](%D-)%s*((%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
 
if 0 ~= get_month_number(month) then -- determined to be a valid range so just check this one to know if month or season
 
if 0 ~= get_month_number(month) then -- determined to be a valid range so just check this one to know if month or season
Line 435: Line 475:  
year2=year;
 
year2=year;
 
 
elseif date_string:match("^%a+ +%d%d%d%d%a?$") then -- month/season year or proper-name year
+
elseif mw.ustring.match(date_string, "^%D- +%d%d%d%d%a?$") then -- month/season year or proper-name year
month, anchor_year, year=date_string:match("(%a+)%s*((%d%d%d%d)%a?)");
+
month, anchor_year, year=mw.ustring.match(date_string, "(%D-)%s*((%d%d%d%d)%a?)");
 
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
Line 447: Line 487:  
end
 
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 mw.ustring.match(date_string, "^[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
year, anchor_year, year2=date_string:match("(%d%d%d%d?)–((%d%d%d%d?)%a?)");
+
year, anchor_year, year2=mw.ustring.match(date_string, "(%d%d%d%d?)[%-]((%d%d%d%d?)%a?)");
 
anchor_year=year..'–'..anchor_year; -- assemble anchor year from both years
 
anchor_year=year..'–'..anchor_year; -- assemble anchor year from both years
 
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
   −
elseif date_string:match("^[1-9]%d%d%d–%d%d%a?$") then -- Year range: YYYY–YY; separated by unspaced endash
+
elseif mw.ustring.match(date_string, "^[1-9]%d%d%d[%-–]%d%d%a?$") then -- Year range: YYYY–YY; separated by unspaced endash
 
local century;
 
local century;
year, century, anchor_year, year2=date_string:match("((%d%d)%d%d)–((%d%d)%a?)");
+
year, century, anchor_year, year2=mw.ustring.match(date_string, "((%d%d)%d%d)[%-]((%d%d)%a?)");
 
anchor_year=year..'–'..anchor_year; -- assemble anchor year from both years
 
anchor_year=year..'–'..anchor_year; -- assemble anchor year from both years
 
if 13 > tonumber(year2) then return false; end -- don't allow 2003-05 which might be May 2003
 
if 13 > tonumber(year2) then return false; end -- don't allow 2003-05 which might be May 2003
Line 470: Line 510:  
else
 
else
 
return false; -- date format not one of the MOS:DATE approved formats
 
return false; -- date format not one of the MOS:DATE approved formats
 +
end
 +
 +
if test_accessdate then -- test accessdate here because we have numerical date parts
 +
if 0 ~= year and 0 ~= month and 0 ~= day and -- all parts of a single date required
 +
0 == year2 and 0 == month2 and 0 == day2 then -- none of these; accessdate must not be a range
 +
if not is_valid_accessdate (year..'-'..month..'-'..day) then
 +
return false; -- return false when accessdate out of bounds
 +
end
 +
else
 +
return false; -- return false when accessdate is a range of two dates
 +
end
 
end
 
end
   Line 513: Line 564:  
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 embargo_date; -- if embargo date is a good dmy, mdy, ymd date then holds original value else reset to 9999
 
local error_message = "";
 
local error_message = "";
 
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.val) 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=
+
v.val = mw.ustring.gsub (v.val, '%d', cfg.date_names.local_digits); -- translate 'local' digits to Western 0-9
local year = v:match("c%. ([1-9]%d%d%d?)%a?"); -- get the year portion so it can be tested
+
if v.val: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.val: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.val:match("((c%. [1-9]%d%d%d?)%a?)"); -- anchor year and COinS_date only from |date= parameter
 
good_date = is_valid_year(year);
 
good_date = is_valid_year(year);
 
elseif 'year'==k then
 
elseif 'year'==k then
Line 527: Line 580:  
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.val: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.val: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.val: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.val: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, tCOinS_date); -- go test the date
+
good_date, anchor_year, COinS_date = check_date (v.val, tCOinS_date); -- go test the date
 +
end
 +
elseif 'year'==k then -- if the parameter is |year= it should hold only a year value
 +
if v.val:match("^[1-9]%d%d%d?%a?$") then -- if |year= 3 or 4 digits only with or without a CITEREF disambiguator
 +
good_date, anchor_year, COinS_date = true, v.val:match("((%d+)%a?)");
 
end
 
end
 
elseif 'access-date'==k then -- if the parameter is |date=
 
elseif 'access-date'==k then -- if the parameter is |date=
good_date = check_date (v); -- go test the date
+
good_date = check_date (v.val, nil, true); -- go test the date; nil is a placeholder; true is the test_accessdate flag
 +
elseif 'embargo'==k then -- if the parameter is |embargo=
 +
good_date = check_date (v.val); -- 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, 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
 
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.val); -- 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
Line 546: Line 605:  
error_message=error_message .. ", "; -- ... add a comma space separator
 
error_message=error_message .. ", "; -- ... add a comma space separator
 
end
 
end
error_message=error_message .. "&#124;" .. k .. "="; -- add the failed parameter
+
error_message=error_message .. "&#124;" .. v.name .. "="; -- add the failed parameter
 
end
 
end
 
end
 
end
 
end
 
end
return anchor_year, error_message; -- and done
+
return anchor_year, embargo_date, error_message; -- and done
 
end
 
end
   Line 571: Line 630:  
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 date and year required YYYY-MM-DD and YYYYx
+
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
 
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 580: Line 639:  
end
 
end
 
 
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
+
elseif date_string:match ("%d%d%d%d?.-%d%d%d%d?") then -- any of the standard range 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 586: Line 645:  
end
 
end
   −
elseif date_string:match ("%d%d%d%d[%s%-–]+%d%d") then -- YYYY-YY date ranges
+
elseif mw.ustring.match(date_string, "%d%d%d%d[%-–]%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 = mw.ustring.match(date_string, "((%d%d)%d%d)[%-–]+(%d%d)");
 
date2 = century..date2; -- convert YY to YYYY
 
date2 = century..date2; -- convert YY to YYYY
 
if year ~= date1 and year ~= date2 then
 
if year ~= date1 and year ~= date2 then
Line 599: Line 658:  
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.
 +
 
 +
For i18n:  This code works only at en.wiki because os.date() doesn't support any languages other than English.
 +
mw.getContentLanguage():formatDate() will work at non-English wikis only when the date format is yyyy-mm-dd. This is
 +
the same issue that plagues is_valid_accessdate()
 +
 
 +
It is possible that a solution like that written for ht:Module:Citation/CS1/Date_validation date_name_xlate() could be applied to this problem
 +
 
 +
]]
 +
 
 +
local function reformat_dates (date_parameters_list, format, short)
 +
local all = false; -- set to false to skip access- and archive-dates
 +
local result = false;
 +
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.val) then -- if the parameter has a value
 +
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
 +
for source, pattern in pairs (source_patterns) do
 +
if param_val.val:match (pattern) then
 +
if 'ymd' == source then
 +
get_ymd_date_parts (param_val.val, source_date); -- get the date parts into the source_date table
 +
elseif 'dmy' == source then
 +
get_dmy_date_parts (param_val.val, source_date); -- get the date parts into the source_date table
 +
elseif 'mdy' == source then
 +
get_mdy_date_parts (param_val.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
 +
-- convert date and save;
 +
date_parameters_list[param_name].val = mw.text.trim (os.date (format_str, os.time(source_date))); -- strip leading space when single digit day and %e is first format
 +
result = true;
 +
end -- if
 +
end -- for
 +
end -- if
 +
end -- if
 +
end -- for
 +
return result; -- declare result and done
 +
end
 +
 
 +
 
 +
--[[--------------------------< D A T E _ H Y P H E N _ T O _ D A S H >----------------------------------------
 +
 
 +
Loops through the list of date-holding parameters and converts any hyphen to an ndash.  Not called if the cs1|2
 +
template has any date errors.
 +
 
 +
Modifies the date_parameters_list and returns true if hyphens are replaced, else returns false.
 +
 
 +
]]
 +
 
 +
local function date_hyphen_to_dash (date_parameters_list)
 +
local result = false;
 +
local n;
 +
for param_name, param_val in pairs(date_parameters_list) do -- for each date-holding parameter in the list
 +
if is_set (param_val.val) then
 +
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)
 +
param_val.val, n = param_val.val:gsub ('%-', '–'); -- replace any hyphen with ndash
 +
if 0 ~= n then
 +
date_parameters_list[param_name].val = param_val.val; -- update the list
 +
result = true;
 +
end
 +
end
 +
end
 +
end
 +
return result; -- so we know if any hyphens were replaced
 +
end
 +
 
 +
 
 +
--[[-------------------------< D A T E _ N A M E _ X L A T E >------------------------------------------------
 +
 
 +
Attempts to translate English month names to local-language month names using names supplied by MediaWiki's
 +
date parser function.  This is simple name-for-name replacement and may not work for all languages.
 +
 
 +
if xlat_dig is true, this function will also translate western (English) digits to the local language's digits.
 +
This will also translate ymd dates.
 +
 
 +
]]
 +
 
 +
local function date_name_xlate (date_parameters_list, xlt_dig)
 +
local xlate;
 +
local mode; -- long or short month names
 +
local modified = false;
 +
local date;
 +
 +
for param_name, param_val in pairs(date_parameters_list) do -- for each date-holding parameter in the list
 +
if is_set(param_val.val) then -- if the parameter has a value
 +
date = param_val.val;
 +
for month in mw.ustring.gmatch (date, '%a+') do -- iterate through all dates in the date (single date or date range)
 +
if cfg.date_names.en.long[month] then
 +
mode = 'F'; -- English name is long so use long local name
 +
elseif cfg.date_names.en.short[month] then
 +
mode = 'M'; -- English name is short so use short local name
 +
else
 +
mode = nil; -- not an English month name; could be local language month name or an English season name
 +
end
 +
 +
if mode then -- might be a season
 +
xlate = mw.getContentLanguage():formatDate(mode, '1' .. month); -- translate the month name to this local language
 +
date = mw.ustring.gsub (date, month, xlate); -- replace the English with the translation
 +
date_parameters_list[param_name].val = date; -- save the translated date
 +
modified = true;
 +
end
 +
end
 +
 
 +
if xlt_dig then -- shall we also translate digits?
 +
date = date:gsub ('%d', cfg.date_names.xlate_digits); -- translate digits from western to 'local digits'
 +
date_parameters_list[param_name].val = date; -- save the translated date
 +
modified = true;
 +
end
 +
end
 +
end
 +
 +
return modified;
 +
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 (cfg_table_ptr, utilities_page_ptr)
 +
is_set = utilities_page_ptr.is_set; -- import functions from selected Module:Citation/CS1/Utilities module
 +
in_array = utilities_page_ptr.in_array; -- import functions from selected Module:Citation/CS1/Utilities module
 +
cfg = cfg_table_ptr; -- import tables from selected Module:Citation/CS1/Configuration
 +
end
 +
 
 +
 
 +
 
 +
return { -- return exported functions
 +
dates = dates,
 +
year_date_check = year_date_check,
 +
reformat_dates = reformat_dates,
 +
date_hyphen_to_dash = date_hyphen_to_dash,
 +
date_name_xlate = date_name_xlate,
 +
set_selected_modules = set_selected_modules
 +
}

Navigation menu