Module:Infobox/dates and Module:Infobox/dates/sandbox: Difference between pages
(Difference between pages)
imported>Gonnym added args.airdate_overall |
imported>Gonnym No edit summary |
||
| Line 1: | Line 1: | ||
-- This module provides functions to format date ranges according to [[MOS:DATERANGE]]. | |||
local p = {} | |||
local getArgs = require('Module:Arguments').getArgs | local getArgs = require('Module:Arguments').getArgs | ||
local | -- Define constants for reuse throughout the module | ||
local DASH = '–' -- en dash | |||
local DASH_BREAK = ' –<br />' -- en dash with line break | |||
local DEFAULT_ERROR_CATEGORY = 'Pages with incorrectly formatted date ranges' | |||
local 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 | |||
} | |||
-- ============================================= | |||
-- Template validation | |||
-- ============================================= | |||
-- Template should be moved eventually to the infobox television season module. | |||
--- Validates date formats in infobox templates. | |||
-- @param frame Frame object from Wikipedia | |||
-- @return Error category string if validation fails, nil otherwise | |||
function p.start_end_date_template_validation(frame) | function p.start_end_date_template_validation(frame) | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local error_category = args.error_category or | local error_category = args.error_category or DEFAULT_ERROR_CATEGORY | ||
local start_date = args.first_aired or args.released or args.airdate or args.release_date or args.airdate_overall | local start_date = args.first_aired or args.released or args.airdate or args.release_date or args.airdate_overall | ||
| Line 22: | Line 39: | ||
end | end | ||
end | end | ||
return nil -- Return nil if validation passes | |||
end | |||
-- ============================================= | |||
-- Helper functions | |||
-- ============================================= | |||
--- Replace non-breaking spaces with regular spaces. | |||
-- @param value String to process | |||
-- @return Processed string with regular spaces | |||
local function replace_space(value) | |||
if value then | |||
return value:gsub(" ", " ") | |||
end | |||
return value | |||
end | |||
--- Extract the hidden span portion from text if it exists. | |||
-- @param text Input text that may contain a span element | |||
-- @return The span portion or empty string | |||
local function extract_span(text) | |||
if not text then | |||
return "" | |||
end | |||
local span_start = string.find(text, "<span") | |||
if span_start then | |||
return string.sub(text, span_start) | |||
end | |||
return "" | |||
end | |||
--- Extract visible part (before any span). | |||
-- @param text Input text | |||
-- @return Visible portion of the text | |||
local function extract_visible(text) | |||
if not text then | |||
return "" | |||
end | |||
return text:match("^(.-)<span") or text | |||
end | |||
--- Parse date components from visible text. | |||
-- @param visible_text The visible portion of a date string | |||
-- @return Table with date components and format | |||
local function parse_date(visible_text) | |||
if not visible_text then | |||
return { | |||
prefix = "", | |||
month = nil, | |||
day = nil, | |||
year = nil, | |||
suffix = "", | |||
format = nil | |||
} | |||
end | |||
local date_format = "mdy" -- Default format | |||
local prefix, month, day, year, suffix | |||
-- Try MDY format first (e.g., "January 15, 2020") | |||
prefix, month, day, year, suffix = string.match(visible_text, '(.-)(%u%a+)%s(%d+),%s(%d+)(.*)') | |||
-- If MDY failed, try DMY format (e.g., "15 January 2020") | |||
if year == nil then | |||
date_format = "dmy" | |||
prefix, day, month, year, suffix = string.match(visible_text, '(.-)(%d%d?)%s(%u%a+)%s(%d+)(.*)') | |||
end | |||
-- If month and year only (e.g., "April 2015") | |||
if year == nil then | |||
month, year = visible_text:match('(%u%a+)%s(%d%d%d%d)') | |||
prefix, suffix, day = "", "", nil | |||
end | |||
-- If year only (e.g., "2015") | |||
if year == nil then | |||
year = visible_text:match('(%d%d%d%d)') | |||
prefix, suffix, month, day = "", "", nil, nil | |||
end | |||
-- Handle "present" case | |||
if visible_text:find("present") then | |||
year = "present" | |||
prefix, suffix, month, day = "", "", nil, nil | |||
end | |||
-- Set default empty strings for optional components | |||
suffix = suffix or '' | |||
prefix = prefix or '' | |||
return { | |||
prefix = prefix, | |||
month = month, | |||
day = day, | |||
year = year, | |||
suffix = suffix, | |||
format = date_format | |||
} | |||
end | |||
--- Get month number from name. | |||
-- @param month_name Name of the month | |||
-- @return Number corresponding to the month or nil if invalid | |||
local function get_month_number(month_name) | |||
return month_name and MONTHS[month_name] | |||
end | end | ||
function | --- Format date range for same year according to Wikipedia style. | ||
-- @param date1 First date components | |||
-- @param date2 Second date components | |||
-- @param span1 First date span HTML | |||
-- @param span2 Second date span HTML | |||
-- @return Formatted date range string | |||
local function format_same_year(date1, date2, span1, span2) | |||
-- Both dates have just year, no month or day | |||
if date1.month == nil and date2.month == nil then | |||
return date1.prefix .. date1.year .. span1 .. DASH .. date2.year .. span2 | |||
end | |||
-- Both dates have month and year, but no day | |||
if date1.day == nil and date2.day == nil then | |||
return date1.prefix .. date1.month .. span1 .. DASH .. date2.month .. ' ' .. date1.year .. span2 | |||
end | |||
if | -- Same month and year | ||
if | if date1.month == date2.month then | ||
return '' | if date1.format == "dmy" then | ||
-- Format: d1–d2 m1 y1 (5–7 January 1979) | |||
return | return date1.prefix .. date1.day .. span1 .. DASH .. date2.day .. ' ' .. date1.month .. ' ' .. date1.year .. span2 | ||
else | |||
-- Format: m1 d1–d2, y1 (January 5–7, 1979) | |||
return date1.prefix .. date1.month .. ' ' .. date1.day .. span1 .. DASH .. date2.day .. ', ' .. date1.year .. span2 | |||
end | end | ||
else | |||
-- Different months, same year | |||
if date1.format == "dmy" then | |||
-- Format: d1 m1 – d2 m2 y1 (3 June –<br/> 18 August 1952) | |||
return date1.prefix .. date1.day .. ' ' .. date1.month .. span1 .. DASH_BREAK .. date2.day .. ' ' .. date2.month .. ' ' .. date1.year .. span2 | |||
else | |||
-- Format: m1 d1 – m2 d2, y1 (June 3 –<br/> August 18, 1952) | |||
return date1.prefix .. date1.month .. ' ' .. date1.day .. span1 .. DASH_BREAK .. date2.month .. ' ' .. date2.day .. ', ' .. date1.year .. span2 | |||
end | |||
end | |||
end | |||
--- Format date range with "present" as the end date | |||
-- @param date1 Start date components | |||
-- @param span1 Start date span HTML | |||
-- @return Formatted date range string with "present" as end date | |||
local function format_present_range(date1, span1) | |||
-- Year only | |||
if date1.month == nil then | |||
return date1.prefix .. date1.year .. span1 .. DASH .. "present" | |||
end | |||
-- Month and year, no day | |||
if date1.day == nil then | |||
return date1.prefix .. date1.month .. ' ' .. date1.year .. span1 .. " " .. DASH .. " present" | |||
end | |||
-- Full date (with line break) | |||
if date1.format == "dmy" then | |||
return date1.prefix .. date1.day .. ' ' .. date1.month .. ' ' .. date1.year .. span1 .. DASH_BREAK .. "present" | |||
else | |||
return date1.prefix .. date1.month .. ' ' .. date1.day .. ', ' .. date1.year .. span1 .. DASH_BREAK .. "present" | |||
end | |||
end | |||
--- Format date range for different years. | |||
-- @param date1 First date components | |||
-- @param date2 Second date components | |||
-- @param visible_text1 Visible text of first date | |||
-- @param visible_text2 Visible text of second date | |||
-- @param span1 First date span HTML | |||
-- @param span2 Second date span HTML | |||
-- @return Formatted date range string for different years | |||
local function format_different_years(date1, date2, visible_text1, visible_text2, span1, span2) | |||
-- If both entries are just years, use simple dash without line break | |||
if date1.month == nil and date2.month == nil then | |||
return visible_text1 .. span1 .. DASH .. visible_text2 .. span2 | |||
end | |||
-- If one of them has a month or day, use dash with line break | |||
return visible_text1 .. span1 .. DASH_BREAK .. visible_text2 .. span2 | |||
end | |||
--- Validate that date2 is after date1 | |||
-- @param date1 First date components | |||
-- @param date2 Second date components | |||
-- @return Boolean indicating if date range is valid | |||
local function validate_date_range(date1, date2) | |||
-- Skip validation if one date is just a year or if second date is "present" | |||
if not date1.month or not date2.month or date2.year == "present" then | |||
return true | |||
end | |||
local month1_number = get_month_number(date1.month) | |||
local month2_number = get_month_number(date2.month) | |||
-- If invalid month names, consider validation failed | |||
if not month1_number or not month2_number then | |||
return false | |||
end | end | ||
-- Convert year strings to numbers | |||
local year1 = tonumber(date1.year) | |||
local year2 = tonumber(date2.year) | |||
if not year1 or not year2 then | |||
return false | |||
if | |||
end | end | ||
-- If years are different, comparison is simple | |||
if | if year1 < year2 then | ||
return true | |||
elseif year1 > year2 then | |||
return false | |||
end | |||
-- Same year, compare months | |||
if month1_number < month2_number then | |||
return true | |||
elseif month1_number > month2_number then | |||
return false | |||
end | |||
-- Same year and month, compare days if available | |||
if date1.day and date2.day then | |||
local day1 = tonumber(date1.day) | |||
local day2 = tonumber(date2.day) | |||
if not day1 or not day2 then | |||
return false | |||
end | |||
return day1 <= day2 | |||
end | |||
-- Same year and month, no days to compare | |||
return true | |||
end | |||
-- ============================================= | |||
-- Main function | |||
-- ============================================= | |||
--- Format date ranges according to Wikipedia style. | |||
-- @param frame Frame object from Wikipedia | |||
-- @return Formatted date range string | |||
function p.dates(frame) | |||
local args = getArgs(frame) | |||
-- Handle missing or empty arguments cases | |||
if not args[1] and not args[2] then | |||
return '' | |||
elseif not args[1] then | |||
return args[2] or '' | |||
elseif not args[2] then | |||
return args[1] or '' | |||
end | |||
-- Get spans from original inputs | |||
local span1 = extract_span(args[1]) | |||
local span2 = extract_span(args[2]) | |||
-- Get visible parts only | |||
local visible_text1 = extract_visible(args[1]) | |||
local visible_text2 = extract_visible(args[2]) | |||
-- Clean up spaces | |||
visible_text1 = replace_space(visible_text1) | |||
visible_text2 = replace_space(visible_text2) | |||
-- Parse dates | |||
local date1 = parse_date(visible_text1) | |||
local date2 = parse_date(visible_text2) | |||
-- Handle unparsable dates (fallback to original format) | |||
if date1.year == nil or (date2.year == nil and not string.find(visible_text2 or "", "present")) then | |||
return (args[1] or '') .. DASH .. (args[2] or '') | |||
end | |||
-- Handle "present" as end date | |||
if (visible_text2 and visible_text2:find("present")) or date2.year == "present" then | |||
return format_present_range(date1, span1) | |||
end | |||
-- Validate date range | |||
if not validate_date_range(date1, date2) then | |||
return 'Invalid date range' | |||
end | |||
-- Format based on whether years are the same | |||
if date1.year == date2.year then | |||
return format_same_year(date1, date2, span1, span2) | |||
else | else | ||
-- Different years | |||
return format_different_years(date1, date2, visible_text1, visible_text2, span1, span2) | |||
end | end | ||
end | end | ||
return p | return p | ||