require('Module:No globals')
local getArgs = require('Module:Arguments').getArgs
local p = {}
--[[--------------------------< T I M E Z O N E D E F I N I T I O N S >--------------------------------------
When specifying utc offset do not use the minus character (U+2212) for offsets west of 0 meridian; use the minus-hyphen
]]
local tz = {
['utc'] = {
abbr = 'UTC',
dst_abbr = '', -- abbreviation to use when daylight saving time is in effect
utc_offset = '00:00', -- hours and minutes offset from UTC for this timezone; + (east of 0 meridian) is optional; - (west of 0 meridian is required)
dst_begins = '', -- daylight saving begins; e.g. 2nd Sunday in March; also last; empty string if not observed
dst_ends = '', -- daylight saving begins; e.g. 1st Sunday in November
dst_time = '', -- the time of day at which dst begins/ends; e.g. 02:00
article = 'Coordinated Universal Time' -- name of related Wikipedia article without markup
},
-- Europe
['gmt'] = {
abbr = 'GMT',
dst_abbr = '',
utc_offset = '00:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Greenwich Mean Time'
},
--['bst'] = { -- also Bangladesh standard time
-- abbr = 'BST',
-- utc_offset = '00:00',
-- dst_begins = '',
-- dst_ends = '',
-- dst_time = '',
-- article = 'British Summer Time'
--}
['cet'] = {
abbr = 'CET',
dst_abbr = 'CEST',
utc_offset = '01:00',
dst_begins = 'last Sunday of March',
dst_ends = 'last Sunday of October',
dst_time = '01:00',
article = 'Central European Time'
},
['eet'] = {
abbr = 'EET',
dst_abbr = 'EEST',
utc_offset = '02:00',
dst_begins = 'last Sunday of March',
dst_ends = 'last Sunday of October',
dst_time = '01:00',
article = 'Eastern European Time'
},
--S Asia
['bst'] = { -- also British summer time
abbr = 'BST',
utc_offset = '00:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Bangladesh Standard Time'
},
['ist'] = {
abbr = 'IST',
utc_offset = '05:30',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Indian Standard Time'
},
--N America,US Pacific
['pst'] = {
abbr = 'PST',
dst_abbr = 'PDT',
utc_offset = '-08:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Pacific Time Zone'
},
['mst'] = {
abbr = 'MST',
dst_abbr = 'MDT',
utc_offset = '-07:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Mountain Time Zone'
},
['cst'] = {
abbr = 'CST',
dst_abbr = 'CDT',
utc_offset = '-06:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Central Time Zone'
},
['est'] = {
abbr = 'EST',
dst_abbr = 'EDT',
utc_offset = '-05:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Eastern Time Zone'
},
['ast'] = {
abbr = 'AST',
dst_abbr = 'ADT',
utc_offset = '-04:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Atlantic Time Zone'
},
['pmst'] = {
abbr = 'PMST',
dst_abbr = 'PMDT',
utc_offset = '-03:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'UTC−03:00'
},
['wgt'] = { -- western greenland time?
abbr = 'WGT',
dst_abbr = '',
utc_offset = '-03:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'UTC−03:00'
},
['nst'] = {
abbr = 'NST',
dst_abbr = 'NDT',
utc_offset = '-03:30',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Newfoundland Standard Time'
},
['akst'] = {
abbr = 'AKST',
dst_abbr = 'AKDT',
utc_offset = '-09:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Alaska Time Zone'
},
['hast'] = { -- same as AleutST and HST
abbr = 'HAST',
dst_abbr = 'HADT',
utc_offset = '-10:00',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Hawaii–Aleutian Time Zone'
},
['chst'] = {
abbr = 'ChST',
dst_abbr = '',
utc_offset = '10:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Chamorro Time Zone'
},
--Japan
['jst'] = {
abbr = 'JST',
dst_abbr = '',
utc_offset = '09:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Chamorro Time Zone'
},
--China
['bt'] = { -- same as China standard time (CST)
abbr = 'BT',
dst_abbr = '',
utc_offset = '08:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in China'
},
--Indonesia
['wit'] = { -- eastern (same as old template's eit)
abbr = 'WIT',
dst_abbr = '',
utc_offset = '09:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Indonesia'
},
['wita'] = { -- central
abbr = 'WITA',
dst_abbr = '',
utc_offset = '08:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Indonesia'
},
['wib'] = { -- western
abbr = 'WIB',
dst_abbr = '',
utc_offset = '07:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Indonesia'
},
--Australia
['awst'] = { -- eastern
abbr = 'AWST',
dst_abbr = 'AWDT',
utc_offset = '08:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Australia'
},
['awst'] = {
abbr = 'AWST',
dst_abbr = '',
utc_offset = '08:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Australia'
},
['ntt'] = { -- Northern Territory time
abbr = 'ACST',
dst_abbr = '',
utc_offset = '09:30',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Australia'
},
['sat'] = { -- South Australia time
abbr = 'ACST',
dst_abbr = 'ACDT',
utc_offset = '09:30',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Australia'
},
['qt'] = { -- Queensland time
abbr = 'AEST',
dst_abbr = '',
utc_offset = '09:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Australia'
},
['qt'] = { -- NSW, TAS, VIC, ACT
abbr = 'AEST',
dst_abbr = 'AEDT',
utc_offset = '09:00',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Australia'
},
--New Zealand
['nzst'] = {
abbr = 'NZST',
dst_abbr = 'NZDT',
utc_offset = '12:00',
dst_begins = 'last Sunday in September',
dst_ends = 'first Sunday in April',
dst_time = '02:00',
article = 'Time in New Zealand'
},
---------- military timezones
['a'] = {abbr = 'A', utc_offset = '01:00', article = 'List of military time zones'},
['b'] = {abbr = 'B', utc_offset = '02:00', article = 'List of military time zones'},
['c'] = {abbr = 'C', utc_offset = '03:00', article = 'List of military time zones'},
['d'] = {abbr = 'D', utc_offset = '04:00', article = 'List of military time zones'},
['e'] = {abbr = 'E', utc_offset = '05:00', article = 'List of military time zones'},
['f'] = {abbr = 'F', utc_offset = '06:00', article = 'List of military time zones'},
['g'] = {abbr = 'G', utc_offset = '07:00', article = 'List of military time zones'},
['h'] = {abbr = 'H', utc_offset = '08:00', article = 'List of military time zones'},
['i'] = {abbr = 'I', utc_offset = '09:00', article = 'List of military time zones'},
['k'] = {abbr = 'K', utc_offset = '10:00', article = 'List of military time zones'},
['l'] = {abbr = 'L', utc_offset = '11:00', article = 'List of military time zones'},
['m'] = {abbr = 'M', utc_offset = '12:00', article = 'List of military time zones'},
['n'] = {abbr = 'N', utc_offset = '-01:00', article = 'List of military time zones'},
['o'] = {abbr = 'O', utc_offset = '-02:00', article = 'List of military time zones'},
['p'] = {abbr = 'P', utc_offset = '-03:00', article = 'List of military time zones'},
['q'] = {abbr = 'Q', utc_offset = '-04:00', article = 'List of military time zones'},
['r'] = {abbr = 'R', utc_offset = '-05:00', article = 'List of military time zones'},
['s'] = {abbr = 'S', utc_offset = '-06:00', article = 'List of military time zones'},
['t'] = {abbr = 'T', utc_offset = '-07:00', article = 'List of military time zones'},
['u'] = {abbr = 'U', utc_offset = '-08:00', article = 'List of military time zones'},
['v'] = {abbr = 'V', utc_offset = '-09:00', article = 'List of military time zones'},
['w'] = {abbr = 'W', utc_offset = '-10:00', article = 'List of military time zones'},
['x'] = {abbr = 'X', utc_offset = '-11:00', article = 'List of military time zones'},
['y'] = {abbr = 'Y', utc_offset = '-12:00', article = 'List of military time zones'},
['z'] = {abbr = 'Z', utc_offset = '±00:00', article = 'List of military time zones'},
----------UTC offsets
['utc_offsets'] = {abbr = '', utc_offset = '', article = ''},
}
--[[--------------------------< I S _ S E T >------------------------------------------------------------------
Whether variable is set or not. A variable is set when it is not nil and not empty.
]]
local function is_set( var )
return not (var == nil or var == '');
end
--[[--------------------------< D E C O D E _ D S T _ E V E N T >----------------------------------------------
extract ordinal, day-name, and month from daylight saving start/end definition string as digits:
Second Sunday in March
returns
2 0 3
]]
local function decode_dst_event (dst_event_string)
local ord, day, month;
local ordinals = {['1st'] = 1, ['first'] = 1, ['2nd'] = 2, ['second'] = 2, ['3rd'] = 3, ['third'] = 3, ['4th'] = 4, ['fourth'] = 4, ['5th'] = 5, ['fifth'] = 5, ['last'] = -1};
local days = {['sunday'] = 0, ['monday'] = 1, ['tuesday'] = 2, ['wednesday'] = 3, ['thursday'] = 4, ['friday'] = 5, ['saturday'] = 6};
local months = {['january'] = 1, ['february'] = 2, ['march'] = 3, ['april'] = 4, ['may'] = 5, ['june'] = 6,
['july'] = 7, ['august'] = 8, ['september'] = 9, ['october'] = 10, ['november'] = 11, ['december'] = 12};
ord, day, month = dst_event_string:match ('([%a%d]+)%s+(%a+)%s+%a+%s+(%a+)');
if not (is_set (ord) and is_set (day) and is_set (month)) then
return nil;
end
return ordinals[ord:lower()], days[day:lower()], months[month:lower()];
end
--[[--------------------------< G E T _ D S T _ M O N T H _ D A Y >--------------------------------------------
Return the date (month and day of the month) for the day that is the ordinal (nth) day-name in month (second
Friday in June) of this year
timestamp is today's date-time number from os.time(); used to supply year
timezone is the timezone parameter value from the template call
Equations used in this function taken from Template:Weekday_in_month
]]
local function get_dst_month_day (timestamp, timezone, start)
local ord, weekday_num, month;
local first_day_of_dst_month_num;
local month_days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if true == start then
ord, weekday_num, month = decode_dst_event (tz[timezone].dst_begins); -- get start string and convert to digits
else
ord, weekday_num, month = decode_dst_event (tz[timezone].dst_ends); -- get end string and convert to digits
end
if not (is_set (ord) and is_set (weekday_num) and is_set (month)) then
return nil;
end
if '-1' == ord then -- j = t + 7×(n + 1) - (wt - w) mod 7
days_in_month = month_days[month];
last_day_of_dst_month_num = os.date ('%w', os.time ({['year']=os.date ('%Y', timestamp), ['month']=month, ['day']=days_in_month}));
return month, days_in_month + 7*(ord + 1) - (last_day_of_dst_month_num - weekday_num) % 7;
else -- j = 7×n - 6 + (w - w1)
first_day_of_dst_month_num = os.date ('%w', os.time ({['year']=os.date ('%Y', timestamp), ['month']=month, ['day']=1}))
return month, 7 * ord - 6 + (weekday_num - first_day_of_dst_month_num) % 7; -- return month and calculated date
end
end
--[[--------------------------< M A K E _ D S T _ T I M E S T A M P S >----------------------------------------
returns a timestamp for the date/time of a daylight saving time event (beginning or ending)
]]
local function make_dst_timestamps (timestamp, timezone)
local dst_begin, dst_end;
local year;
local dst_b_month, dst_e_month, dst_day;
local dst_hour, dst_minute;
local invert = false;
year = os.date ('%Y', timestamp); -- current year
dst_b_month, dst_day = get_dst_month_day (timestamp, timezone, true); -- starting of dst
if not is_set (dst_b_month) then
return nil;
end
dst_hour, dst_minute = tz[timezone].dst_time:match ('(%d%d):(%d%d)'); -- get dst time
dst_begin = os.time ({['year'] = year, ['month'] = dst_b_month, ['day'] = dst_day, ['hour'] = dst_hour, ['min'] = dst_minute}); -- form start timestamp
dst_e_month, dst_day = get_dst_month_day (timestamp, timezone, false); -- starting of dst
if not is_set (dst_e_month) then
return nil;
end
dst_hour, dst_minute = tz[timezone].dst_time:match ('(%d%d):(%d%d)'); -- get dst time
dst_end = os.time ({['year'] = year, ['month'] = dst_e_month, ['day'] = dst_day, ['hour'] = dst_hour, ['min'] = dst_minute});
if dst_b_month > dst_e_month then
invert = true; -- true for southern hemisphere start September YYYY end April YYYY+1
end
return dst_begin, dst_end, invert;
end
--[[--------------------------< G E T _ U T C _ O F F S E T >--------------------------------------------------
Get utc offset in hours and minutes, convert to seconds. If the offset can't be converted return 0.
TODO: return error message?
]]
local function get_utc_offset (timezone)
local sign;
local hours;
local minutes;
sign, hours, minutes = mw.ustring.match (tz[timezone].utc_offset, '([%+%-±]?)(%d%d):(%d%d)');
if '-' == sign then sign = -1; else sign = 1; end
if is_set (hours) and is_set (minutes) then
return sign * ((hours * 3600) + (minutes * 60));
else
return 0;
end
end
--[=[-------------------------< P . T I M E >------------------------------------------------------------------
This template takes two unnamed parameters:
1. the time zone abbreviation
2. a date format flag; if set to 'y', formats the output date in dmy else mdy format
Timezone abbreviations can be found here: [[List_of_time_zone_abbreviations]]
]=]
function p.time (frame)
local args = getArgs(frame);
local timestamp;
local dst_begin_ts, dst_end_ts;
local tz_abbr;
local tz_string;
local utc_offset;
local invert;
if args[1] then
args[1] = args[1]:lower(); -- make lower case
if mw.ustring.match (args[1], 'utc[%+%-±]?%d%d:%d%d') then
tz['utc_offsets'].abbr = args[1]:upper(); -- set the link label
tz['utc_offsets'].article = tz['utc_offsets'].abbr; -- same as the article title
tz['utc_offsets'].utc_offset = mw.ustring.match (args[1], 'utc([%+%-±]?%d%d:%d%d)'); -- extract the offset value
args[1] = 'utc_offsets'; -- point to the generic utc offsets table
end
if not is_set (tz[args[1]]) then
return '<span style="font-size:100%" class="error">Time: unknown timezone</span>';
end
else
args[1] = 'utc'; -- default to utc
end
if args[2] then
args[2] = args[2]:lower(); -- also lower case
end
timestamp = os.time (); -- get current server time (UTC)
utc_offset = get_utc_offset (args[1]);
timestamp = timestamp + utc_offset;
if is_set (tz[args[1]].dst_begins) and is_set (tz[args[1]].dst_ends) and is_set (tz[args[1]].dst_time) then
dst_begin_ts, dst_end_ts, invert = make_dst_timestamps (timestamp, args[1]); -- get begin and end dst timestamps and invert flag
if nil == dst_begin_ts or nil == dst_end_ts then
return '<span style="font-size:100%" class="error">Time: error calculating dst timestamps</span>';
end
if invert then -- southern hemisphere
if timestamp <= dst_begin_ts and timestamp >= dst_end_ts-3600 then -- is current date time standard time?
tz_abbr = tz[args[1]].abbr;
else
timestamp = timestamp + 3600; -- add an hour
tz_abbr = tz[args[1]].dst_abbr;
end
else -- northern hemisphere
if timestamp >= dst_begin_ts and timestamp <= dst_end_ts-3600 then
timestamp = timestamp + 3600; -- add an hour
tz_abbr = tz[args[1]].dst_abbr;
else
tz_abbr = tz[args[1]].abbr;
end
end
elseif is_set (tz[args[1]].dst_begins) or is_set (tz[args[1]].dst_ends) or is_set (tz[args[1]].dst_time) then
return '<span style="font-size:100%" class="error">Time: incomplete definition for ' .. args[1]:upper() .. '</span>';
else
tz_abbr = tz[args[1]].abbr;
end
if 'y' == args[2] then
tz_string = os.date ('%R, %e %B %Y', timestamp);
else
tz_string = os.date ('%R, %B %e, %Y', timestamp);
end
local refreshLink = mw.title.getCurrentTitle():fullUrl{action = 'purge'}
return string.format ('%s [[%s|%s]] <span class="plainlinks" style="font-size:80%%;">[[%s refresh]]</span>', tz_string, tz[args[1]].article, tz_abbr, refreshLink);
end
return p;