Module:Countdown-ymd/sandbox
Appearance
![]() | This is the module sandbox page for Module:Countdown-ymd (diff). |
-- This module powers {{countdown}}.
require('Module:No globals')
local p = {}
local static_text = {
['event'] = '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.',
}
local getArgs = require('Module:Arguments').getArgs
--[[--------------------------< F O R M A T _ U N I T >--------------------------------------------------------
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
--[[--------------------------< U T C _ O F F S E T >----------------------------------------------------------
Returns length of event duration in seconds. If 'duration unit' not defined, assumes seconds. If 'duration unit'
defined but not one of day, days, hour, hours, minute, minutes, second, seconds then returns zero.
]]
local function duration (duration, unit)
if not unit then
return duration; -- unit not defined, assume seconds
end
if unit:match ('days?') then
return duration * 86400;
elseif unit:match ('hours?') then
return duration * 3600;
elseif unit:match ('minutes?') then
return duration * 60;
elseif unit:match ('seconds?') then
return duration;
else
return 0;
end
end
--[[--------------------------< U T C _ O F F S E T >----------------------------------------------------------
Returns offset from UTC in seconds. If 'utc offset' parameter is out of range or malformed, returns 0.
]]
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 < utc_offset_hr or 59 < utc_offset_min then -- hour and minute range checks
return 0;
end
if '-' == sign then
sign = -1; -- negative west offset
else
sign = 1; -- + or sign omitted east offset
end
utc_offset_hr = sign * (utc_offset_hr * 3600); -- utc offset hours * seconds/hour
utc_offset_min = sign * (utc_offset_min * 60); -- utc offset minutes * seconds/minute
return utc_offset_hr + utc_offset_min; -- return the UTC offset adjustment in seconds
end
--[[--------------------------< G E T _ D A Y S _ I N _ M O N T H >--------------------------------------------
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};
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
--[[--------------------------< D I F F _ T I M E >------------------------------------------------------------
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 < 0 then
diff.sec = diff.sec + 60; -- borrow from minutes
a.min = a.min - 1;
if a.min < 0 then
a.min = a.min + 60; -- borrow from hours
a.hour = a.hour - 1;
end
end
diff.min = a.min - b.min;
if diff.min < 0 then
diff.min = diff.min + 60; -- borrow from hours
a.hour = a.hour - 1;
end
diff.hour = a.hour - b.hour;
return diff;
end
--[[--------------------------< D I F F _ D A T E >------------------------------------------------------------
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 < 0 then
a.month = a.month - 1;
if a.month < 1 then
a.year = a.year - 1; -- borrow a month from years
a.month = a.month + 12;
end
diff.day = diff.day + get_days_in_month (a.year, a.month); -- borrow all of the days from the *previous* month
end
diff.month = a.month - b.month;
if diff.month < 0 then
a.year = a.year - 1; -- borrow a month from years
diff.month = diff.month + 12;
end
diff.year = a.year - b.year;
return diff;
end
--[[--------------------------< I S _ V A L I D _ D A T E _ T I M E >------------------------------------------
]]
local function is_valid_date_time (year, month, day, hour, minute, second)
if not year:match ('^%d%d%d%d$') then return false; end -- must be four digits
if not day:match ('^%d%d?$') then return false; end -- must be one or two digits
local month_length = get_days_in_month (tonumber(year), tonumber(month)); -- nil if invalid month
-- if not month_length or month_length < day or 1 > day then return false; end -- must be valid month and valid day for that month
if not month_length then return false; end -- must be valid month and valid day for that month
-- if hour then
-- if not hour:match ('%d%d?') then return false; end -- must be one or two digits
-- if 23 < hour then return false; end -- 0 - 23 hours
-- end
-- if minute then
-- if not minute:match ('%d%d?') then return false; end -- must be one or two digits
-- if 59 < minute then return false; end -- 0 - 59 minutes
-- end
-- if second then
-- if not second:match ('%d%d?') then return false; end -- must be one or two digits
-- if 59 < second then return false; end -- 0 - 59 seconds
-- end
return true;
end
------------------------------< M A I N >----------------------------------------------------------------------
function p.main(frame)
local args = getArgs(frame)
if not (args.year and args.month and args.day) then -- exit with error message if we don't have these minimal parameters
return '<span style="font-size:100%" class="error">Error: year, month, and day must be specified</span>'
end
if ((args.minute or args.second) and not args.hour) or (not args.minute and args.second) then -- exit with error message if we don't have these minimal parameters
return '<span style="font-size:100%" class="error">Error: missing time parameter</span>'
end
if false == is_valid_date_time (args.year, args.month, args.day, args.hour, args.minute, args.second) then
return '<span style="font-size:100%" class="error">Error: invalid date and/or time: ' .. args.year .. ' ' .. args.month .. ' ' .. get_days_in_month (tonumber(args.year), tonumber(args.month)) or 'nil' ..'</span>'
end
local event = os.time({year=args.year, month=args.month, day=args.day, 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
end
if event < os.time () then
if not args.duration then
return args.event or static_text.passed; -- if the event start time has passed, we're done
else
event = event + duration (args.duration, args['duration unit']);
if event < os.time () then
return args.event or static_text.ended; -- if the event start time + duration has passed, we're done
end
end
else
args.duration = nil; -- unset so that we render text around the countdown correctly
end
local today = os.date ('*t'); --fetch table of current date time parameters from the server
local event_time = os.date ('*t', event); --fetch table of event date time parameters from the server
local hms_til_start = diff_time (event_time, today) -- table of time difference (future time - current time)
if hms_til_start.hour < 0 then -- will be negative if we need to borrow hours from day
hms_til_start.hour = hms_til_start.hour + 24; -- borrow a day's worth of hours from event start date
event_time.day = event_time.day - 1;
end
local ymd_til_start = diff_date (event_time, today) -- table of date difference (future date - current date)
local result = {} -- results table with some formatting; values less than one are not added to the table
table.insert (result, format_unit (ymd_til_start.year, 'year')); -- add date parameters
table.insert (result, format_unit (ymd_til_start.month, 'month'));
table.insert (result, format_unit (ymd_til_start.day, 'day'));
local count = #result; -- zero if less than 24 hours to event; when less than 24 hours display all non-zero time units
table.insert (result, format_unit (hms_til_start.hour, 'hour')); -- always include hours if it is not zero
if args.hour or 0 == count then -- if event start hour provided in template, show non-zero minutes
table.insert (result, format_unit (hms_til_start.min, 'minute'));
end
if (args.minute and args.hour) or 0 == count then -- if event start hour and minute provided in template, show non-zero seconds
table.insert (result, format_unit (hms_til_start.sec, 'second'));
end
result = table.concat (result, ', ');
result = mw.ustring.gsub(result, '(%d+)', '<span style="color: ' .. (args.color or '#F00') .. '; font-weight: bold;">%1</span>')
local refreshLink = mw.title.getCurrentTitle():fullUrl{action = 'purge'}
refreshLink = mw.ustring.format(' <small><span class="plainlinks"> ([%s refresh])</span></small>', refreshLink)
if 'none' == args.event then
args.event = ''; -- keyword none sets prefix to empty string
elseif not args.event then -- if not defined
if not args.duration then -- will be nil if event hasn't started yet or |duration= not specified
args.event = static_text.begins; -- use default begin text
else
args.event = static_text.ends; -- use default end text
end
else
args.event = args.event .. ' '; -- add a space
end
return args.event .. result .. refreshLink;
end
return p