<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mywikibiz.com/index.php?action=history&amp;feed=atom&amp;title=Module%3ACountdown-ymd</id>
	<title>Module:Countdown-ymd - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mywikibiz.com/index.php?action=history&amp;feed=atom&amp;title=Module%3ACountdown-ymd"/>
	<link rel="alternate" type="text/html" href="https://mywikibiz.com/index.php?title=Module:Countdown-ymd&amp;action=history"/>
	<updated>2026-08-03T01:01:05Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.35.3</generator>
	<entry>
		<id>https://mywikibiz.com/index.php?title=Module:Countdown-ymd&amp;diff=471380&amp;oldid=prev</id>
		<title>Zoran: Pywikibot 6.4.0</title>
		<link rel="alternate" type="text/html" href="https://mywikibiz.com/index.php?title=Module:Countdown-ymd&amp;diff=471380&amp;oldid=prev"/>
		<updated>2021-07-15T21:11:17Z</updated>

		<summary type="html">&lt;p&gt;Pywikibot 6.4.0&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- This module powers {{countdown-ymd}}.&lt;br /&gt;
require('Module:No globals')&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
local static_text = {&lt;br /&gt;
	['eventtime'] = 'Time to event:',&lt;br /&gt;
	['begins'] = 'Event begins in',												-- when |duration= is set&lt;br /&gt;
	['ends'] = 'Event ends in',													-- when |duration= is set&lt;br /&gt;
	['ended'] = 'Event has ended.',												-- when |duration= is set&lt;br /&gt;
	['passed'] = 'Event time has passed.',&lt;br /&gt;
	['invalid'] = '&amp;lt;span style=&amp;quot;font-size:100%&amp;quot; class=&amp;quot;error&amp;quot;&amp;gt;Error: invalid date and/or time&amp;lt;/span&amp;gt;',&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
local getArgs = require('Module:Arguments').getArgs&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; F O R M A T _ U N I T &amp;gt;--------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Concatenates a singular or plural label to a time/date unit: unit label or unit labels.  If unit is 0 or less, returns nil&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function format_unit (unit, label)&lt;br /&gt;
	if 1 &amp;gt; unit then															-- less than one so don't display&lt;br /&gt;
		return nil;																-- return nil so the result of this call isn't stuffed into results table by table.insert&lt;br /&gt;
	elseif 1 == unit then&lt;br /&gt;
		return unit .. ' ' .. label;											-- only one unit so return singular label&lt;br /&gt;
	else&lt;br /&gt;
		return unit .. ' ' .. label .. 's';										-- multiple units so return plural label&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; D U R A T I O N &amp;gt;--------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Returns duration of event in seconds.  If the units are not defined, assumes seconds.  If the units are&lt;br /&gt;
defined but not one of day, days, hour, hours, minute, minutes, second, seconds then returns zero.&lt;br /&gt;
&lt;br /&gt;
|duration=&amp;lt;number&amp;gt;&amp;lt;space&amp;gt;&amp;lt;unit&amp;gt;&lt;br /&gt;
&lt;br /&gt;
TODO: Why are we even considering seconds and minutes?  Would it be better to specify and end time?&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function duration (duration)&lt;br /&gt;
	local number, unit = duration:match ('(%d+)%s+(%a*)');&lt;br /&gt;
	&lt;br /&gt;
	if not number then&lt;br /&gt;
		return 0;																-- duration not properly specified&lt;br /&gt;
	elseif not unit then&lt;br /&gt;
		return number;															-- unit not defined, assume seconds&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if unit:match ('days?') then&lt;br /&gt;
		return number * 86400;&lt;br /&gt;
	elseif unit:match ('hours?') then&lt;br /&gt;
		return number * 3600;&lt;br /&gt;
	elseif unit:match ('minutes?') then&lt;br /&gt;
		return number * 60;&lt;br /&gt;
	elseif unit:match ('seconds?') then&lt;br /&gt;
		return number;&lt;br /&gt;
	else&lt;br /&gt;
		return 0;																-- unknown unit&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; U T C _ O F F S E T &amp;gt;----------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Returns offset from UTC in seconds.  If 'utc offset' parameter is out of range or malformed, returns 0.&lt;br /&gt;
&lt;br /&gt;
TODO: Return a success/fail flag so we can emit an error message?&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function utc_offset (offset)&lt;br /&gt;
	local sign, utc_offset_hr, utc_offset_min;&lt;br /&gt;
	&lt;br /&gt;
	if offset:match('^[%+%-]%d%d:%d%d$') then									-- formal style: sign, hours colon minutes all required&lt;br /&gt;
		sign, utc_offset_hr, utc_offset_min = offset:match('^([%+%-])(%d%d):(%d%d)$');&lt;br /&gt;
	elseif offset:match('^[%+%-]?%d?%d:?%d%d$') then							-- informal: sign and colon optional, 1- or 2-digit hours, and minutes &lt;br /&gt;
		sign, utc_offset_hr, utc_offset_min = offset:match('^([%+%-]?)(%d?%d):?(%d%d)$');&lt;br /&gt;
	elseif offset:match('^[%+%-]?%d?%d$') then									-- informal: sign optional, 1- or 2-digit hours only&lt;br /&gt;
		sign, utc_offset_hr = offset:match('^([%+%-]?)(%d?%d)$');&lt;br /&gt;
		utc_offset_min = 0;														-- because not included in parameter, set it to 0 minutes&lt;br /&gt;
	else&lt;br /&gt;
		return 0;																-- malformed so return 0 seconds&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	utc_offset_hr = tonumber (utc_offset_hr);&lt;br /&gt;
	utc_offset_min = tonumber (utc_offset_min);&lt;br /&gt;
	&lt;br /&gt;
	if 12 &amp;lt; utc_offset_hr or 59 &amp;lt; utc_offset_min then							-- hour and minute range checks&lt;br /&gt;
		return 0;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if '-' == sign then&lt;br /&gt;
		sign = -1;																-- negative west offset&lt;br /&gt;
	else&lt;br /&gt;
		sign = 1;																-- + or sign omitted east offset&lt;br /&gt;
	end&lt;br /&gt;
	utc_offset_hr = sign * (utc_offset_hr * 3600);								-- utc offset hours * seconds/hour&lt;br /&gt;
	utc_offset_min = sign * (utc_offset_min * 60);								-- utc offset minutes * seconds/minute&lt;br /&gt;
	return utc_offset_hr + utc_offset_min;										-- return the UTC offset adjustment in seconds&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; G E T _ D A Y S _ I N _ M O N T H &amp;gt;--------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Returns the number of days in the month where month is a number 1–12 and year is four-digit Gregorian calendar.&lt;br /&gt;
Accounts for leap year.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&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;
	year = tonumber (year);														-- force these to be numbers just in case&lt;br /&gt;
	month = tonumber (month);&lt;br /&gt;
&lt;br /&gt;
	if (2 == month) then														-- if February&lt;br /&gt;
		if (0 == (year%4) and (0 ~= (year%100) or 0 == (year%400))) then		-- is year a leap year?&lt;br /&gt;
			return 29;															-- if leap year then 29 days in February&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return days_in_month [month];&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; D I F F _ T I M E &amp;gt;------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
calculates the difference between two times; returns a table of the differences in diff.hour, diff.min, and diff.sec&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function diff_time (a, b)&lt;br /&gt;
	local diff = {}&lt;br /&gt;
	diff.sec = a.sec - b. sec;&lt;br /&gt;
	if diff.sec &amp;lt; 0 then&lt;br /&gt;
		diff.sec = diff.sec + 60;												-- borrow from minutes&lt;br /&gt;
		a.min = a.min - 1;&lt;br /&gt;
		if a.min &amp;lt; 0 then&lt;br /&gt;
			a.min = a.min + 60;													-- borrow from hours&lt;br /&gt;
			a.hour = a.hour - 1;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	diff.min = a.min - b.min;&lt;br /&gt;
	if diff.min &amp;lt; 0 then&lt;br /&gt;
		diff.min = diff.min + 60;												-- borrow from hours&lt;br /&gt;
		a.hour = a.hour - 1;&lt;br /&gt;
	end&lt;br /&gt;
	diff.hour = a.hour - b.hour;&lt;br /&gt;
	return diff;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; D I F F _ D A T E &amp;gt;------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
calculates the difference between two dates; returns a table of the difference in diff.year, diff.month, and diff.day&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function diff_date (a, b)&lt;br /&gt;
	local diff = {}&lt;br /&gt;
	diff.day = a.day - b.day;&lt;br /&gt;
	if diff.day &amp;lt; 0 then&lt;br /&gt;
		a.month = a.month - 1;&lt;br /&gt;
		if a.month &amp;lt; 1 then	&lt;br /&gt;
			a.year = a.year - 1;												-- borrow a month from years&lt;br /&gt;
			a.month = a.month + 12;&lt;br /&gt;
		end&lt;br /&gt;
		diff.day = diff.day + get_days_in_month (a.year, a.month);				-- borrow all of the days from the *previous* month&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	diff.month = a.month - b.month;&lt;br /&gt;
	if diff.month &amp;lt; 0 then&lt;br /&gt;
		a.year = a.year - 1;													-- borrow a month from years&lt;br /&gt;
		diff.month = diff.month + 12;&lt;br /&gt;
	end&lt;br /&gt;
	diff.year = a.year - b.year;&lt;br /&gt;
	return diff;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ V A L I D _ D A T E _ T I M E &amp;gt;------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Validate date/time.  Also, determine if we have all of the necessary date/time componants.  Minimal required&lt;br /&gt;
date/time is |year=.&lt;br /&gt;
&lt;br /&gt;
For dates, these are required (all other variations emit an error message):&lt;br /&gt;
	|year= or&lt;br /&gt;
	|year= |month= or&lt;br /&gt;
	|year= |month= |day=&lt;br /&gt;
	&lt;br /&gt;
If time is included, these are required (all other variations emit an error message):&lt;br /&gt;
	|hour= or&lt;br /&gt;
	|hour= |minute= or&lt;br /&gt;
	|hour= |minute= |second=&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_valid_date_time (year, month, day, hour, minute, second)&lt;br /&gt;
	if not year or (not month and day) then										-- must have YMD, YM, or Y&lt;br /&gt;
		return false;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	year = tonumber (year);&lt;br /&gt;
	if not year or 1582 &amp;gt; year or 9999 &amp;lt; year then return false; end			-- must be four digits in gregorian calander&lt;br /&gt;
	&lt;br /&gt;
	if month then&lt;br /&gt;
		month = tonumber (month);&lt;br /&gt;
		if not month or 1 &amp;gt; month or 12 &amp;lt; month then return false; end			-- 1 to 12&lt;br /&gt;
	end&lt;br /&gt;
	if day then&lt;br /&gt;
		day = tonumber (day);&lt;br /&gt;
		if not day or 1 &amp;gt; day or get_days_in_month (year, month) &amp;lt; day then		-- 1 to 28, 29, 30, or 31 depending on month&lt;br /&gt;
			return false;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if ((minute or second) and not hour) or (not minute and second) then		-- must have H:M:S or H:M or H or none at all&lt;br /&gt;
			return false;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if hour then&lt;br /&gt;
		hour = tonumber (hour);&lt;br /&gt;
		if not hour or 0 &amp;gt; hour or 23 &amp;lt; hour then return false; end				-- 0 to 23&lt;br /&gt;
	end&lt;br /&gt;
	if minute then&lt;br /&gt;
		minute = tonumber (minute);&lt;br /&gt;
		if not minute or 0 &amp;gt; minute or 59 &amp;lt; minute then return false; end		-- 0 to 59&lt;br /&gt;
	end&lt;br /&gt;
	if second then&lt;br /&gt;
		second = tonumber (second);&lt;br /&gt;
		if not second or 0 &amp;gt; second or 59 &amp;lt; second then return false; end		-- 0 to 59&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return true;&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; M A I N &amp;gt;----------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Supported parameters:&lt;br /&gt;
	date and time parameters:&lt;br /&gt;
		|year= (required), |month=, |day=&lt;br /&gt;
		|hour=, |minute=, |second=&lt;br /&gt;
		|utc offset=&lt;br /&gt;
		|duration=&lt;br /&gt;
&lt;br /&gt;
	presentation parameters:&lt;br /&gt;
		|color=&lt;br /&gt;
		&lt;br /&gt;
	wrapping-text parameters:&lt;br /&gt;
		|event lead= – text ahead of countdown text while event is in progress; countdown text is time to end of event; default is static_text.ends&lt;br /&gt;
		|event tail= – text that follows countdown text while event is in progress; default is empty string&lt;br /&gt;
		|expired= - display text when event is in the past; default is static_text.passed; when |duration= is set then default is static_text.ended&lt;br /&gt;
		|lead= – text ahead of countdown text; default is static_text.begins; overridden by |event lead= while event is in progress;&lt;br /&gt;
		|tail= – text that follows countdown text; default is empty string; overridden by |event tail= while event is in progress&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = getArgs(frame)&lt;br /&gt;
&lt;br /&gt;
	if false == is_valid_date_time (args.year, args.month, args.day, args.hour, args.minute, args.second) then	-- validate our inputs; minimal requirement is |year=&lt;br /&gt;
		return  static_text.invalid;&lt;br /&gt;
	end&lt;br /&gt;
																				-- convert event time parameters to seconds; use default January 1 @ 0h for defaults if not provided&lt;br /&gt;
	local event = os.time({year=args.year, month=args.month or 1, day=args.day or 1, hour=args.hour or 0, min=args.minute, sec=args.second});	-- convert to seconds&lt;br /&gt;
	if args['utc offset'] then&lt;br /&gt;
		event = event - utc_offset(args['utc offset']);							-- adjust event time to UTC from local time&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if 'none' == args.expired then&lt;br /&gt;
		args.expired = '';&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event &amp;lt; os.time () then													-- if event time is in the past&lt;br /&gt;
		if not args.duration then&lt;br /&gt;
			return args.expired or static_text.passed;							-- if the event start time has passed, we're done&lt;br /&gt;
		else&lt;br /&gt;
			event = event + duration (args.duration);							-- calculate event ending time&lt;br /&gt;
			if event &amp;lt; os.time () then&lt;br /&gt;
				return args.expired or static_text.ended;						-- if the event start time + duration has passed, we're done&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	else																		-- here when event has not yet started or occured&lt;br /&gt;
		if not args.lead then&lt;br /&gt;
			if args.duration then&lt;br /&gt;
				args.lead = static_text.begins;									-- default lead text when |duration= set but |lead= not set&lt;br /&gt;
			else&lt;br /&gt;
				args.lead = static_text.eventtime;								-- default lead text when |duration= and |lead= not set&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		args.duration = nil;													-- event not yet started; unset so that we render text around the countdown correctly&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local today = os.date ('*t');												--fetch table of current date time parameters from the server&lt;br /&gt;
	local event_time = os.date ('*t', event);									--fetch table of event date time parameters from the server&lt;br /&gt;
	local hms_til_start = diff_time (event_time, today)							-- table of time difference (future time - current time)&lt;br /&gt;
	if hms_til_start.hour &amp;lt; 0 then												-- will be negative if we need to borrow hours from day&lt;br /&gt;
		hms_til_start.hour = hms_til_start.hour + 24;							-- borrow a day's worth of hours from event start date&lt;br /&gt;
		event_time.day = event_time.day - 1;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local ymd_til_start = diff_date (event_time, today)							-- table of date difference (future date - current date)&lt;br /&gt;
&lt;br /&gt;
	local result = {}															-- results table with some formatting; values less than one are not added to the table &lt;br /&gt;
	table.insert (result, format_unit (ymd_til_start.year, 'year'));			-- add date parameters&lt;br /&gt;
	table.insert (result, format_unit (ymd_til_start.month, 'month'));&lt;br /&gt;
	table.insert (result, format_unit (ymd_til_start.day, 'day'));&lt;br /&gt;
	&lt;br /&gt;
	local count = #result;														-- zero if less than 24 hours to event; when less than 24 hours display all non-zero time units&lt;br /&gt;
	table.insert (result, format_unit (hms_til_start.hour, 'hour'));			-- always include hours if it is not zero&lt;br /&gt;
	if args.hour or 0 == count then												-- if event start hour provided in template, show non-zero minutes&lt;br /&gt;
		table.insert (result, format_unit (hms_til_start.min, 'minute'));&lt;br /&gt;
	end&lt;br /&gt;
	if (args.minute and args.hour) or 0 == count then							-- if event start hour and minute provided in template, show non-zero seconds&lt;br /&gt;
		table.insert (result, format_unit (hms_til_start.sec, 'second'));&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	result = table.concat (result, ', ');&lt;br /&gt;
	result = mw.ustring.gsub(result, '(%d+)', '&amp;lt;span style=&amp;quot;color: ' .. (args.color or 'blue') .. '; font-weight: bold;&amp;quot;&amp;gt;%1&amp;lt;/span&amp;gt;')&lt;br /&gt;
	local refreshLink = mw.title.getCurrentTitle():fullUrl{action = 'purge'}&lt;br /&gt;
	refreshLink = mw.ustring.format(' &amp;lt;sup&amp;gt;[&amp;lt;span class=&amp;quot;plainlinks&amp;quot;&amp;gt;[%s refresh]&amp;lt;/span&amp;gt;]&amp;lt;/sup&amp;gt;', refreshLink)&lt;br /&gt;
&lt;br /&gt;
	if not args.duration then													-- will be nil if event hasn't started yet or |duration= not specified&lt;br /&gt;
		args.lead = args.lead or static_text.eventtime;							-- use default begins text&lt;br /&gt;
	else&lt;br /&gt;
		args.lead = args['event lead'] or static_text.ends;						-- event has started use |event lead= text or default ends text&lt;br /&gt;
		args.tail = args['event tail'];											-- and use |event tail= text&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if 'none' == args.lead then													-- here, if either arg.lead and args['event lead'] were set to keyword'none'&lt;br /&gt;
		args.lead = '';															-- set lead text to empty string&lt;br /&gt;
	elseif args.lead then&lt;br /&gt;
		args.lead = args.lead .. ' ';											-- add a space&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if args.tail then&lt;br /&gt;
		args.tail = ' ' .. args.tail;											-- add a space&lt;br /&gt;
	else&lt;br /&gt;
		args.tail = '';															-- empty string for concatenation&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return args.lead .. result .. args.tail .. refreshLink;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Zoran</name></author>
	</entry>
</feed>