Jump to content

Module:Date time and Module:Date time/sandbox: Difference between pages

(Difference between pages)
Page 1
Page 2
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 in templates such as
This module provides functions for validating and formatting dates for the following templates:
{{Start date}}, {{End date}}, {{Start date and age}}, and {{End 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 with leading zeros
-- @param value (number|string) The value to pad
-- @return string The value padded to at least two digits, or nil if input is nil
-- @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 == nil then
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[0-9][0-9]$") ~= nil or
string.match(value, "^0[1-9][0-9]") ~= nil
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.year,
date_time_values.month,
date_time_values.month,
date_time_values.day)
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.year,
date_time_values.month)
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