มอดูล:Countdown-ymd

-- This module powers .
require('Module:No globals')
local p = {}
local static_text = {
	'eventtime' = 'Time to event:',
	'begins' = 'Event begins in',												-- when |duration= is set
	'ends' = 'Event ends in',													-- when |duration= is set
	'ended' = 'Event has ended.',												-- when |duration= is set
	'passed' = 'Event time has passed.',
	'invalid' = 'Error: invalid date and/or time',
	}
local getArgs = require('Module:Arguments').getArgs
------------------------------------------------------------------------------------
Concatenates a singular or plural label to a time/date unit: unit label or unit labels.  If unit is 0 or less, returns nil
local function format_unit (unit, label)
	if 1 > unit then															-- less than one so don't display
		return nil;																-- return nil so the result of this call isn't stuffed into results table by table.insert
	elseif 1 == unit then
		return unit .. ' ' .. label;											-- only one unit so return singular label
	else
		return unit .. ' ' .. label .. 's';										-- multiple units so return plural label
	end
end
--duration=
TODO: Why are we even considering seconds and minutes?  Would it be better to specify and end time?
local function duration (duration)
	local number, unit = duration:match ('(%d+)%s+(%a*)');
	
	if not number then
		return 0;																-- duration not properly specified
	elseif not unit then
		return number;															-- unit not defined, assume seconds
	end
	
	if unit:match ('days?') then
		return number * 86400;
	elseif unit:match ('hours?') then
		return number * 3600;
	elseif unit:match ('minutes?') then
		return number * 60;
	elseif unit:match ('seconds?') then
		return number;
	else
		return 0;																-- unknown unit
	end
end
--------------------------------------------------------------------------------------
Returns offset from UTC in seconds.  If 'utc offset' parameter is out of range or malformed, returns 0.
TODO: Return a success/fail flag so we can emit an error message?
local function utc_offset (offset)
	local sign, utc_offset_hr, utc_offset_min;
	
	if offset:match('^%+%-%d%d:%d%d$') then									-- formal style: sign, hours colon minutes all required
		sign, utc_offset_hr, utc_offset_min = offset:match('^(%+%-)(%d%d):(%d%d)$');
	elseif offset:match('^%+%-?%d?%d:?%d%d$') then							-- informal: sign and colon optional, 1- or 2-digit hours, and minutes 
		sign, utc_offset_hr, utc_offset_min = offset:match('^(%+%-?)(%d?%d):?(%d%d)$');
	elseif offset:match('^%+%-?%d?%d$') then									-- informal: sign optional, 1- or 2-digit hours only
		sign, utc_offset_hr = offset:match('^(%+%-?)(%d?%d)$');
		utc_offset_min = 0;														-- because not included in parameter, set it to 0 minutes
	else
		return 0;																-- malformed so return 0 seconds
	end
	
	utc_offset_hr = tonumber (utc_offset_hr);
	utc_offset_min = tonumber (utc_offset_min);
	
	if 12 --------------------------------------------
Returns the number of days in the month where month is a number 1–12 and year is four-digit Gregorian calendar.
Accounts for leap year.
local function get_days_in_month (year, month)
	local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	
	year = tonumber (year);														-- force these to be numbers just in case
	month = tonumber (month);
	if (2 == month) then														-- if February
		if (0 == (year%4) and (0 ~= (year%100) or 0 == (year%400))) then		-- is year a leap year?
			return 29;															-- if leap year then 29 days in February
		end
	end
	return days_in_month month;
end
----------------------------------------------------------------------------------------
calculates the difference between two times; returns a table of the differences in diff.hour, diff.min, and diff.sec
local function diff_time (a, b)
	local diff = {}
	diff.sec = a.sec - b. sec;
	if diff.sec ------------------------------------------------------------
calculates the difference between two dates; returns a table of the difference in diff.year, diff.month, and diff.day
local function diff_date (a, b)
	local diff = {}
	diff.day = a.day - b.day;
	if diff.day ------------------------------------------
Validate date/time.  Also, determine if we have all of the necessary date/time componants.  Minimal required
date/time is |year=.
For dates, these are required (all other variations emit an error message):
	|year= or
	|year= |month= or
	|year= |month= |day=
	
If time is included, these are required (all other variations emit an error message):
	|hour= or
	|hour= |minute= or
	|hour= |minute= |second=
local function is_valid_date_time (year, month, day, hour, minute, second)
	if not year or (not month and day) then										-- must have YMD, YM, or Y
		return false;
	end
	year = tonumber (year);
	if not year or 1582 > year or 9999  month or 12  day or get_days_in_month (year, month)  hour or 23  minute or 59  second or 59 ----------------------------------------------------------------------
Supported parameters:
	date and time parameters:
		
day=
		
second=
		
utc offset=
		
duration=
	presentation parameters:
		
color=
		
	wrapping-text parameters:
		
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
		
event tail= – text that follows countdown text while event is in progress; default is empty string
		
duration= is set then default is static_text.ended
		
event lead= while event is in progress;
		
event tail= while event is in progress
function p.main(frame)
	local args = getArgs(frame)
	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=
		return  static_text.invalid;
	end
																				-- convert event time parameters to seconds; use default January 1 @ 0h for defaults if not provided
	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
	if args'utc offset' then
		event = event - utc_offset(args'utc offset');							-- adjust event time to UTC from local time
	end
	
	if 'none' == args.expired then
		args.expired = ;
	end
	
	if event %1')
	local refreshLink = mw.title.getCurrentTitle():fullUrl{action = 'purge'}
	refreshLink = mw.ustring.format(' %s refresh', refreshLink)
	if not args.duration then													-- will be nil if event hasn't started yet or |duration= not specified
		args.lead = args.lead or static_text.eventtime;							-- use default begins text
	else
		args.lead = args'event lead' or static_text.ends;						-- event has started use |event lead= text or default ends text
		args.tail = args'event tail';											-- and use |event tail= text
	end
	if 'none' == args.lead then													-- here, if either arg.lead and args'event lead' were set to keyword'none'
		args.lead = ;															-- set lead text to empty string
	elseif args.lead then
		args.lead = args.lead .. ' ';											-- add a space
	end
	if args.tail then
		args.tail = ' ' .. args.tail;											-- add a space
	else
		args.tail = ;															-- empty string for concatenation
	end
	
	return args.lead .. result .. args.tail .. refreshLink;
end
return p