Module:Date time and Module:Date time/sandbox: Difference between pages
Appearance
(Difference between pages)
Content deleted Content added
remove categories from /testcases other than in this module |
No edit summary |
||
Line 2: | Line 2: | ||
Module:Date time – Date formatting and validation module. |
Module:Date time – Date formatting and validation module. |
||
This module provides functions for validating and formatting dates |
This module provides functions for validating and formatting dates for the following templates: |
||
{{Start date}}, {{End date}}, {{Start date and age}}, |
{{Start date}}, {{End date}}, {{Start date and age}}, {{End date and age}}, {{Start and end dates}}. |
||
It handles: |
It handles: |
||
- Validation of date components (year, month, day) |
- Validation of date components (year, month, day) |
||
Line 9: | Line 9: | ||
- Timezone formatting and validation |
- Timezone formatting and validation |
||
- Generation of appropriate hCalendar microformat markup |
- Generation of appropriate hCalendar microformat markup |
||
- "time-ago" calculations |
- "time-ago" calculations for age-related templates |
||
Design notes: |
Design notes: |
||
Line 26: | Line 26: | ||
--------------- |
--------------- |
||
local HTML_SPACE = " " |
local HTML_SPACE = " " -- Space character for HTML compatibility |
||
local HTML_NBSP = " " |
local HTML_NBSP = " " -- Non-breaking space for HTML |
||
local DASH = "–" -- En dash for ranges (e.g., year–year) |
|||
-- Error message constants |
-- Error message constants |
||
Line 45: | Line 46: | ||
duplicate_parameters = 'Duplicate parameters used: %s and %s', |
duplicate_parameters = 'Duplicate parameters used: %s and %s', |
||
template = "Template not supported", |
template = "Template not supported", |
||
time_without_hour = "Minutes and seconds require an hour value" |
time_without_hour = "Minutes and seconds require an hour value", |
||
end_date_before_start_date = 'End date is before start date' |
|||
} |
} |
||
Line 94: | Line 96: | ||
--- Pads a number with leading zeros to ensure a minimum of two digits. |
--- Pads a number with leading zeros to ensure a minimum of two digits. |
||
-- @param value (number|string) The value to pad |
-- @param value (number|string) The value to pad |
||
-- @return string The |
-- @return string|nil The padded value, or nil if input is nil |
||
local function pad_left_zeros(value) |
local function pad_left_zeros(value) |
||
if value |
if not value then |
||
return nil |
return nil |
||
end |
end |
||
return string.format("%02d", tonumber(value)) |
|||
local str = tostring(value) |
|||
return string.rep("0", math.max(0, 2 - #str)) .. str |
|||
end |
end |
||
Line 220: | Line 220: | ||
-- Reject "00" and values with leading zero followed by more than one digit |
-- Reject "00" and values with leading zero followed by more than one digit |
||
return value == "00" or |
return value == "00" or |
||
string.match(value, "^0[0-9][0-9]$") ~= nil or |
|||
string.match(value, "^0[1-9][0-9]") ~= nil |
|||
end |
end |
||
Line 353: | Line 353: | ||
return date_string |
return date_string |
||
end |
|||
--- Formats a date range according to [[MOS:DATERANGE]] guidelines. |
|||
-- @param start_date (table) Table with start date components (year, month, day) |
|||
-- @param end_date (table) Table with end date components (year, month, day) |
|||
-- @param df (string) Date format flag ("yes" or "y" for day-month-year format) |
|||
-- @return string Formatted date range string following the style guidelines |
|||
local function format_date_range_string(start_date, end_date, df) |
|||
-- Ensure start year is provided |
|||
if not start_date.year then |
|||
return "" |
|||
end |
|||
-- Case: To present |
|||
if end_date.is_present then |
|||
if start_date.month or start_date.day then |
|||
-- If the start date includes a month or day, use a spaced dash |
|||
return format_date_string(start_date.year, start_date.month, start_date.day, df) .. HTML_SPACE .. DASH .. HTML_SPACE .. "present" |
|||
else |
|||
-- If the start date only has the year |
|||
return start_date.year .. DASH .. "present" |
|||
end |
|||
end |
|||
-- Ensure end year is provided (if not "present") |
|||
if not end_date.year then |
|||
return "" |
|||
end |
|||
-- Case: Year–Year range (e.g., 1881–1892) |
|||
if start_date.year ~= end_date.year and not start_date.month and not start_date.day and not end_date.month and not end_date.day then |
|||
return start_date.year .. DASH .. end_date.year |
|||
end |
|||
-- Case: Day–Day in the same month (e.g., 5–7 January 1979 or January 5–7, 1979) |
|||
if start_date.month == end_date.month and start_date.year == end_date.year and start_date.day and end_date.day then |
|||
local month_name = get_month_name(start_date.month) |
|||
if df then |
|||
return start_date.day .. DASH .. end_date.day .. HTML_NBSP .. month_name .. HTML_NBSP .. start_date.year |
|||
else |
|||
return month_name .. HTML_NBSP .. start_date.day .. DASH .. end_date.day .. "," .. HTML_NBSP .. start_date.year |
|||
end |
|||
end |
|||
-- Case: Month–Month range (e.g., May–July 1940) |
|||
if start_date.year == end_date.year and not start_date.day and not end_date.day and start_date.month and end_date.month then |
|||
local start_month_name = get_month_name(start_date.month) |
|||
local end_month_name = get_month_name(end_date.month) |
|||
return start_month_name .. DASH .. end_month_name .. HTML_NBSP .. start_date.year |
|||
end |
|||
-- Case: Between specific dates in different months (e.g., 3 June – 18 August 1952 or June 3 – August 18, 1952) |
|||
if start_date.year == end_date.year and start_date.month ~= end_date.month and start_date.day and end_date.day then |
|||
local start_month_name = get_month_name(start_date.month) |
|||
local end_month_name = get_month_name(end_date.month) |
|||
if df then |
|||
return start_date.day .. HTML_NBSP .. start_month_name .. HTML_SPACE .. DASH .. HTML_SPACE .. end_date.day .. HTML_NBSP .. end_month_name .. HTML_NBSP .. start_date.year |
|||
else |
|||
return start_month_name .. HTML_NBSP .. start_date.day .. HTML_SPACE .. DASH .. HTML_SPACE .. end_month_name .. HTML_NBSP .. end_date.day .. "," .. HTML_NBSP .. start_date.year |
|||
end |
|||
end |
|||
-- Case: Between specific dates in different years (e.g., 12 February 1809 – 19 April 1882 or February 12, 1809 – April 15, 1865) |
|||
if start_date.year ~= end_date.year and start_date.month and end_date.month and start_date.day and end_date.day then |
|||
local start_month_name = get_month_name(start_date.month) |
|||
local end_month_name = get_month_name(end_date.month) |
|||
if df then |
|||
return start_date.day .. HTML_NBSP .. start_month_name .. HTML_NBSP .. start_date.year .. HTML_SPACE .. DASH .. HTML_SPACE .. end_date.day .. HTML_NBSP .. end_month_name .. HTML_NBSP .. end_date.year |
|||
else |
|||
return start_month_name .. HTML_NBSP .. start_date.day .. "," .. HTML_NBSP .. start_date.year .. HTML_SPACE .. DASH .. HTML_SPACE .. end_month_name .. HTML_NBSP .. end_date.day .. "," .. HTML_NBSP .. end_date.year |
|||
end |
|||
end |
|||
-- For any other cases, format each date separately and join with a dash |
|||
local start_str = format_date_string(start_date.year, start_date.month, start_date.day, df) |
|||
local end_str = format_date_string(end_date.year, end_date.month, end_date.day, df) |
|||
return start_str .. HTML_SPACE .. DASH .. HTML_SPACE .. end_str |
|||
end |
end |
||
Line 420: | Line 498: | ||
-- Format with padding for month and day if needed |
-- Format with padding for month and day if needed |
||
timestamp = string.format("%d-%02d-%02d", |
timestamp = string.format("%d-%02d-%02d", |
||
date_time_values.year, |
|||
date_time_values.month, |
|||
date_time_values.day |
|||
) |
|||
min_magnitude = "days" |
min_magnitude = "days" |
||
elseif date_time_values.month then |
elseif date_time_values.month then |
||
-- Format with padding for month if needed |
-- Format with padding for month if needed |
||
timestamp = string.format("%d-%02d", |
timestamp = string.format("%d-%02d", |
||
date_time_values.year, |
|||
date_time_values.month |
|||
) |
|||
-- Get the current date |
-- Get the current date |
||
Line 466: | Line 546: | ||
-- Validation Functions -- |
-- Validation Functions -- |
||
-------------------------- |
-------------------------- |
||
--- Validates that dates are in chronological order when using date ranges. |
|||
-- Supports partial dates by defaulting missing components (month and day) to 1. |
|||
-- @param start_date (table) Table with start date components (year, month, day) |
|||
-- @param end_date (table) Table with end date components (year, month, day) |
|||
-- @return boolean true if end_date occurs after or equals start_date, false otherwise |
|||
local function is_date_order_valid(start_date, end_date) |
|||
local start_timestamp = os.time({ |
|||
year = start_date.year, |
|||
month = start_date.month or 1, |
|||
day = start_date.day or 1 |
|||
}) |
|||
local end_timestamp = os.time({ |
|||
year = end_date.year, |
|||
month = end_date.month or 1, |
|||
day = end_date.day or 1 |
|||
}) |
|||
return end_timestamp >= start_timestamp |
|||
end |
|||
--- Validates the date and time values provided. |
--- Validates the date and time values provided. |
||
Line 639: | Line 738: | ||
return time_string .. date_string .. timezone_string .. time_ago .. h_calendar |
return time_string .. date_string .. timezone_string .. time_ago .. h_calendar |
||
end |
end |
||
--- Generates a formatted date range string with microformat markup. |
|||
--- Used by {{Start and end dates}}. |
|||
-- @param frame (table) The MediaWiki frame containing template arguments |
|||
-- @return string A formatted date range string, or an error message if validation fails |
|||
function p.generate_date_range(frame) |
|||
local get_args = require("Module:Arguments").getArgs |
|||
local args = get_args(frame) |
|||
-- Validate start date |
|||
local start_validation_error = _validate_date_time({args[1], args[2], args[3], df = args.df}) |
|||
-- Check if end date is "present" |
|||
local is_present = args[4] == "present" |
|||
local end_validation_error |
|||
local current_date |
|||
if is_present then |
|||
-- Create a date table with current date |
|||
current_date = { |
|||
year = os.date("%Y"), -- Current year |
|||
month = os.date("%m"), -- Current month |
|||
day = os.date("%d") -- Current day |
|||
} |
|||
end_validation_error = nil |
|||
else |
|||
end_validation_error = _validate_date_time({args[4], args[5], args[6]}) |
|||
end |
|||
if start_validation_error or end_validation_error then |
|||
return start_validation_error or end_validation_error |
|||
end |
|||
-- Sanitize inputs |
|||
local start_date = { |
|||
year = args[1], |
|||
month = pad_left_zeros(args[2]), |
|||
day = pad_left_zeros(args[3]) |
|||
} |
|||
local end_date = { |
|||
year = is_present and current_date.year or args[4], |
|||
month = is_present and pad_left_zeros(current_date.month) or pad_left_zeros(args[5]), |
|||
day = is_present and pad_left_zeros(current_date.day) or pad_left_zeros(args[6]), |
|||
is_present = is_present -- Add flag to indicate "present" |
|||
} |
|||
if not is_date_order_valid(start_date, end_date) then |
|||
return generate_error(ERROR_MESSAGES.end_date_before_start_date) |
|||
end |
|||
-- Generate date range string |
|||
local date_range_string = format_date_range_string(start_date, end_date, args.df) |
|||
-- Generate h-calendar markup |
|||
local start_h_calendar = generate_h_calendar(start_date, "dtstart") |
|||
local end_h_calendar = generate_h_calendar(end_date, "dtend") |
|||
return date_range_string .. start_h_calendar .. end_h_calendar |
|||
end |
|||
-- Exposed for the /testcases |
|||
p.ERROR_MESSAGES = ERROR_MESSAGES |
|||
return p |
return p |