<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://the-democratika.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ADate_time</id>
	<title>Module:Date time - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://the-democratika.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ADate_time"/>
	<link rel="alternate" type="text/html" href="https://the-democratika.com/wiki/index.php?title=Module:Date_time&amp;action=history"/>
	<updated>2026-04-04T16:56:12Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://the-democratika.com/wiki/index.php?title=Module:Date_time&amp;diff=5838&amp;oldid=prev</id>
		<title>&gt;Gonnym: changing non-breaking space</title>
		<link rel="alternate" type="text/html" href="https://the-democratika.com/wiki/index.php?title=Module:Date_time&amp;diff=5838&amp;oldid=prev"/>
		<updated>2025-03-11T11:54:14Z</updated>

		<summary type="html">&lt;p&gt;changing non-breaking space&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--[[&lt;br /&gt;
Module:Date time – Date formatting and validation module.&lt;br /&gt;
This module provides functions for validating and formatting dates in Wikipedia templates such as&lt;br /&gt;
{{Start date}} and {{End date}}. It handles validation of date components, timezone formatting,&lt;br /&gt;
and generates appropriate hCalendar microformat markup.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
require(&amp;quot;strict&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
---------------&lt;br /&gt;
-- Constants --&lt;br /&gt;
---------------&lt;br /&gt;
&lt;br /&gt;
local HTML_SPACE = &amp;quot;&amp;amp;#32;&amp;quot;&lt;br /&gt;
local HTML_NBSP = &amp;quot;&amp;amp;nbsp;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Error message constants&lt;br /&gt;
local ERROR_MESSAGES = {&lt;br /&gt;
	integers = &amp;quot;All values must be integers&amp;quot;,&lt;br /&gt;
	has_leading_zeros = &amp;quot;Values cannot have unnecessary leading zeros&amp;quot;,&lt;br /&gt;
	missing_year = &amp;quot;Year value is required&amp;quot;,&lt;br /&gt;
	invalid_month = &amp;quot;Value is not a valid month&amp;quot;,&lt;br /&gt;
	missing_month = &amp;quot;Month value is required when a day is provided&amp;quot;,&lt;br /&gt;
	invalid_day = &amp;quot;Value is not a valid day (Month %d has %d days)&amp;quot;,&lt;br /&gt;
	invalid_hour = &amp;quot;Value is not a valid hour&amp;quot;,&lt;br /&gt;
	invalid_minute = &amp;quot;Value is not a valid minute&amp;quot;,&lt;br /&gt;
	invalid_second = &amp;quot;Value is not a valid second&amp;quot;,&lt;br /&gt;
	timezone_incomplete_date = &amp;quot;A timezone cannot be set without a day and hour&amp;quot;,&lt;br /&gt;
	invalid_timezone = &amp;quot;Value is not a valid timezone&amp;quot;,&lt;br /&gt;
	df = &amp;#039;df must be either &amp;quot;yes&amp;quot; or &amp;quot;y&amp;quot;&amp;#039;,&lt;br /&gt;
	template = &amp;quot;Template not supported&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Template class mapping&lt;br /&gt;
local TEMPLATE_CLASSES = {&lt;br /&gt;
	[&amp;quot;start date&amp;quot;] = &amp;quot;bday dtstart published updated itvstart&amp;quot;,&lt;br /&gt;
	[&amp;quot;end date&amp;quot;] = &amp;quot;dtend itvend&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- English month names&lt;br /&gt;
local MONTHS = {&lt;br /&gt;
	&amp;quot;January&amp;quot;, &amp;quot;February&amp;quot;, &amp;quot;March&amp;quot;, &amp;quot;April&amp;quot;, &amp;quot;May&amp;quot;, &amp;quot;June&amp;quot;,&lt;br /&gt;
	&amp;quot;July&amp;quot;, &amp;quot;August&amp;quot;, &amp;quot;September&amp;quot;, &amp;quot;October&amp;quot;, &amp;quot;November&amp;quot;, &amp;quot;December&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Namespaces where error categories should be applied&lt;br /&gt;
local CATEGORY_NAMESPACES = {&lt;br /&gt;
	[0] = true,    -- Article&lt;br /&gt;
	[4] = true,    -- Wikipedia&lt;br /&gt;
	[100] = true,  -- Portal&lt;br /&gt;
	[118] = true   -- Draft&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Cached leap year calculations for performance&lt;br /&gt;
local leap_year_cache = {}&lt;br /&gt;
&lt;br /&gt;
----------------------&lt;br /&gt;
-- Helper Functions --&lt;br /&gt;
----------------------&lt;br /&gt;
&lt;br /&gt;
--- Pads a number with leading zeros to ensure a minimum of two digits.&lt;br /&gt;
-- @param value (number|string) The value to pad with leading zeros&lt;br /&gt;
-- @return string The value padded to at least two digits, or nil if input is nil&lt;br /&gt;
local function pad_left_zeros(value)&lt;br /&gt;
	if value == nil then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local str = tostring(value)&lt;br /&gt;
	return string.rep(&amp;quot;0&amp;quot;, math.max(0, 2 - #str)) .. str&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Replaces U+2212 (Unicode minus) with U+002D (ASCII hyphen) or vice versa.&lt;br /&gt;
-- @param value (string) The string value to process&lt;br /&gt;
-- @param to_unicode (boolean) If true, converts ASCII hyphen to Unicode minus;&lt;br /&gt;
--                            If false, converts Unicode minus to ASCII hyphen&lt;br /&gt;
-- @return string The processed string with appropriate minus characters, or nil if input is nil&lt;br /&gt;
local function replace_minus_character(value, to_unicode)&lt;br /&gt;
	if not value then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if to_unicode then&lt;br /&gt;
		return value:gsub(&amp;quot;-&amp;quot;, &amp;quot;−&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return value:gsub(&amp;quot;−&amp;quot;, &amp;quot;-&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Normalizes timezone format by ensuring proper padding of hours.&lt;br /&gt;
-- @param timezone (string) The timezone string to normalize&lt;br /&gt;
-- @return string The normalized timezone string with properly padded hours, or nil if input is nil&lt;br /&gt;
local function fix_timezone(timezone)&lt;br /&gt;
	if not timezone then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Replace U+2212 (Unicode minus) with U+002D (ASCII hyphen)&lt;br /&gt;
	timezone = replace_minus_character(timezone, false)&lt;br /&gt;
&lt;br /&gt;
	-- Match the timezone pattern for ±H:MM format&lt;br /&gt;
	local sign, hour, minutes = timezone:match(&amp;quot;^([+-])(%d+):(%d+)$&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	if sign and hour and minutes then&lt;br /&gt;
		-- Pad the hour with a leading zero if necessary&lt;br /&gt;
		hour = pad_left_zeros(hour)&lt;br /&gt;
		return sign .. hour .. &amp;quot;:&amp;quot; .. minutes&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- If no match, return the original timezone (this handles invalid or already padded timezones)&lt;br /&gt;
	return timezone&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Checks if a timezone string is valid according to standard timezone formats.&lt;br /&gt;
-- Valid timezones range from UTC-12:00 to UTC+14:00.&lt;br /&gt;
-- @param timezone (string) The timezone string to validate&lt;br /&gt;
-- @return boolean true if the timezone is valid, false otherwise&lt;br /&gt;
local function is_timezone_valid(timezone)&lt;br /&gt;
	-- Consolidated timezone pattern for better performance&lt;br /&gt;
	local valid_patterns = {&lt;br /&gt;
		-- Z (UTC)&lt;br /&gt;
		&amp;quot;^Z$&amp;quot;,&lt;br /&gt;
		-- Full timezone with minutes ±HH:MM&lt;br /&gt;
		&amp;quot;^[+]0[1-9]:[0-5][0-9]$&amp;quot;,&lt;br /&gt;
		&amp;quot;^[+-]0[1-9]:[0-5][0-9]$&amp;quot;, &lt;br /&gt;
		&amp;quot;^[+-]1[0-2]:[0-5][0-9]$&amp;quot;,&lt;br /&gt;
		&amp;quot;^[+]1[34]:[0-5][0-9]$&amp;quot;,&lt;br /&gt;
		-- Whole hour timezones ±HH&lt;br /&gt;
		&amp;quot;^[+-]0[1-9]$&amp;quot;,&lt;br /&gt;
		&amp;quot;^[+-]1[0-2]$&amp;quot;,&lt;br /&gt;
		&amp;quot;^[+]1[34]$&amp;quot;,&lt;br /&gt;
		-- Special cases&lt;br /&gt;
		&amp;quot;^[+]00:00$&amp;quot;,&lt;br /&gt;
		&amp;quot;^[+]00$&amp;quot;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	-- Additional checks for invalid -00 and -00:00 cases&lt;br /&gt;
	if timezone == &amp;quot;-00&amp;quot; or timezone == &amp;quot;-00:00&amp;quot; then&lt;br /&gt;
		return false&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	for _, pattern in ipairs(valid_patterns) do&lt;br /&gt;
		if string.match(timezone, pattern) then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Checks if a given year is a leap year.&lt;br /&gt;
-- Uses a cache for better performance.&lt;br /&gt;
-- @param year (number) The year to check for leap year status&lt;br /&gt;
-- @return boolean true if the year is a leap year, false otherwise&lt;br /&gt;
local function is_leap_year(year)&lt;br /&gt;
	if leap_year_cache[year] == nil then&lt;br /&gt;
		leap_year_cache[year] = (year % 4 == 0 and year % 100 ~= 0) or (year % 400 == 0)&lt;br /&gt;
	end&lt;br /&gt;
	return leap_year_cache[year]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Returns the number of days in a given month of a specified year.&lt;br /&gt;
-- Handles leap years for February.&lt;br /&gt;
-- @param year (number) The year to check for leap year conditions&lt;br /&gt;
-- @param month (number) The month (1-12) for which to return the number of days&lt;br /&gt;
-- @return number The number of days in the specified month, accounting for leap years&lt;br /&gt;
local function get_days_in_month(year, month)&lt;br /&gt;
	local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }&lt;br /&gt;
&lt;br /&gt;
	if month == 2 and is_leap_year(year) then&lt;br /&gt;
		return 29&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return days_in_month[month] or 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Checks if a given value has invalid leading zeros.&lt;br /&gt;
-- @param value (string) The value to check for leading zeros&lt;br /&gt;
-- @param field_type (string) Field type (&amp;quot;day&amp;quot;, &amp;quot;month&amp;quot;, &amp;quot;hour&amp;quot;, &amp;quot;minute&amp;quot;, &amp;quot;second&amp;quot;)&lt;br /&gt;
-- @return boolean true if the value has invalid leading zeros, false otherwise&lt;br /&gt;
local function has_leading_zeros(value, field_type)&lt;br /&gt;
	value = tostring(value)&lt;br /&gt;
&lt;br /&gt;
	-- Common checks for day and month&lt;br /&gt;
	if field_type == &amp;quot;day&amp;quot; or field_type == &amp;quot;month&amp;quot; then&lt;br /&gt;
		-- Reject &amp;quot;00&amp;quot; and values with leading zero followed by more than one digit&lt;br /&gt;
		return value == &amp;quot;00&amp;quot; or &lt;br /&gt;
		       string.match(value, &amp;quot;^0[0-9][0-9]$&amp;quot;) ~= nil or &lt;br /&gt;
		       string.match(value, &amp;quot;^0[1-9][0-9]&amp;quot;) ~= nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Checks for hour, minute, second&lt;br /&gt;
	if field_type == &amp;quot;hour&amp;quot; or field_type == &amp;quot;minute&amp;quot; or field_type == &amp;quot;second&amp;quot; then&lt;br /&gt;
		-- Allow &amp;quot;00&amp;quot; and &amp;quot;01&amp;quot; to &amp;quot;09&amp;quot;&lt;br /&gt;
		if value == &amp;quot;00&amp;quot; or string.match(value, &amp;quot;^0[1-9]$&amp;quot;) then&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		-- Reject values starting with &amp;quot;0&amp;quot; followed by more than one digit&lt;br /&gt;
		return string.match(value, &amp;quot;^0[0-9][0-9]+$&amp;quot;) ~= nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Checks if a given value is an integer.&lt;br /&gt;
-- @param value (string|number) The value to check&lt;br /&gt;
-- @return boolean true if the value is a valid integer, false otherwise&lt;br /&gt;
local function is_integer(value)&lt;br /&gt;
	if not value then&lt;br /&gt;
		return false&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local num_value = tonumber(value)&lt;br /&gt;
	return num_value and math.floor(num_value) == num_value&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Returns the name of a month based on its numerical representation.&lt;br /&gt;
-- @param month_number (number) The month number (1-12)&lt;br /&gt;
-- @return string|nil The name of the month, or nil if invalid&lt;br /&gt;
local function get_month_name(month_number)&lt;br /&gt;
	month_number = tonumber(month_number)&lt;br /&gt;
	return MONTHS[month_number]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Local variables for error handling&lt;br /&gt;
local help_link&lt;br /&gt;
local error_category&lt;br /&gt;
&lt;br /&gt;
-- In the improved code, locate the generate_error function and modify it like this:&lt;br /&gt;
&lt;br /&gt;
--- Generates an error message wrapped in HTML.&lt;br /&gt;
-- @param message (string) The error message to format&lt;br /&gt;
-- @param add_tracking_category (boolean, optional) If false, omits the tracking category&lt;br /&gt;
-- @return string An HTML-formatted error message with help link and error category&lt;br /&gt;
local function generate_error(message, add_tracking_category)&lt;br /&gt;
	local category = &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	if add_tracking_category ~= false then&lt;br /&gt;
		category = error_category or &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Get current page title object&lt;br /&gt;
	local article_title = mw.title.getCurrentTitle()&lt;br /&gt;
	&lt;br /&gt;
	-- Special case for test cases page&lt;br /&gt;
	local is_test_page = (article_title.fullText == &amp;quot;Module talk:Date time/testcases&amp;quot;)&lt;br /&gt;
	&lt;br /&gt;
	-- Remove category if the page is not in a tracked namespace and not the test page&lt;br /&gt;
	if not (CATEGORY_NAMESPACES[article_title.namespace] or is_test_page) then&lt;br /&gt;
		category = &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return &amp;#039;&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;Error: &amp;#039; .. message .. &amp;#039;&amp;lt;/strong&amp;gt; &amp;#039; .. help_link .. category&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------&lt;br /&gt;
-- Formatting Functions --&lt;br /&gt;
--------------------------&lt;br /&gt;
&lt;br /&gt;
--- Formats the time portion of a datetime string.&lt;br /&gt;
-- @param hour (string) The hour component&lt;br /&gt;
-- @param minute (string) The minute component&lt;br /&gt;
-- @param second (string) The second component&lt;br /&gt;
-- @return string The formatted time string, or empty string if hour is nil&lt;br /&gt;
local function format_time_string(hour, minute, second)&lt;br /&gt;
	if not hour then&lt;br /&gt;
		return &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local time_string = string.format(&amp;quot;%s:%s&amp;quot;, hour, minute)&lt;br /&gt;
&lt;br /&gt;
	if second and second ~= &amp;quot;00&amp;quot; and minute ~= &amp;quot;00&amp;quot; then&lt;br /&gt;
		time_string = string.format(&amp;quot;%s:%s&amp;quot;, time_string, second)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return time_string .. &amp;quot;,&amp;quot; .. HTML_SPACE&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Formats the date portion of a datetime string based on the specified format.&lt;br /&gt;
-- @param year (string) The year component&lt;br /&gt;
-- @param month (string) The month component&lt;br /&gt;
-- @param day (string) The day component&lt;br /&gt;
-- @param date_format_dmy (string) The date format (day-month-year if set, otherwise month-day-year)&lt;br /&gt;
-- @return string The formatted date string, or empty string if year is nil&lt;br /&gt;
local function format_date_string(year, month, day, date_format_dmy)&lt;br /&gt;
	if not year then&lt;br /&gt;
		return &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local date_string&lt;br /&gt;
	if month then&lt;br /&gt;
		local month_name = get_month_name(month)&lt;br /&gt;
		&lt;br /&gt;
		if day then&lt;br /&gt;
			day = tonumber(day)&lt;br /&gt;
			if date_format_dmy then&lt;br /&gt;
				date_string = day .. HTML_NBSP .. month_name&lt;br /&gt;
			else&lt;br /&gt;
				date_string = month_name .. HTML_NBSP .. day .. &amp;quot;,&amp;quot;&lt;br /&gt;
			end&lt;br /&gt;
			date_string = date_string .. HTML_NBSP .. year&lt;br /&gt;
		else&lt;br /&gt;
			date_string = month_name .. HTML_NBSP .. year&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		date_string = year&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return date_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Formats the timezone portion of a datetime string.&lt;br /&gt;
-- @param timezone (string) The timezone component&lt;br /&gt;
-- @return string The formatted timezone string, or empty string if timezone is nil&lt;br /&gt;
local function format_timezone(timezone)&lt;br /&gt;
	if not timezone then&lt;br /&gt;
		return &amp;quot;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return HTML_SPACE .. (timezone == &amp;quot;Z&amp;quot; and &amp;quot;(UTC)&amp;quot; or &amp;quot;(&amp;quot; .. timezone .. &amp;quot;)&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Generates an hCalendar microformat string for the given date-time values.&lt;br /&gt;
-- @param date_time_values (table) A table containing date and time components&lt;br /&gt;
-- @param classes (string) The CSS classes to apply to the microformat span&lt;br /&gt;
-- @return string The HTML for the hCalendar microformat&lt;br /&gt;
local function generate_h_calendar(date_time_values, classes)&lt;br /&gt;
	local parts = {}&lt;br /&gt;
&lt;br /&gt;
	if date_time_values.year then&lt;br /&gt;
		table.insert(parts, date_time_values.year)&lt;br /&gt;
&lt;br /&gt;
		if date_time_values.month then&lt;br /&gt;
			table.insert(parts, &amp;quot;-&amp;quot; .. date_time_values.month)&lt;br /&gt;
&lt;br /&gt;
			if date_time_values.day then&lt;br /&gt;
				table.insert(parts, &amp;quot;-&amp;quot; .. date_time_values.day)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		if date_time_values.hour then&lt;br /&gt;
			table.insert(parts, &amp;quot;T&amp;quot; .. date_time_values.hour)&lt;br /&gt;
&lt;br /&gt;
			if date_time_values.minute then&lt;br /&gt;
				table.insert(parts, &amp;quot;:&amp;quot; .. date_time_values.minute)&lt;br /&gt;
&lt;br /&gt;
				if date_time_values.second then&lt;br /&gt;
					table.insert(parts, &amp;quot;:&amp;quot; .. date_time_values.second)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local h_calendar_content = table.concat(parts) .. (date_time_values.timezone or &amp;quot;&amp;quot;)&lt;br /&gt;
	local class_span = string.format(&amp;#039;&amp;lt;span class=&amp;quot;%s&amp;quot;&amp;gt;&amp;#039;, classes)&lt;br /&gt;
&lt;br /&gt;
	return string.format(&lt;br /&gt;
		&amp;#039;&amp;lt;span style=&amp;quot;display: none;&amp;quot;&amp;gt;%s(%s)&amp;lt;/span&amp;gt;&amp;#039;, &lt;br /&gt;
		HTML_NBSP, &lt;br /&gt;
		class_span .. h_calendar_content .. &amp;#039;&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
	)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------&lt;br /&gt;
-- Validation Functions --&lt;br /&gt;
--------------------------&lt;br /&gt;
&lt;br /&gt;
--- Validates the date and time values provided.&lt;br /&gt;
-- @param args (table) Table containing date and time values and optional parameters&lt;br /&gt;
-- @return nil|string Nil if validation passes, or an error message if validation fails&lt;br /&gt;
local function _validate_date_time(args)&lt;br /&gt;
	local template_name = args.template or &amp;quot;start date&amp;quot;&lt;br /&gt;
	help_link = string.format(&amp;quot;&amp;lt;small&amp;gt;[[:Template:%s|(help)]]&amp;lt;/small&amp;gt;&amp;quot;, template_name)&lt;br /&gt;
	error_category = string.format(&amp;quot;[[Category:Pages using %s with invalid values]]&amp;quot;, template_name)&lt;br /&gt;
&lt;br /&gt;
	-- Store and validate date-time values&lt;br /&gt;
	local date_time_values = {&lt;br /&gt;
		year = args[1], &lt;br /&gt;
		month = args[2], &lt;br /&gt;
		day = args[3],&lt;br /&gt;
		hour = args[4], &lt;br /&gt;
		minute = args[5], &lt;br /&gt;
		second = args[6]&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	-- Validate each value&lt;br /&gt;
	for key, value in pairs(date_time_values) do&lt;br /&gt;
		if value then&lt;br /&gt;
			-- Check for integer and leading zeros&lt;br /&gt;
			if not is_integer(value) then&lt;br /&gt;
				return generate_error(ERROR_MESSAGES.integers)&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			if has_leading_zeros(tostring(value), key) then&lt;br /&gt;
				return generate_error(ERROR_MESSAGES.has_leading_zeros)&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			-- Convert to number&lt;br /&gt;
			date_time_values[key] = tonumber(value)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Validate date components&lt;br /&gt;
	if not date_time_values.year then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.missing_year)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if date_time_values.month and (date_time_values.month &amp;lt; 1 or date_time_values.month &amp;gt; 12) then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.invalid_month)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if date_time_values.day then&lt;br /&gt;
		if not date_time_values.month then&lt;br /&gt;
			return generate_error(ERROR_MESSAGES.missing_month)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local max_day = get_days_in_month(date_time_values.year, date_time_values.month)&lt;br /&gt;
		if date_time_values.day &amp;lt; 1 or date_time_values.day &amp;gt; max_day then&lt;br /&gt;
			return generate_error(string.format(ERROR_MESSAGES.invalid_day, date_time_values.month, max_day))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Validate time components&lt;br /&gt;
	if date_time_values.hour and (date_time_values.hour &amp;lt; 0 or date_time_values.hour &amp;gt; 23) then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.invalid_hour)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if date_time_values.minute and (date_time_values.minute &amp;lt; 0 or date_time_values.minute &amp;gt; 59) then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.invalid_minute)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if date_time_values.second and (date_time_values.second &amp;lt; 0 or date_time_values.second &amp;gt; 59) then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.invalid_second)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Timezone cannot be set without a specific date and hour&lt;br /&gt;
	if args[7] and not (date_time_values.day and date_time_values.hour) then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.timezone_incomplete_date)&lt;br /&gt;
	elseif args[7] and not is_timezone_valid(args[7]) then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.invalid_timezone)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Validate display format parameter&lt;br /&gt;
	if args.df and not (args.df == &amp;quot;yes&amp;quot; or args.df == &amp;quot;y&amp;quot;) then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.df)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
----------------------&lt;br /&gt;
-- Public Functions --&lt;br /&gt;
----------------------&lt;br /&gt;
&lt;br /&gt;
--- Validates date-time values from template arguments.&lt;br /&gt;
-- @param frame (table) The MediaWiki frame containing template arguments&lt;br /&gt;
-- @return nil|string Result of date-time validation&lt;br /&gt;
function p.validate_date_time(frame)&lt;br /&gt;
	local get_args = require(&amp;quot;Module:Arguments&amp;quot;).getArgs&lt;br /&gt;
	local args = get_args(frame)&lt;br /&gt;
	args[7] = fix_timezone(args[7])&lt;br /&gt;
	return _validate_date_time(args)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Generates a formatted date string with microformat markup.&lt;br /&gt;
-- @param frame (table) The MediaWiki frame containing template arguments&lt;br /&gt;
-- @return string A formatted date string, or an error message if validation fails&lt;br /&gt;
function p.generate_date(frame)&lt;br /&gt;
	local get_args = require(&amp;quot;Module:Arguments&amp;quot;).getArgs&lt;br /&gt;
	local args = get_args(frame)&lt;br /&gt;
	&lt;br /&gt;
	args[7] = fix_timezone(args[7])&lt;br /&gt;
&lt;br /&gt;
	local validation_error = _validate_date_time(args)&lt;br /&gt;
	if validation_error then&lt;br /&gt;
		return validation_error&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local classes = TEMPLATE_CLASSES[args.template or &amp;quot;start date&amp;quot;]&lt;br /&gt;
	if not classes then&lt;br /&gt;
		return generate_error(ERROR_MESSAGES.template, false)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Process date-time values&lt;br /&gt;
	local date_time_values = {&lt;br /&gt;
		year = args[1], &lt;br /&gt;
		month = args[2], &lt;br /&gt;
		day = args[3],&lt;br /&gt;
		hour = pad_left_zeros(args[4]), &lt;br /&gt;
		minute = args[5] and pad_left_zeros(args[5]) or &amp;quot;00&amp;quot;,&lt;br /&gt;
		second = args[6] and pad_left_zeros(args[6]) or &amp;quot;00&amp;quot;, &lt;br /&gt;
		timezone = replace_minus_character(args[7], true) -- Restore U+2212 (Unicode minus)&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
    -- Generate individual components&lt;br /&gt;
	local time_string = format_time_string(&lt;br /&gt;
		date_time_values.hour, &lt;br /&gt;
		date_time_values.minute, &lt;br /&gt;
		date_time_values.second&lt;br /&gt;
	)&lt;br /&gt;
&lt;br /&gt;
	local date_string = format_date_string(&lt;br /&gt;
		date_time_values.year, &lt;br /&gt;
		date_time_values.month, &lt;br /&gt;
		date_time_values.day, &lt;br /&gt;
		args.df&lt;br /&gt;
	)&lt;br /&gt;
&lt;br /&gt;
	local timezone_string = format_timezone(date_time_values.timezone)&lt;br /&gt;
&lt;br /&gt;
	local h_calendar = generate_h_calendar(date_time_values, classes)&lt;br /&gt;
&lt;br /&gt;
	-- Combine components&lt;br /&gt;
	return time_string .. date_string .. timezone_string .. h_calendar&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>&gt;Gonnym</name></author>
	</entry>
</feed>