<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://safernicotine.wiki/mediawiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ATime</id>
	<title>Module:Time - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://safernicotine.wiki/mediawiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ATime"/>
	<link rel="alternate" type="text/html" href="https://safernicotine.wiki/mediawiki/index.php?title=Module:Time&amp;action=history"/>
	<updated>2026-04-04T17:03:44Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.0</generator>
	<entry>
		<id>https://safernicotine.wiki/mediawiki/index.php?title=Module:Time&amp;diff=36329&amp;oldid=prev</id>
		<title>imported&gt;WOSlinker: use require(&#039;strict&#039;) instead of require(&#039;Module:No globals&#039;)</title>
		<link rel="alternate" type="text/html" href="https://safernicotine.wiki/mediawiki/index.php?title=Module:Time&amp;diff=36329&amp;oldid=prev"/>
		<updated>2022-10-21T21:55:54Z</updated>

		<summary type="html">&lt;p&gt;use require(&amp;#039;strict&amp;#039;) instead of require(&amp;#039;Module:No globals&amp;#039;)&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en-GB&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 22:55, 21 October 2022&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot;&gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;require (&#039;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;Module:No globals&lt;/del&gt;&#039;)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;require(&#039;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;strict&lt;/ins&gt;&#039;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local yesno = require(&amp;#039;Module:Yesno&amp;#039;)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local yesno = require(&amp;#039;Module:Yesno&amp;#039;)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local getArgs = require (&amp;#039;Module:Arguments&amp;#039;).getArgs&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local getArgs = require (&amp;#039;Module:Arguments&amp;#039;).getArgs&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;

&lt;!-- diff cache key uk_vapewiki:diff:1.41:old-6924:rev-36329:php=table --&gt;
&lt;/table&gt;</summary>
		<author><name>imported&gt;WOSlinker</name></author>
	</entry>
	<entry>
		<id>https://safernicotine.wiki/mediawiki/index.php?title=Module:Time&amp;diff=6924&amp;oldid=prev</id>
		<title>Richardpruen: 1 revision imported</title>
		<link rel="alternate" type="text/html" href="https://safernicotine.wiki/mediawiki/index.php?title=Module:Time&amp;diff=6924&amp;oldid=prev"/>
		<updated>2021-08-29T04:18:45Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en-GB&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 05:18, 29 August 2021&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;4&quot; class=&quot;diff-notice&quot; lang=&quot;en-GB&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;!-- diff cache key uk_vapewiki:diff:1.41:old-6923:rev-6924 --&gt;
&lt;/table&gt;</summary>
		<author><name>Richardpruen</name></author>
	</entry>
	<entry>
		<id>https://safernicotine.wiki/mediawiki/index.php?title=Module:Time&amp;diff=6923&amp;oldid=prev</id>
		<title>Wikipedia&gt;Pppery: Sync from sandbox per edit request: support |dst=always; use Module:Yesno to handle more truthy values</title>
		<link rel="alternate" type="text/html" href="https://safernicotine.wiki/mediawiki/index.php?title=Module:Time&amp;diff=6923&amp;oldid=prev"/>
		<updated>2020-05-25T02:32:35Z</updated>

		<summary type="html">&lt;p&gt;Sync from sandbox per edit request: support |dst=always; use &lt;a href=&quot;/mediawiki/index.php/Module:Yesno&quot; title=&quot;Module:Yesno&quot;&gt;Module:Yesno&lt;/a&gt; to handle more truthy values&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;require (&amp;#039;Module:No globals&amp;#039;)&lt;br /&gt;
local yesno = require(&amp;#039;Module:Yesno&amp;#039;)&lt;br /&gt;
local getArgs = require (&amp;#039;Module:Arguments&amp;#039;).getArgs&lt;br /&gt;
&lt;br /&gt;
local tz = {};																	-- holds local copy of the specified timezone table from tz_data{}&lt;br /&gt;
local cfg = {};																	-- for internationalization &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; I S _ S E T &amp;gt;------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Whether variable is set or not.  A variable is set when it is not nil and not empty.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function is_set( var )&lt;br /&gt;
	return not (nil == var or &amp;#039;&amp;#039; == var);&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; S U B S T I T U T E &amp;gt;----------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Populates numbered arguments in a message string using an argument table.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function substitute (msg, args)&lt;br /&gt;
	return args and mw.message.newRawMessage (msg, args):plain() or msg;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; E R R O R _ M S G &amp;gt;------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
create an error message&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function error_msg (msg, arg)&lt;br /&gt;
	return substitute (cfg.err_msg, substitute (cfg.err_text[msg], arg))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; D E C O D E _ D S T _ E V E N T &amp;gt;----------------------------------------------&lt;br /&gt;
&lt;br /&gt;
extract ordinal, day-name, and month from daylight saving start/end definition string as digits:&lt;br /&gt;
	Second Sunday in March&lt;br /&gt;
returns&lt;br /&gt;
	2 0 3&lt;br /&gt;
&lt;br /&gt;
Casing doesn&amp;#039;t matter but the form of the string does:&lt;br /&gt;
	&amp;lt;ordinal&amp;gt; &amp;lt;day&amp;gt; &amp;lt;any single word&amp;gt; &amp;lt;month&amp;gt; – all are separated by spaces&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function decode_dst_event (dst_event_string)&lt;br /&gt;
	local ord, day, month;&lt;br /&gt;
	&lt;br /&gt;
	dst_event_string = dst_event_string:lower();								-- force the string to lower case because that is how the tables above are indexed&lt;br /&gt;
	ord, day, month = dst_event_string:match (&amp;#039;([%a%d]+)%s+(%a+)%s+%a+%s+(%a+)&amp;#039;);&lt;br /&gt;
	&lt;br /&gt;
	if not (is_set (ord) and is_set (day) and is_set (month)) then				-- if one or more of these not set, then pattern didn&amp;#039;t match&lt;br /&gt;
		return nil;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return cfg.ordinals[ord], cfg.days[day], cfg.months[month];&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;
&lt;br /&gt;
--[[--------------------------&amp;lt; G E T _ D S T _ M O N T H _ D A Y &amp;gt;--------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Return the date (month and day of the month) for the day that is the ordinal (nth) day-name in month (second&lt;br /&gt;
Friday in June) of the current year&lt;br /&gt;
&lt;br /&gt;
timestamp is today&amp;#039;s date-time number from os.time(); used to supply year&lt;br /&gt;
timezone is the timezone parameter value from the template call&lt;br /&gt;
&lt;br /&gt;
Equations used in this function taken from Template:Weekday_in_month&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function get_dst_month_day (timestamp, start)&lt;br /&gt;
	local ord, weekday_num, month;&lt;br /&gt;
	local first_day_of_dst_month_num;&lt;br /&gt;
	local last_day_of_dst_month_num;&lt;br /&gt;
	local days_in_month;&lt;br /&gt;
	local year;&lt;br /&gt;
&lt;br /&gt;
	if true == start then&lt;br /&gt;
		ord, weekday_num, month = decode_dst_event (tz.dst_begins);				-- get start string and convert to digits&lt;br /&gt;
	else&lt;br /&gt;
		ord, weekday_num, month = decode_dst_event (tz.dst_ends);				-- get end string and convert to digits&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if not (is_set (ord) and is_set (weekday_num) and is_set (month)) then&lt;br /&gt;
		return nil;																-- could not decode event string&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	year = os.date (&amp;#039;%Y&amp;#039;, timestamp);&lt;br /&gt;
&lt;br /&gt;
	if -1 == ord then		-- j = t + 7×(n + 1) - (wt - w) mod 7				-- if event occurs on the last day-name of the month (&amp;#039;last Sunday of October&amp;#039;)&lt;br /&gt;
		days_in_month = get_days_in_month (year, month);&lt;br /&gt;
		last_day_of_dst_month_num = os.date (&amp;#039;%w&amp;#039;, os.time ({[&amp;#039;year&amp;#039;]=year, [&amp;#039;month&amp;#039;]=month, [&amp;#039;day&amp;#039;]=days_in_month}));&lt;br /&gt;
		return month, days_in_month + 7*(ord + 1) - ((last_day_of_dst_month_num - weekday_num) % 7);&lt;br /&gt;
	else	-- j = 7×n - 6 + (w - w1) mod 7&lt;br /&gt;
		first_day_of_dst_month_num = os.date (&amp;#039;%w&amp;#039;, os.time ({[&amp;#039;year&amp;#039;]=year, [&amp;#039;month&amp;#039;]=month, [&amp;#039;day&amp;#039;]=1}))&lt;br /&gt;
		return month, 7 * ord - 6 + (weekday_num - first_day_of_dst_month_num) % 7;		-- return month and calculated date&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; G E T _ U T C _ O F F S E T &amp;gt;--------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Get utc offset in hours and minutes, convert to seconds.  If the offset can&amp;#039;t be converted return nil.&lt;br /&gt;
TODO: return error message?&lt;br /&gt;
TODO: limit check this? +/-n hours?&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function get_utc_offset ()&lt;br /&gt;
	local sign;&lt;br /&gt;
	local hours;&lt;br /&gt;
	local minutes;&lt;br /&gt;
	&lt;br /&gt;
	sign, hours, minutes = mw.ustring.match (tz.utc_offset, &amp;#039;([%+%-±−]?)(%d%d):(%d%d)&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
	if &amp;#039;-&amp;#039; == sign then sign = -1; else sign = 1; end&lt;br /&gt;
	if is_set (hours) and is_set (minutes) then&lt;br /&gt;
		return sign * ((hours * 3600) + (minutes * 60));&lt;br /&gt;
	else&lt;br /&gt;
		return nil;																-- we require that all timezone tables have what appears to be a valid offset&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; M A K E _ D S T _ T I M E S T A M P S &amp;gt;----------------------------------------&lt;br /&gt;
&lt;br /&gt;
Return UTC timestamps for the date/time of daylight saving time events (beginning and ending).  These timestamps&lt;br /&gt;
will be compared to current UTC time.  A dst timestamp is the date/time in seconds UTC for the timezone at the&lt;br /&gt;
hour of the dst event.&lt;br /&gt;
&lt;br /&gt;
For dst rules that specify local event times, the timestamp is the sum of:&lt;br /&gt;
	timestamp = current year + dst_month + dst_day + dst_time (all in seconds) local time&lt;br /&gt;
Adjust local time to UTC by subtracting utc_offset:&lt;br /&gt;
	timestamp = timestamp - utc_offset (in seconds)&lt;br /&gt;
For dst_end timestamp, subtract an hour for DST&lt;br /&gt;
	timestamp = timestamp - 3600 (in seconds)&lt;br /&gt;
&lt;br /&gt;
For dst rules that specify utc event time the process is the same except that utc offset is not subtracted.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function make_dst_timestamps (timestamp)&lt;br /&gt;
	local dst_begin, dst_end;													-- dst begin and end time stamps &lt;br /&gt;
	local year;																	-- current year&lt;br /&gt;
	local dst_b_month, dst_e_month, dst_day;									-- month and date of dst event&lt;br /&gt;
	local dst_hour, dst_minute;													-- hour and minute of dst event on year-dst_month-dst_day&lt;br /&gt;
	local invert = false;														-- flag to pass on when dst_begin month is numerically larger than dst_end month (southern hemisphere)&lt;br /&gt;
	local utc_offset;&lt;br /&gt;
	local utc_flag;&lt;br /&gt;
&lt;br /&gt;
	year = os.date (&amp;#039;%Y&amp;#039;, timestamp);											-- current year&lt;br /&gt;
	utc_offset = get_utc_offset ();												-- in seconds&lt;br /&gt;
	if not is_set (utc_offset) then												-- utc offset is a required timezone property&lt;br /&gt;
		return nil;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	dst_b_month, dst_day = get_dst_month_day (timestamp, true);					-- month and day that dst begins&lt;br /&gt;
	if not is_set (dst_b_month) then&lt;br /&gt;
		return nil;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	dst_hour, dst_minute = tz.dst_time:match (&amp;#039;(%d%d):(%d%d)&amp;#039;);					-- get dst time&lt;br /&gt;
	utc_flag = tz.dst_time:find (&amp;#039;[Uu][Tt][Cc]%s*$&amp;#039;);							-- set flag when dst events occur at a specified utc time&lt;br /&gt;
&lt;br /&gt;
	dst_begin = os.time ({[&amp;#039;year&amp;#039;] = year, [&amp;#039;month&amp;#039;] = dst_b_month, [&amp;#039;day&amp;#039;] = dst_day, [&amp;#039;hour&amp;#039;] = dst_hour, [&amp;#039;min&amp;#039;] = dst_minute});	-- form start timestamp&lt;br /&gt;
	if not is_set (utc_flag) then												-- if dst events are specified to occur at local time&lt;br /&gt;
		dst_begin = dst_begin - utc_offset;										-- adjust local time to utc by subtracting utc offset&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	dst_e_month, dst_day = get_dst_month_day (timestamp, false);				-- month and day that dst ends&lt;br /&gt;
	if not is_set (dst_e_month) then&lt;br /&gt;
		return nil;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if is_set (tz.dst_e_time) then&lt;br /&gt;
		dst_hour, dst_minute = tz.dst_e_time:match (&amp;#039;(%d%d):(%d%d)&amp;#039;);			-- get ending dst time; this one for those locales that use different start and end times&lt;br /&gt;
		utc_flag = tz.dst_e_time:find (&amp;#039;[Uu][Tt][Cc]%s*$&amp;#039;);						-- set flag if dst is pegged to utc time&lt;br /&gt;
	end	&lt;br /&gt;
&lt;br /&gt;
	dst_end = os.time ({[&amp;#039;year&amp;#039;] = year, [&amp;#039;month&amp;#039;] = dst_e_month, [&amp;#039;day&amp;#039;] = dst_day, [&amp;#039;hour&amp;#039;] = dst_hour, [&amp;#039;min&amp;#039;] = dst_minute});	-- form end timestamp&lt;br /&gt;
	if not is_set (utc_flag) then												-- if dst events are specified to occur at local time&lt;br /&gt;
		dst_end = dst_end - 3600;												-- assume that local end time is DST so adjust to local ST&lt;br /&gt;
		dst_end = dst_end - utc_offset;											-- adjust local time to utc by subtracting utc offset&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	if dst_b_month &amp;gt; dst_e_month then&lt;br /&gt;
		invert = true;															-- true for southern hemisphere eg: start September YYYY end April YYYY+1&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return dst_begin, dst_end, invert;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; G E T _ T E S T _ T I M E &amp;gt;----------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
decode ISO formatted date/time into a table suitable for os.time().  Fallback to {{Timestamp}} format.&lt;br /&gt;
For testing, this time is UTC just as is returned by the os.time() function.&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function get_test_time (iso_date)&lt;br /&gt;
	local year, month, day, hour, minute, second;&lt;br /&gt;
&lt;br /&gt;
	year, month, day, hour, minute, second = iso_date:match (&amp;#039;(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)&amp;#039;);&lt;br /&gt;
	if not year then&lt;br /&gt;
		year, month, day, hour, minute, second = iso_date:match (&amp;#039;^(%d%d%d%d)(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)$&amp;#039;);&lt;br /&gt;
		if not year then&lt;br /&gt;
			return nil;															-- test time did not match the specified patterns&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return {[&amp;#039;year&amp;#039;] = year, [&amp;#039;month&amp;#039;] = month, [&amp;#039;day&amp;#039;] = day, [&amp;#039;hour&amp;#039;] = hour, [&amp;#039;min&amp;#039;] = minute, [&amp;#039;sec&amp;#039;] = second};&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[----------------------&amp;lt; G E T _ F U L L _ U T C _ O F F S E T &amp;gt;-----------------------------------------------&lt;br /&gt;
&lt;br /&gt;
Creates a standard UTC offset from numerical inputs, for function time to convert to a table.  Expected inputs shall have the form:&lt;br /&gt;
	&amp;lt;sign&amp;gt;&amp;lt;hour&amp;gt;&amp;lt;separator&amp;gt;&amp;lt;portion&amp;gt;&lt;br /&gt;
where:&lt;br /&gt;
	&amp;lt;sign&amp;gt; – optional; one of the characters: &amp;#039;+&amp;#039;, &amp;#039;-&amp;#039; (hyphen), &amp;#039;±&amp;#039;, &amp;#039;−&amp;#039; (minus); defaults to &amp;#039;+&amp;#039;&lt;br /&gt;
	&amp;lt;hour&amp;gt; - one or two digits&lt;br /&gt;
	&amp;lt;separator&amp;gt; - one of the characters &amp;#039;.&amp;#039; or &amp;#039;:&amp;#039;; required when &amp;lt;portion&amp;gt; is included; ignored else&lt;br /&gt;
	&amp;lt;portion&amp;gt; - optional; one or two digits when &amp;lt;separator&amp;gt; is &amp;#039;.&amp;#039;; two digits else&lt;br /&gt;
&lt;br /&gt;
returns correct utc offset string when input has a correct form; else returns the unmodified input&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function get_full_utc_offset (utc_offset)&lt;br /&gt;
	local h, m, sep, sign;&lt;br /&gt;
	&lt;br /&gt;
	local patterns = {&lt;br /&gt;
		&amp;#039;^([%+%-±−]?)(%d%d?)(%.)(%d%d?)$&amp;#039;,										-- one or two fractional hour digits&lt;br /&gt;
		&amp;#039;^([%+%-±−]?)(%d%d?)(:)(%d%d)$&amp;#039;,										-- two minute digits&lt;br /&gt;
		&amp;#039;^([%+%-±−]?)(%d%d?)[%.:]?$&amp;#039;,											-- hours only; ignore trailing separator&lt;br /&gt;
		}&lt;br /&gt;
	&lt;br /&gt;
	for _, pattern in ipairs(patterns) do										-- loop through the patterns&lt;br /&gt;
		sign, h, sep, m = mw.ustring.match (utc_offset, pattern);&lt;br /&gt;
		if h then&lt;br /&gt;
			break;																-- if h is set then pattern matched&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if not h then&lt;br /&gt;
		return utc_offset;														-- did not match a pattern&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	sign = (&amp;#039;&amp;#039; == sign) and &amp;#039;+&amp;#039; or sign;										-- sign character is required; set to &amp;#039;+&amp;#039; if not specified&lt;br /&gt;
&lt;br /&gt;
	m = (&amp;#039;.&amp;#039; == sep) and ((sep .. m) * 60) or m or 0;							-- fractional h to m&lt;br /&gt;
&lt;br /&gt;
	return string.format (&amp;#039;utc%s%02d:%02d&amp;#039;, sign, h, m);&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; T A B L E _ L E N &amp;gt;------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
return number of elements in table&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function table_len (tbl)&lt;br /&gt;
	local count = 0;&lt;br /&gt;
	for _ in pairs (tbl) do&lt;br /&gt;
		count = count + 1;&lt;br /&gt;
	end&lt;br /&gt;
	return count;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; F I R S T _ S E T &amp;gt;------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
scans through a list of parameter names that are aliases of each other and returns the value assigned to the&lt;br /&gt;
first args[alias] that has a set value; nil else. scan direction is right-to-left (top-to-bottom)&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local function first_set (list, args)&lt;br /&gt;
	local i = 1;&lt;br /&gt;
	local count = table_len (list);												-- get count of items in list&lt;br /&gt;
	&lt;br /&gt;
	while i &amp;lt;= count do															-- loop through all items in list&lt;br /&gt;
		if is_set( args[list[i]] ) then											-- if parameter name in list is set in args&lt;br /&gt;
			return args[list[i]];												-- return the value assigned to the args parameter&lt;br /&gt;
		end&lt;br /&gt;
		i = i + 1;																-- point to next&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[=[-------------------------&amp;lt; T I M E &amp;gt;----------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
This template takes several parameters (some positonal, some not); none are required:&lt;br /&gt;
	1. the time zone abbreviation/UTC offset (positional, always the first unnamed parameter)&lt;br /&gt;
	2. a date format flag; second positional parameter or |df=; can have one of several values&lt;br /&gt;
	3. |dst= when set to &amp;#039;no&amp;#039; disables dst calculations for locations that do not observe dst – Arizona in MST&lt;br /&gt;
	4. |timeonly= when set to &amp;#039;yes&amp;#039; only display the time&lt;br /&gt;
	5. |dateonly= when set to &amp;#039;yes&amp;#039; only display the date&lt;br /&gt;
	6. |hide-refresh = when set to &amp;#039;yes&amp;#039; removes the refresh link&lt;br /&gt;
	7. |hide-tz = when set to &amp;#039;yes&amp;#039; removes the timezone name&lt;br /&gt;
	8. |unlink-tz = when set to &amp;#039;yes&amp;#039; unlinks the timzone name&lt;br /&gt;
	9. |_TEST_TIME_= a specific utc time in ISO date time format used for testing this code&lt;br /&gt;
	&lt;br /&gt;
TODO: convert _TEST_TIME_ to |time=?&lt;br /&gt;
&lt;br /&gt;
Timezone abbreviations can be found here: [[List_of_time_zone_abbreviations]]&lt;br /&gt;
&lt;br /&gt;
For custom date format parameters |df-cust=,  |df-cust-a=,  |df-cust-p= use codes &lt;br /&gt;
described here: [[:mw:Help:Extension:ParserFunctions##time]]&lt;br /&gt;
&lt;br /&gt;
]=]&lt;br /&gt;
&lt;br /&gt;
local function time (frame)&lt;br /&gt;
	local args = getArgs (frame);&lt;br /&gt;
	local utc_timestamp, timestamp;												-- current or _TEST_TIME_ timestamps; timestamp is local ST or DST time used in output&lt;br /&gt;
	local dst_begin_ts, dst_end_ts;												-- DST begin and end timestamps in UTC&lt;br /&gt;
	local tz_abbr;																-- select ST or DST timezone abbreviaion used in output &lt;br /&gt;
	local time_string;															-- holds output time/date in |df= format&lt;br /&gt;
	local utc_offset;&lt;br /&gt;
	local invert;																-- true when southern hemisphere&lt;br /&gt;
	local DF;																	-- date format flag; the |df= parameter&lt;br /&gt;
	local is_dst_tz;&lt;br /&gt;
&lt;br /&gt;
	local data = table.concat ({&amp;#039;Module:Time/data&amp;#039;, frame:getTitle():find(&amp;#039;sandbox&amp;#039;, 1, true) and &amp;#039;/sandbox&amp;#039; or &amp;#039;&amp;#039;}); -- make a data module name; sandbox or live&lt;br /&gt;
	data = mw.loadData (data);													-- load the data module&lt;br /&gt;
	cfg = data.cfg;																-- get the configuration table&lt;br /&gt;
	local tz_aliases = data.tz_aliases;											-- get the aliases table&lt;br /&gt;
	local tz_data = data.tz_data;												-- get the tz data table&lt;br /&gt;
&lt;br /&gt;
	local Timeonly = yesno(first_set (cfg.aliases[&amp;#039;timeonly&amp;#039;], args));			-- boolean&lt;br /&gt;
	local Dateonly = yesno(first_set (cfg.aliases[&amp;#039;dateonly&amp;#039;], args));			-- boolean&lt;br /&gt;
	if Timeonly and Dateonly then												-- invalid condition when both are set&lt;br /&gt;
		Timeonly, Dateonly = false;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local Hide_refresh = yesno(first_set (cfg.aliases[&amp;#039;hide-refresh&amp;#039;], args));	-- boolean&lt;br /&gt;
	local Hide_tz = yesno(first_set (cfg.aliases[&amp;#039;hide-tz&amp;#039;], args));			-- boolean&lt;br /&gt;
	local Unlink_tz = yesno(first_set (cfg.aliases[&amp;#039;unlink-tz&amp;#039;], args));		-- boolean&lt;br /&gt;
	local DST = first_set (cfg.aliases[&amp;#039;dst&amp;#039;], args) or true;					-- string &amp;#039;always&amp;#039; or boolean&lt;br /&gt;
	&lt;br /&gt;
	local Lang = first_set (cfg.aliases[&amp;#039;lang&amp;#039;], args);							-- to render in a language other than the local wiki&amp;#039;s language&lt;br /&gt;
	&lt;br /&gt;
	local DF_cust = first_set (cfg.aliases[&amp;#039;df-cust&amp;#039;], args);					-- custom date/time formats&lt;br /&gt;
	&lt;br /&gt;
	local DF_cust_a = first_set (cfg.aliases[&amp;#039;df-cust-a&amp;#039;], args);				-- for am/pm sensitive formats&lt;br /&gt;
	local DF_cust_p = first_set (cfg.aliases[&amp;#039;df-cust-p&amp;#039;], args);&lt;br /&gt;
&lt;br /&gt;
	if not ((DF_cust_a and DF_cust_p) or										-- DF_cust_a xor DF_cust_p&lt;br /&gt;
			(not DF_cust_a and not DF_cust_p))then&lt;br /&gt;
		return error_msg (&amp;#039;bad_df_pair&amp;#039;);										-- both are required&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if args[1] then&lt;br /&gt;
		args[1] = get_full_utc_offset (args[1]):lower();						-- make lower case because tz table member indexes are lower case&lt;br /&gt;
	else&lt;br /&gt;
		args[1] = &amp;#039;utc&amp;#039;;														-- default to utc&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if mw.ustring.match (args[1], &amp;#039;utc[%+%-±−]%d%d:%d%d&amp;#039;) then					-- if rendering time for a UTC offset timezone&lt;br /&gt;
		tz.abbr = args[1]:upper():gsub(&amp;#039;%-&amp;#039;, &amp;#039;−&amp;#039;);								-- set the link label to upper case and replace hyphen with a minus character (U+2212)&lt;br /&gt;
		tz.article = tz.abbr;													-- article title same as abbreviation&lt;br /&gt;
		tz.utc_offset = mw.ustring.match (args[1], &amp;#039;utc([%+%-±−]?%d%d:%d%d)&amp;#039;):gsub(&amp;#039;−&amp;#039;, &amp;#039;%-&amp;#039;);	-- extract the offset value; replace minus character with hyphen&lt;br /&gt;
		local s, t = mw.ustring.match (tz.utc_offset, &amp;#039;(±)(%d%d:%d%d)&amp;#039;);		-- ± only valid for offset 00:00&lt;br /&gt;
		if s and &amp;#039;00:00&amp;#039; ~= t then&lt;br /&gt;
			return error_msg (&amp;#039;bad_sign&amp;#039;);&lt;br /&gt;
		end&lt;br /&gt;
		tz.df = &amp;#039;iso&amp;#039;;&lt;br /&gt;
		args[1] = &amp;#039;utc_offsets&amp;#039;;												-- spoof to show that we recognize this timezone&lt;br /&gt;
	else&lt;br /&gt;
		tz = tz_aliases[args[1]] and tz_data[tz_aliases[args[1]]] or tz_data[args[1]];	-- make a local copy of the timezone table from tz_data{}&lt;br /&gt;
		if not tz then&lt;br /&gt;
			return error_msg (&amp;#039;unknown_tz&amp;#039;, args[1]);							-- if the timezone given isn&amp;#039;t in module:time/data(/sandbox)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	DF = first_set (cfg.aliases[&amp;#039;df&amp;#039;], args) or args[2] or tz.df or cfg.default_df;	-- template |df= overrides typical df from tz properties&lt;br /&gt;
	DF = DF:lower();															-- normalize to lower case&lt;br /&gt;
	if not cfg.df_vals[DF] then&lt;br /&gt;
		return error_msg (&amp;#039;bad_format&amp;#039;, DF);&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if is_set (args._TEST_TIME_) then											-- typically used to test the code at a specific utc time&lt;br /&gt;
		local test_time = get_test_time (args._TEST_TIME_);&lt;br /&gt;
		if not test_time then&lt;br /&gt;
			return error_msg (&amp;#039;test_time&amp;#039;);&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		utc_timestamp = os.time(test_time);&lt;br /&gt;
	else&lt;br /&gt;
		utc_timestamp = os.time ();												-- get current server time (UTC)&lt;br /&gt;
	end&lt;br /&gt;
	utc_offset = get_utc_offset ();												-- utc offset for specified timezone in seconds&lt;br /&gt;
	timestamp = utc_timestamp + utc_offset;										-- make local time timestamp&lt;br /&gt;
&lt;br /&gt;
	if &amp;#039;always&amp;#039; == DST then														-- if needed to always display dst time&lt;br /&gt;
		timestamp = timestamp + 3600;											-- add a hour for dst&lt;br /&gt;
		tz_abbr = tz.dst_abbr;													-- dst abbreviation&lt;br /&gt;
	elseif not yesno(DST) then													-- for timezones that DO observe dst but for this location ...&lt;br /&gt;
		tz_abbr = tz.abbr;														-- ... dst is not observed (|dst=no) show time as standard time&lt;br /&gt;
	else&lt;br /&gt;
		if is_set (tz.dst_begins) and is_set (tz.dst_ends) and is_set (tz.dst_time) then	-- make sure we have all of the parts&lt;br /&gt;
			dst_begin_ts, dst_end_ts, invert = make_dst_timestamps (timestamp);	-- get begin and end dst timestamps and invert flag&lt;br /&gt;
&lt;br /&gt;
			if nil == dst_begin_ts or nil == dst_end_ts then&lt;br /&gt;
				return error_msg (&amp;#039;bad_dst&amp;#039;);&lt;br /&gt;
			end&lt;br /&gt;
	&lt;br /&gt;
			if invert then														-- southern hemisphere; use beginning and ending of standard time in the comparison&lt;br /&gt;
				if utc_timestamp &amp;gt;= dst_end_ts and utc_timestamp &amp;lt; dst_begin_ts then	-- is current date time standard time?&lt;br /&gt;
					tz_abbr = tz.abbr;											-- standard time abbreviation&lt;br /&gt;
				else		&lt;br /&gt;
					timestamp = timestamp + 3600;								-- add an hour&lt;br /&gt;
					tz_abbr = tz.dst_abbr;										-- dst abbreviation&lt;br /&gt;
				end&lt;br /&gt;
			else																-- northern hemisphere&lt;br /&gt;
				if utc_timestamp &amp;gt;= dst_begin_ts and utc_timestamp &amp;lt; dst_end_ts then	-- all timestamps are UTC&lt;br /&gt;
					timestamp = timestamp + 3600;								-- add an hour &lt;br /&gt;
					tz_abbr = tz.dst_abbr;&lt;br /&gt;
				else&lt;br /&gt;
					tz_abbr = tz.abbr;&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		elseif is_set (tz.dst_begins) or is_set (tz.dst_ends) or is_set (tz.dst_time) then	-- if some but not all not all parts then emit error message&lt;br /&gt;
			return error_msg (&amp;#039;bad_def&amp;#039;, args[1]:upper());&lt;br /&gt;
		else&lt;br /&gt;
			tz_abbr = tz.abbr;													-- dst not observed for this timezone&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if Dateonly then&lt;br /&gt;
		if &amp;#039;iso&amp;#039; == DF then														-- |df=iso&lt;br /&gt;
			DF = &amp;#039;iso_date&amp;#039;;&lt;br /&gt;
		elseif DF:find (&amp;#039;^dmy&amp;#039;) or &amp;#039;y&amp;#039; == DF then								-- |df=dmy, |df=dmy12, |df=dmy24, |df=y&lt;br /&gt;
			DF = &amp;#039;dmy_date&amp;#039;;&lt;br /&gt;
		else&lt;br /&gt;
			DF = &amp;#039;mdy_date&amp;#039;;													-- default&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
	elseif Timeonly or DF:match (&amp;#039;^%d+$&amp;#039;) then									-- time only of |df= is just digits&lt;br /&gt;
		DF = table.concat ({&amp;#039;t&amp;#039;, DF:match (&amp;#039;%l*(12)&amp;#039;) or &amp;#039;24&amp;#039;});				-- |df=12, |df=24, |df=dmy12, |df=dmy24, |df=mdy12, |df=mdy24; default to t24&lt;br /&gt;
		&lt;br /&gt;
	elseif &amp;#039;y&amp;#039; == DF or &amp;#039;dmy24&amp;#039; == DF then&lt;br /&gt;
		DF = &amp;#039;dmy&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
	elseif &amp;#039;mdy24&amp;#039; == DF then&lt;br /&gt;
		DF = &amp;#039;mdy&amp;#039;;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local dformat;&lt;br /&gt;
	if is_set (DF_cust) then&lt;br /&gt;
		dformat=DF_cust;&lt;br /&gt;
	elseif is_set (DF_cust_a) then												-- custom format is am/pm sensitive?&lt;br /&gt;
		if &amp;#039;am&amp;#039; == os.date (&amp;#039;%P&amp;#039;, timestamp) then								-- if current time is am&lt;br /&gt;
			dformat = DF_cust_a;												-- use custom am format&lt;br /&gt;
		else&lt;br /&gt;
			dformat = DF_cust_p;												-- use custom pm format&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		dformat = cfg.format[DF];												-- use format from tables or from |df=&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	time_string = frame:callParserFunction ({name=&amp;#039;#time&amp;#039;, args={dformat, &amp;#039;@&amp;#039;..timestamp, Lang}});&lt;br /&gt;
	if Lang then&lt;br /&gt;
		time_string = table.concat ({											-- bidirectional isolation of non-local language; yeah, rather brute force but simple&lt;br /&gt;
			&amp;#039;&amp;lt;bdi lang=&amp;quot;&amp;#039;,														-- start of opening bdi tag&lt;br /&gt;
			Lang,																-- insert rendered language code&lt;br /&gt;
			&amp;#039;&amp;quot;&amp;gt;&amp;#039;,																-- end of opening tag&lt;br /&gt;
			time_string,														-- insert the time string&lt;br /&gt;
			&amp;#039;&amp;lt;/bdi&amp;gt;&amp;#039;															-- and close the tag&lt;br /&gt;
			});&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if not is_set (tz.article) then												-- if some but not all not all parts then emit error message&lt;br /&gt;
		return error_msg (&amp;#039;bad_def&amp;#039;, args[1]:upper());&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local refresh_link = (Hide_refresh and &amp;#039;&amp;#039;) or&lt;br /&gt;
		table.concat ({&lt;br /&gt;
			&amp;#039; &amp;lt;span class=&amp;quot;plainlinks&amp;quot; style=&amp;quot;font-size:85%;&amp;quot;&amp;gt;[[&amp;#039;,				-- open span&lt;br /&gt;
			mw.title.getCurrentTitle():fullUrl({action = &amp;#039;purge&amp;#039;}),				-- add the a refresh link url&lt;br /&gt;
			&amp;#039; &amp;#039;,&lt;br /&gt;
			cfg[&amp;#039;refresh-label&amp;#039;],												-- add the label&lt;br /&gt;
			&amp;#039;]]&amp;lt;/span&amp;gt;&amp;#039;,														-- close the span&lt;br /&gt;
			});&lt;br /&gt;
&lt;br /&gt;
	local tz_tag = (Hide_tz and &amp;#039;&amp;#039;) or&lt;br /&gt;
		((Unlink_tz and table.concat ({&amp;#039; &amp;#039;, tz_abbr})) or						-- unlinked&lt;br /&gt;
			table.concat ({&amp;#039; [[&amp;#039;, tz.article, &amp;#039;|&amp;#039;, tz_abbr, &amp;#039;]]&amp;#039;}));			-- linked&lt;br /&gt;
	&lt;br /&gt;
	return table.concat ({time_string, tz_tag, refresh_link});&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[--------------------------&amp;lt; E X P O R T E D   F U N C T I O N S &amp;gt;------------------------------------------&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
return {time = time}&lt;/div&gt;</summary>
		<author><name>Wikipedia&gt;Pppery</name></author>
	</entry>
</feed>