Module:Time/sandbox: Difference between revisions

Content deleted Content added
No edit summary
No edit summary
 
(347 intermediate revisions by 5 users not shown)
Line 1:
require('Module:No globalsstrict')
local getArgsyesno = require('Module:ArgumentsYesno').getArgs
local getArgs = require ('Module:Arguments').getArgs
local data = mw.loadData ('Module:Time/data' .. (mw.getCurrentFrame():getTitle():match ('/sandbox') or '')); -- load the data module
local cfg = data.cfg; -- for internationalization
 
local tz = {}; -- holds local copy of the specified timezone table from tz_data{}
local p = {}
 
--[[--------------------------< T I M E Z O N E P R O P E R T I E S >----------------------------------------
 
--[[--------------------------< I S _ S E T >------------------------------------------------------------------
When specifying utc offset do not use the minus character (U+2212) for offsets west of 0 meridian; use the minus-hyphen
 
Whether variable is set or not. A variable is set when it is not nil and not empty.
 
]]
 
local function is_set( var )
local tz = { -- these are ordered by UTC offset +HH:MM followed by -HH:MM
return not (nil == var or '' == var);
end
 
------------------------------< E A S T U T C + H H : M M >--------------------------------------------------
 
--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------
['utc'] = {
abbr = 'UTC', -- (required) abbreviation to use during standard time
dst_abbr = '', -- abbreviation to use during daylight saving time
utc_offset = '00:00', -- (required) hours and minutes offset from UTC for this timezone; '+' is optional; '-' (hyphen-minus) is required for timezones west of 0 meridian
df = 'dmy', -- date format typically used in the time zone
dst_begins = '', -- daylight saving begins; e.g. 2nd Sunday in March; also last; empty string if not observed
dst_ends = '', -- (required if dst_begins is set) daylight saving begins; e.g. 1st Sunday in November; ignored if dst_begins not set
dst_time = '', -- (required if dst_begins is set) local time on the day that dst begins/ends; for EU DST rules specify utc time: e.g.: '01:00 UTC'
dst_e_time = '', -- local daylight saving time on the day that dst ends; only when different from dst_time; see acst for an example
article = 'Coordinated Universal Time' -- (required) name of related Wikipedia article without markup
},
['gmt'] = {
abbr = 'GMT',
dst_abbr = '',
utc_offset = '00:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Greenwich Mean Time'
},
['gmt-uk'] = {
abbr = 'GMT',
dst_abbr = 'BST', -- British Summer Time
utc_offset = '00:00',
df = 'dmy',
dst_begins = 'last Sunday of March',
dst_ends = 'last Sunday of October',
dst_time = '01:00 UTC', -- 01:00 standard time is same as 01:00 UTC
article = 'Time in the United Kingdom'
},
['gmt-ie'] = { -- ist is shared with Indian Standard Time so use gmt-ie
abbr = 'GMT', -- winter time
dst_abbr = 'IST', -- Irish Standard Time occurs in summer
utc_offset = '00:00',
df = 'dmy',
dst_begins = 'last Sunday of March',
dst_ends = 'last Sunday of October',
dst_time = '01:00 UTC', -- 01:00 standard time is same as 01:00 UTC
article = 'Time in Ireland'
},
['wet'] = {
abbr = 'WET',
dst_abbr = 'WEST',
utc_offset = '00:00',
df = 'dmy',
dst_begins = 'last Sunday of March',
dst_ends = 'last Sunday of October',
dst_time = '01:00 UTC', -- 01:00 standard time is same as 01:00 UTC
article = 'Western European Time'
},
['cet'] = {
abbr = 'CET',
dst_abbr = 'CEST',
utc_offset = '01:00',
df = 'dmy',
dst_begins = 'last Sunday of March',
dst_ends = 'last Sunday of October',
dst_time = '01:00 UTC', -- 02:00 standard time is same as 01:00 UTC
article = 'Central European Time'
},
['eet'] = {
abbr = 'EET',
dst_abbr = 'EEST',
utc_offset = '02:00',
df = 'dmy',
dst_begins = 'last Sunday of March',
dst_ends = 'last Sunday of October',
dst_time = '01:00 UTC', -- 03:00 standard time is same as 01:00 UTC
article = 'Eastern European Time'
},
['usz1'] = {
abbr = 'USZ1',
dst_abbr = '',
utc_offset = '02:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Kaliningrad Time'
},
['msk'] = {
abbr = 'MSK',
dst_abbr = '',
utc_offset = '03:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Moscow Time'
},
['samt'] = {
abbr = 'SAMT',
dst_abbr = '',
utc_offset = '04:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Samara Time'
},
['yekt'] = {
abbr = 'YEKT',
dst_abbr = '',
utc_offset = '05:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Yekaterinburg Time'
},
['pkt'] = {
abbr = 'PKT',
dst_abbr = '',
utc_offset = '05:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Pakistan Standard Time'
},
['ist'] = {
abbr = 'IST',
utc_offset = '05:30',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Indian Standard Time'
},
['npt'] = {
abbr = 'NPT',
dst_abbr = '',
utc_offset = '05:45',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Nepal Time'
},
['bst'] = {
abbr = 'BST',
utc_offset = '06:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Bangladesh Standard Time'
},
['omst'] = {
abbr = 'OMST',
dst_abbr = '',
utc_offset = '06:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Omsk Time'
},
['krat'] = {
abbr = 'KRAT',
dst_abbr = '',
utc_offset = '07:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Krasnoyarsk Time'
},
['wib'] = { -- western indonesia
abbr = 'WIB',
dst_abbr = '',
utc_offset = '07:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Indonesia'
},
['awst'] = {
abbr = 'AWST',
dst_abbr = '',
utc_offset = '08:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Australia'
},
['bt'] = { -- same as China standard time (CST)
abbr = 'BT',
dst_abbr = '',
utc_offset = '08:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in China'
},
['irkt'] = {
abbr = 'IRKT',
dst_abbr = '',
utc_offset = '08:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Irkutsk Time'
},
['wita'] = { -- central
abbr = 'WITA',
dst_abbr = '',
utc_offset = '08:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Indonesia'
},
['jst'] = {
abbr = 'JST',
dst_abbr = '',
utc_offset = '09:00',
df = 'iso',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Japan Standard Time'
},
['kst'] = {
abbr = 'KST',
dst_abbr = '',
utc_offset = '09:00',
df = 'iso',
dst_begins = '',
dst_ends = '',
dst_time = '',
dst_e_time = '',
article = 'Time in South Korea'
},
['wit'] = { -- eastern Indonesia (same as old template's eit)
abbr = 'WIT',
dst_abbr = '',
utc_offset = '09:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Time in Indonesia'
},
['yakt'] = {
abbr = 'YAKT',
dst_abbr = '',
utc_offset = '09:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Yakutsk Time'
},
['acst'] = { -- Northern Territory, South Australia time
abbr = 'ACST',
dst_abbr = 'ACDT',
utc_offset = '09:30',
df = 'dmy',
dst_begins = 'first Sunday in October',
dst_ends = 'first Sunday in April',
dst_time = '02:00',
dst_e_time = '03:00',
article = 'Time in Australia'
},
['aest'] = { -- QLD, NSW, TAS, VIC, ACT
abbr = 'AEST',
dst_abbr = 'AEDT',
utc_offset = '10:00',
df = 'dmy',
dst_begins = 'first Sunday in October',
dst_ends = 'first Sunday in April',
dst_time = '02:00', -- begins at 02:00 AEST
dst_e_time = '03:00', -- ends at 02:00 AEST which is 03:00 AEDT
article = 'Time in Australia'
},
['chst'] = {
abbr = 'ChST',
dst_abbr = '',
utc_offset = '10:00',
df = 'mdy', -- because a US territory?
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Chamorro Time Zone'
},
['vlat'] = {
abbr = 'VLAT',
dst_abbr = '',
utc_offset = '10:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Vladivostok Time'
},
['sret'] = {
abbr = 'SRET',
dst_abbr = '',
utc_offset = '11:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Srednekolymsk Time'
},
['nzst'] = {
abbr = 'NZST',
dst_abbr = 'NZDT',
utc_offset = '12:00',
df = 'dmy',
dst_begins = 'last Sunday in September',
dst_ends = 'first Sunday in April',
dst_time = '02:00', -- begins at 02:00 NZST
dst_e_time = '03:00', -- ends at 02:00 NZST which is 03:00 NZDT
article = 'Time in New Zealand'
},
['pett'] = {
abbr = 'PETT',
dst_abbr = '',
utc_offset = '12:00',
df = 'dmy',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Kamchatka Time'
},
['idle'] = { -- international dateline east
abbr = 'IDLE',
dst_abbr = '',
utc_offset = '12:00',
df = 'iso',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'UTC+12:00'
},
 
Populates numbered arguments in a message string using an argument table.
 
]]
------------------------------< W E S T U T C - H H : M M >--------------------------------------------------
 
local function substitute (msg, args_t)
['pmst'] = {
return args_t and mw.message.newRawMessage (msg, args_t):plain() or msg;
abbr = 'PMST',
end
dst_abbr = 'PMDT',
utc_offset = '-03:00',
df = 'dmy', -- because France
dst_begins = '2nd Sunday in March', -- North American rules
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'UTC−03:00'
},
['wgt'] = { -- western greenland time? Not for Thule Airbase which observes US DST rules; requires own properties
abbr = 'WGT',
dst_abbr = 'WGST', -- ??? what is the correct abbreviation?
utc_offset = '-03:00',
df = 'dmy',
dst_begins = 'last Sunday in March',
dst_ends = 'last Sunday in October',
dst_time = '01:00 UTC',
article = 'Time in Denmark'
},
['nst'] = {
abbr = 'NST',
dst_abbr = 'NDT',
utc_offset = '-03:30',
df = 'dmy',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Newfoundland Standard Time'
},
['ast'] = {
abbr = 'AST',
dst_abbr = 'ADT',
utc_offset = '-04:00',
df = 'dmy',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Atlantic Time Zone'
},
['est'] = {
abbr = 'EST',
dst_abbr = 'EDT',
utc_offset = '-05:00',
df = 'mdy',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Eastern Time Zone'
},
['cst'] = {
abbr = 'CST',
dst_abbr = 'CDT',
utc_offset = '-06:00',
df = 'mdy',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Central Time Zone'
},
['mst'] = {
abbr = 'MST',
dst_abbr = 'MDT',
utc_offset = '-07:00',
df = 'mdy',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Mountain Time Zone'
},
['pst'] = {
abbr = 'PST',
dst_abbr = 'PDT',
utc_offset = '-08:00',
df = 'mdy',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Pacific Time Zone'
},
['akst'] = {
abbr = 'AKST',
dst_abbr = 'AKDT',
utc_offset = '-09:00',
df = 'mdy',
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',
df = 'mdy',
dst_begins = '2nd Sunday in March',
dst_ends = '1st Sunday in November',
dst_time = '02:00',
article = 'Hawaii–Aleutian Time Zone'
},
['idlw'] = { -- international dateline west
abbr = 'IDLW',
dst_abbr = '',
utc_offset = '-12:00',
df = 'iso',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'UTC−12:00'
},
['aoe'] = { -- last ___location for any date
abbr = 'AoE',
dst_abbr = '',
utc_offset = '-12:00',
df = 'iso',
dst_begins = '',
dst_ends = '',
dst_time = '',
article = 'Anywhere on Earth'
},
 
------------------------------< M I L I T A R Y >--------------------------------------------------------------
-- {{cite web |url=http://wwp.greenwichmeantime.com/info/timezone.htm |title=Military and Civilian Time Designations |accessdate=2016-03-25 |website=Greenwich Mean Time}}
--http://astro.ukho.gov.uk/nao/miscellanea/WMTZ/Wmtz160224.pdf
['a'] = {abbr = 'A', utc_offset = '01:00', df = 'iso', article = 'List of military time zones'},
['b'] = {abbr = 'B', utc_offset = '02:00', df = 'iso', article = 'List of military time zones'},
['c'] = {abbr = 'C', utc_offset = '03:00', df = 'iso', article = 'List of military time zones'},
['c*'] = {abbr = 'C*', utc_offset = '03:30', df = 'iso', article = 'List of military time zones'},
['d'] = {abbr = 'D', utc_offset = '04:00', df = 'iso', article = 'List of military time zones'},
['d*'] = {abbr = 'D*', utc_offset = '04:30', df = 'iso', article = 'List of military time zones'},
['e'] = {abbr = 'E', utc_offset = '05:00', df = 'iso', article = 'List of military time zones'},
['e*'] = {abbr = 'E*', utc_offset = '05:30', df = 'iso', article = 'List of military time zones'},
['e+'] = {abbr = 'E<span class="plainlinks" style="font-size:120%%;">†</span>', utc_offset = '05:45', df = 'iso', article = 'List of military time zones'},
['f'] = {abbr = 'F', utc_offset = '06:00', df = 'iso', article = 'List of military time zones'},
['f*'] = {abbr = 'F*', utc_offset = '06:30', df = 'iso', article = 'List of military time zones'},
['g'] = {abbr = 'G', utc_offset = '07:00', df = 'iso', article = 'List of military time zones'},
['h'] = {abbr = 'H', utc_offset = '08:00', df = 'iso', article = 'List of military time zones'},
['h*'] = {abbr = 'H*', utc_offset = '08:30', df = 'iso', article = 'List of military time zones'},
['i'] = {abbr = 'I', utc_offset = '09:00', df = 'iso', article = 'List of military time zones'},
['i*'] = {abbr = 'I*', utc_offset = '09:30', df = 'iso', article = 'List of military time zones'},
['k'] = {abbr = 'K', utc_offset = '10:00', df = 'iso', article = 'List of military time zones'},
['k*'] = {abbr = 'K*', utc_offset = '10:30', df = 'iso', article = 'List of military time zones'},
['l'] = {abbr = 'L', utc_offset = '11:00', df = 'iso', article = 'List of military time zones'},
['l*'] = {abbr = 'L*', utc_offset = '11:30', df = 'iso', article = 'List of military time zones'},
['m'] = {abbr = 'M', utc_offset = '12:00', df = 'iso', article = 'List of military time zones'},
['m++'] = {abbr = 'M‡', utc_offset = '12:45', df = 'iso', article = 'List of military time zones'},
['m*'] = {abbr = 'M*', utc_offset = '13:00', df = 'iso', article = 'List of military time zones'},
['m+'] = {abbr = 'M†', utc_offset = '14:00', df = 'iso', df = 'iso', article = 'List of military time zones'},
['n'] = {abbr = 'N', utc_offset = '-01:00', df = 'iso', article = 'List of military time zones'},
['o'] = {abbr = 'O', utc_offset = '-02:00', df = 'iso', article = 'List of military time zones'},
['p'] = {abbr = 'P', utc_offset = '-03:00', df = 'iso', article = 'List of military time zones'},
['p*'] = {abbr = 'P*', utc_offset = '-03:30', df = 'iso', article = 'List of military time zones'},
['q'] = {abbr = 'Q', utc_offset = '-04:00', df = 'iso', article = 'List of military time zones'},
['q*'] = {abbr = 'Q*', utc_offset = '-04:30', df = 'iso', article = 'List of military time zones'},
['r'] = {abbr = 'R', utc_offset = '-05:00', df = 'iso', article = 'List of military time zones'},
['s'] = {abbr = 'S', utc_offset = '-06:00', df = 'iso', article = 'List of military time zones'},
['t'] = {abbr = 'T', utc_offset = '-07:00', df = 'iso', article = 'List of military time zones'},
['u'] = {abbr = 'U', utc_offset = '-08:00', df = 'iso', article = 'List of military time zones'},
['u*'] = {abbr = 'U*', utc_offset = '-08:30', df = 'iso', article = 'List of military time zones'},
['v'] = {abbr = 'V', utc_offset = '-09:00', df = 'iso', article = 'List of military time zones'},
['v*'] = {abbr = 'V*', utc_offset = '-09:30', df = 'iso', article = 'List of military time zones'},
['w'] = {abbr = 'W', utc_offset = '-10:00', df = 'iso', article = 'List of military time zones'},
['x'] = {abbr = 'X', utc_offset = '-11:00', df = 'iso', article = 'List of military time zones'},
['y'] = {abbr = 'Y', utc_offset = '-12:00', df = 'iso', article = 'List of military time zones'},
['z'] = {abbr = 'Z', utc_offset = '±00:00', df = 'iso', article = 'List of military time zones'},
 
----[[--------------------------< UE TR CR O R O_ F FM S E T SG >------------------------------------------------------------
-- this table entry filled by the code in p.time()
 
['utc_offsets'] = {abbr = '', utc_offset = '', df = 'iso', article = ''},
} -- end of tz table
 
--[[--------------------------< I S _ S E T >------------------------------------------------------------------
 
Whether variable is set or not. A variable is set when it is not nil and not empty.
 
create an error message
<args_t> is a sequence where [1] is template name and [2] is substituted error message
]]
 
local function is_seterror_msg ( var args_t)
return notsubstitute (varcfg.err_msg, == nil or var == ''args_t);
end
 
Line 559 ⟶ 55:
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};
dst_event_string = dst_event_string:lower(); -- force the string to lower case because that is how the tables above are indexed
Line 572 ⟶ 63:
end
return cfg.ordinals[ord], cfg.days[day], cfg.months[month];
end
 
Line 610 ⟶ 101:
]]
 
local function get_dst_month_day (timestamp, timezone, start)
local ord, weekday_num, month;
local first_day_of_dst_month_num;
Line 618 ⟶ 109:
 
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
Line 631 ⟶ 122:
if -1 == ord then -- j = t + 7×(n + 1) - (wt - w) mod 7 -- if event occurs on the last day-name of the month ('last Sunday of October')
days_in_month = get_days_in_month (year, month);
last_day_of_dst_month_num = os.date ('%w', os.time ({['year']=year, ['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) mod 7
Line 642 ⟶ 133:
--[[--------------------------< 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 0nil.
TODO: return error message?
TODO: limit check this? +/-n hours?
]]
 
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 nil; -- we require that all timezone tabletables have what appears to be a valid offset
end
end
Line 677 ⟶ 168:
 
For dst rules that specify utc event time the process is the same except that utc offset is not subtracted.
 
 
]]
 
local function make_dst_timestamps (timestamp, timezone)
local dst_begin, dst_end; -- dst begin and end time stamps
local year; -- current year
Line 691 ⟶ 181:
 
year = os.date ('%Y', timestamp); -- current year
utc_offset = get_utc_offset (timezone); -- in seconds
if not is_set (utc_offset) then -- utc offset is a required timezone property
return nil;
end
 
dst_b_month, dst_day = get_dst_month_day (timestamp, timezone, true); -- month and day that dst begins
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
utc_flag = tz[timezone].dst_time:find ('[Uu][Tt][Cc]%s*$'); -- set flag when dst events occur at a specified utc time
 
dst_begin = os.time ({['year'] = year, ['month'] = dst_b_month, ['day'] = dst_day, ['hour'] = dst_hour, ['min'] = dst_minute}); -- form start timestamp
Line 709 ⟶ 199:
end
 
dst_e_month, dst_day = get_dst_month_day (timestamp, timezone, false); -- month and day that dst ends
if not is_set (dst_e_month) then
return nil;
end
if is_set (tz[timezone].dst_e_time) then
dst_hour, dst_minute = tz[timezone].dst_e_time:match ('(%d%d):(%d%d)'); -- get ending dst time; this one for those locales that use different start and end times
utc_flag = tz[timezone].dst_e_time:find ('[Uu][Tt][Cc]%s*$'); -- set flag if dst is pegged to utc time
end
 
Line 736 ⟶ 226:
--[[--------------------------< G E T _ T E S T _ T I M E >----------------------------------------------------
 
decode ISO formatted date/time into a table suitable for os.time(). ForFallback testing,to this{{Timestamp}} time is utc just as isformat.
For testing, this time is UTC just as is returned by the os.time() function.
 
]]
Line 744 ⟶ 234:
local year, month, day, hour, minute, second;
 
year, month, day, hour, minute, second = iso_date:match ('(%d%d%d%d)\%-(%d%d)\%-(%d%d)T(%d%d):(%d%d):(%d%d)');
if not year then
year, month, day, hour, minute, second = iso_date:match ('^(%d%d%d%d)(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)$');
return nil; -- test time did not match the specified pattern
if not year then
return nil; -- test time did not match the specified patterns
end
end
return {['year'] = year, ['month'] = month, ['day'] = day, ['hour'] = hour, ['min'] = minute, ['sec'] = second};
end
 
--[[----------------------< G E T _ F U L L _ U T C _ O F F S E T >-----------------------------------------------
function p.offset ()
 
local dst_e_month;
Creates a standard UTC offset from numerical inputs, for function time to convert to a table. Expected inputs shall have the form:
return (dst_e_month);
<sign><hour><separator><portion>
where:
<sign> – optional; one of the characters: '+', '-' (hyphen), '±', '−' (minus); defaults to '+'
<hour> - one or two digits
<separator> - one of the characters '.' or ':'; required when <portion> is included; ignored else
<portion> - optional; one or two digits when <separator> is '.'; two digits else
 
returns correct utc offset string when input has a correct form; else returns the unmodified input
 
]]
 
local function get_full_utc_offset (utc_offset)
local h, m, sep, sign;
local patterns = {
'^([%+%-±−]?)(%d%d?)(%.)(%d%d?)$', -- one or two fractional hour digits
'^([%+%-±−]?)(%d%d?)(:)(%d%d)$', -- two minute digits
'^([%+%-±−]?)(%d%d?)[%.:]?$', -- hours only; ignore trailing separator
}
for _, pattern in ipairs(patterns) do -- loop through the patterns
sign, h, sep, m = mw.ustring.match (utc_offset, pattern);
if h then
break; -- if h is set then pattern matched
end
end
 
if not h then
return utc_offset; -- did not match a pattern
end
sign = ('' == sign) and '+' or sign; -- sign character is required; set to '+' if not specified
 
m = ('.' == sep) and ((sep .. m) * 60) or m or 0; -- fractional h to m
 
return string.format ('utc%s%02d:%02d', sign, h, m);
end
 
return p.offset;
--[=[-------------------------< P . T I M E >------------------------------------------------------------------
 
--[[--------------------------< T A B L E _ L E N >------------------------------------------------------------
This template takes several parameters; none are required:
 
1. the time zone abbreviation (positional, always the first unnamed parameter)
return number of elements in table
2. a date format flag; second positional parameter or |df=; can have one of several assigned values:
 
y – display output time in dmy format
]]
dmy – same as 'y'
 
mdy – default; included for completeness
local function table_len (tbl)
iso – display output time in YYYY-MM-DDTHH:mm format
local count = 0;
for _ in pairs (tbl) do
count = count + 1;
end
return count;
end
 
 
--[[--------------------------< F I R S T _ S E T >------------------------------------------------------------
 
scans through a list of parameter names that are aliases of each other and returns the value assigned to the
first args[alias] that has a set value; nil else. scan direction is right-to-left (top-to-bottom)
 
]]
 
local function first_set (list, args)
local i = 1;
local count = table_len (list); -- get count of items in list
while i <= count do -- loop through all items in list
if is_set( args[list[i]] ) then -- if parameter name in list is set in args
return args[list[i]]; -- return the value assigned to the args parameter
end
i = i + 1; -- point to next
end
end
 
 
--[=[-------------------------< T I M E >----------------------------------------------------------------------
 
This template takes several parameters (some positonal, some not); none are required:
1. the time zone abbreviation/UTC offset (positional, always the first unnamed parameter)
2. a date format flag; second positional parameter or |df=; can have one of several values
3. |dst= when set to 'no' disables dst calculations for locations that do not observe dst – Arizona in MST
4. |timeonly= when set to 'yes' only display the time
4. |_TEST_TIME_= a specific utc time in ISO date time format used for testing this code
5. |dateonly= when set to 'yes' only display the date
6. |hide-refresh = when set to 'yes' removes the refresh link
7. |hide-tz = when set to 'yes' removes the timezone name
8. |unlink-tz = when set to 'yes' unlinks the timzone name
9. |_TEST_TIME_= a specific utc time in ISO date time format used for testing this code
TODO: convert _TEST_TIME_ to |time=?
 
Timezone abbreviations can be found here: [[List_of_time_zone_abbreviations]]
 
For custom date format parameters |df-cust=, |df-cust-a=, |df-cust-p= use codes
described here: [[:mw:Help:Extension:ParserFunctions##time]]
 
]=]
 
--[=[local function p.time (frame)
local args = getArgs (frame);
local utc_timestamp, timestamp; -- current or _TEST_TIME_ timestamps; timestamp is local ST or DST time used in output
local dst_begin_ts, dst_end_ts; -- DST begin and end timestamps in UTC
local tz_abbr; -- select ST or DST timezone abbreviaion used in output
local tz_stringtime_string; -- holds output time/date in |df= format
local utc_offset;
local invert; -- true when southern hemisphere
local dfDF; -- date format flag; the |df= parameter
local is_dst_tz;
local hf; -- hour format flag; derived from the |df=dmy12 and |df=mdy12 parameters
 
local tz_aliases = data.tz_aliases; -- get the aliases table
if args[1] then
args[1] local tz_data = args[1]:lower()data.tz_data; -- makeget lowerthe casetz because tzdata table member indexes are lower case
 
local Timeonly = yesno(first_set (cfg.aliases['timeonly'], args)); -- boolean
if mw.ustring.match (args[1], 'utc[%+%-±−]?%d%d:%d%d') then -- if rendering time for a UTC offset timezone
local Dateonly = yesno(first_set (cfg.aliases['dateonly'], args)); -- boolean
tz['utc_offsets'].abbr = args[1]:upper():gsub('%-', '−'); -- set the link label to upper case and replace hyphen with a minus character (U+2212)
if Timeonly and Dateonly then -- invalid condition when both are set
tz['utc_offsets'].article = tz['utc_offsets'].abbr; -- article title same as abbreviation
Timeonly, Dateonly = false;
tz['utc_offsets'].utc_offset = mw.ustring.match (args[1], 'utc([%+%-±−]?%d%d:%d%d)'):gsub('−', '%-'); -- extract the offset value; replace minus character with hyphen
end
args[1] = 'utc_offsets'; -- point to the generic utc offsets table
end
local Hide_refresh = yesno(first_set (cfg.aliases['hide-refresh'], args)); -- boolean
if not is_set (tz[args[1]]) then
local Hide_tz = yesno(first_set (cfg.aliases['hide-tz'], args)); -- boolean
return '<span style="font-size:100%" class="error">{{time}} – unknown timezone ([[Template:Time#Error messages|help]])</span>';
local Unlink_tz = yesno(first_set (cfg.aliases['unlink-tz'], args)); -- boolean
end
local DST = first_set (cfg.aliases['dst'], args) or true; -- string 'always' or boolean
local Lang = first_set (cfg.aliases['lang'], args); -- to render in a language other than the local wiki's language
local DF_cust = first_set (cfg.aliases['df-cust'], args); -- custom date/time formats
local DF_cust_a = first_set (cfg.aliases['df-cust-a'], args); -- for am/pm sensitive formats
local DF_cust_p = first_set (cfg.aliases['df-cust-p'], args);
 
if not ((DF_cust_a and DF_cust_p) or -- DF_cust_a xor DF_cust_p
(not DF_cust_a and not DF_cust_p))then
return error_msg ({'Time', cfg.err_text['bad_df_pair']}); -- both are required
end
 
if args[1] then
args[1] = get_full_utc_offset (args[1]):lower(); -- make lower case because tz table member indexes are lower case
else
args[1] = 'utc'; -- default to utc
end
 
if mw.ustring.match (args[1], 'utc[%+%-±−]%d%d:%d%d') then -- if rendering time for a UTC offset timezone
df = args.df or args[2] or tz[args[1]].df or ''; -- template |df= overrides typical df from tz properties TODO: error check these values?
tz.abbr = args[1]:upper():gsub('%-', '−'); -- set the link label to upper case and replace hyphen with a minus character (U+2212)
if is_set (df) then
dftz.article = df:lower()tz.abbr; -- lower case because we will compare toarticle lowertitle casesame valuesas laterabbreviation
tz.utc_offset = mw.ustring.match (args[1], 'utc([%+%-±−]?%d%d:%d%d)'):gsub('−', '%-'); -- extract the offset value; replace minus character with hyphen
local s, t = mw.ustring.match (tz.utc_offset, '(±)(%d%d:%d%d)'); -- ± only valid for offset 00:00
if s and '00:00' ~= t then
return error_msg ({'Time', cfg.err_text['bad_sign']});
end
tz.df = 'iso';
args[1] = 'utc_offsets'; -- spoof to show that we recognize this timezone
else
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{}
if not tz then
return error_msg ({'Time', substitute (cfg.err_text['unknown_tz'], args[1])}); -- if the timezone given isn't in module:time/data(/sandbox)
end
end
 
DF = first_set (cfg.aliases['df'], args) or args[2] or tz.df or cfg.default_df; -- template |df= overrides typical df from tz properties
hf = df:match ('%l%ly(12)'); -- hf == '12' selects 12-hour AM/PM display; other values ignored and 24 hour clock time displayed
DF = DF:lower(); -- normalize to lower case
if is_set (hf) then
if not cfg.df_vals[DF] then
df = df:match ('(%l%ly)12'); -- extract df
return error_msg ({'Time', substitute (cfg.err_text['bad_format'], DF)});
end
 
Line 815 ⟶ 412:
local test_time = get_test_time (args._TEST_TIME_);
if not test_time then
return error_msg ({'Time', cfg.err_text['test_time']});
return '<span style="font-size:100%" class="error">{{time}} – malformed or incomplete _TEST_TIME_ ([[Template:Time#Error messages|help]])</span>';
end
 
-- utc_timestamp = os.time(get_test_time (args._TEST_TIME_));
utc_timestamp = os.time(test_time);
else
utc_timestamp = os.time (); -- get current server time (UTC)
end
utc_offset = get_utc_offset (args[1]); -- utc offset for specified timezone in seconds
timestamp = utc_timestamp + utc_offset; -- make local time timestamp
 
if 'noalways' == args.dstDST then -- forif timezonesneeded thatto DOalways observedisplay dst but for this ___location ...time
tz_abbrtimestamp = tz[args[1]].abbrtimestamp + 3600; -- ...add dsta ishour notfor observed (|dst=no) show time as standard time
tz_abbr = tz.dst_abbr; -- dst abbreviation
elseif not yesno(DST) then -- for timezones that DO observe dst but for this ___location ...
tz_abbr = tz.abbr; -- ... dst is not observed (|dst=no) show time as standard time
else
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 -- make sure we have all of the parts
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 error_msg ({'Time', cfg.err_text['bad_dst']});
return '<span style="font-size:100%" class="error">{{time}} – error calculating dst timestamps ([[Template:Time#Error messages|help]])</span>';
end
if invert then -- southern hemisphere; use beginning and ending of standard time in the comparison
if utc_timestamp >= dst_end_ts and utc_timestamp < dst_begin_ts then -- is current date time standard time?
tz_abbr = tz[args[1]].abbr; -- standard time abbreviation
else
timestamp = timestamp + 3600; -- add an hour for
tz_abbr = tz[args[1]].dst_abbr; -- dst abbreviation
end
else -- northern hemisphere
if utc_timestamp >= dst_begin_ts and utc_timestamp < dst_end_ts then -- all timestamps are UTC
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 -- if some but not all not all parts then emit error message
return error_msg ({'Time', substitute (cfg.err_text['bad_def'], args_t[1]:upper())});
return '<span style="font-size:100%" class="error">{{time}} – incomplete definition for ' .. args[1]:upper() .. ' ([[Template:Time#Error messages|help]])</span>';
else
tz_abbr = tz[args[1]].abbr; -- dst not observed for this timezone
end
end
if Dateonly then
if 'y' == df or 'dmy' == df then -- format the output (|df=y is legacy from original template)
if '12iso' == hfDF then -- |df=iso
DF = 'iso_date';
tz_string = mw.text.trim (os.date ('%l:%M %p, %e %B %Y', timestamp)); -- dmy, 12 hour am/pm
elseif DF:find ('^dmy') or 'y' == DF then -- |df=dmy, |df=dmy12, |df=dmy24, |df=y
DF = 'dmy_date';
else
tz_stringDF = os.date ('%R, %e %B %Ymdy_date', timestamp); -- dmydefault
end
 
elseif 'iso' == df then
elseif Timeonly or DF:match ('^%d+$') then -- time only of |df= is just digits
tz_string = os.date ('%FT%R', timestamp); -- iso
DF = table.concat ({'t', DF:match ('%l*(12)') or '24'}); -- |df=12, |df=24, |df=dmy12, |df=dmy24, |df=mdy12, |df=mdy24; default to t24
else
if '12' == hf then
elseif 'y' == DF or 'dmy24' == DF then
tz_string = mw.text.trim (os.date ('%l:%M %p, %B %e, %Y', timestamp)); -- mdy (legacy default)
DF = 'dmy';
 
elseif 'mdy24' == DF then
DF = 'mdy';
end
local dformat;
if is_set (DF_cust) then
dformat=DF_cust;
elseif is_set (DF_cust_a) then -- custom format is am/pm sensitive?
if 'am' == os.date ('%P', timestamp) then -- if current time is am
dformat = DF_cust_a; -- use custom am format
else
dformat = DF_cust_p; -- use custom pm format
tz_string = os.date ('%R, %B %e, %Y', timestamp); -- mdy (legacy default)
end
else
dformat = cfg.format[DF]; -- use format from tables or from |df=
end
 
time_string = frame:callParserFunction ({name='#time', args={dformat, '@'..timestamp, Lang}});
if Lang then
time_string = table.concat ({ -- bidirectional isolation of non-local language; yeah, rather brute force but simple
'<bdi lang="', -- start of opening bdi tag
Lang, -- insert rendered language code
'">', -- end of opening tag
time_string, -- insert the time string
'</bdi>' -- and close the tag
});
end
 
if not is_set (tz.article) then -- if some but not all not all parts then emit error message
return error_msg ({'Time', substitute (cfg.err_text['bad_def'], args_t[1]:upper())});
end
local refresh_link = (Hide_refresh and '') or
if not is_set (tz[args[1]].article) then -- if some but not all not all parts then emit error message
table.concat ({
return '<span style="font-size:100%" class="error">{{time}} – incomplete definition for ' .. args[1]:upper() .. ' ([[Template:Time#Error messages|help]])</span>';
' <span class="plainlinks" style="font-size:85%;">[[', -- open span
mw.title.getCurrentTitle():fullUrl({action = 'purge'}), -- add the a refresh link url
' ',
cfg['refresh-label'], -- add the label
']]</span>', -- close the span
});
 
local tz_tag = (Hide_tz and '') or
((Unlink_tz and table.concat ({' ', tz_abbr})) or -- unlinked
table.concat ({' [[', tz.article, '|', tz_abbr, ']]'})); -- linked
return table.concat ({time_string, tz_tag, refresh_link});
 
end
 
 
--[[--------------------------< U T C _ O F F S E T >----------------------------------------------------------
 
implements {{UTC offset}}
 
mimics templates {{Time/GMT offset}}, {{Time/EST offset}}, etc.
 
{{#invoke:Time|utc_offset|<tz>}} – for a stand-alone invoke
{{#invoke:Time|utc_offset}} – for an invoke in a template (<tz> is first positional parameter in the template call)
 
where <tz> is a timezone abbreviation known to Module:Time/data
 
returns a UTC offset string suitable for use with the {{#time:}} parser function:
{{#time:H:i | {{#invoke:Time|utc_offset|MST}} }}
{{#time:H:i | {{UTC_offset|MST}} }}
 
]]
 
local function utc_offset (frame)
local function apply_dst_ajdust (offset) -- local function to adjust standard time to daylight time; called when adjustment is needed
local hours, minutes = offset:match ('^(%-?%d%d):(%d%d)'); -- extract signed hours and minutes from specified offset
return string.format ('%s:%s', tonumber (hours) + 1, minutes); -- return optional sign hh:mm string
end
 
local args_t = getArgs (frame); -- fetch arguments; only {{{1}}}, timesone specifier is used
local refreshLink = mw.title.getCurrentTitle():fullUrl{action = 'purge'} -- create a refresh link
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);
 
if not args_t[1] then -- no timezone specifier
return error_msg ({'UTC offset', cfg.err_text['missing_tz']}); -- abandon with error message
end
 
local timezone = args_t[1]:lower(); -- lowercase for indexing into tz data tables
timezone = data.tz_aliases[timezone] or timezone; -- if <timezone> is an alias, map to its canonical value
 
if not data.tz_data[timezone] then -- timezone specifier not known
return error_msg ({'UTC offset', substitute (cfg.err_text['unknown_tz'], {timezone})}); -- abandon with error message
end
 
tz = data.tz_aliases[timezone] and data.tz_data[data.tz_aliases[timezone]] or data.tz_data[timezone]; -- fetch a copy of this timezone's data; <tz> is a page-global table used by functions called from this function
local utc_timestamp = os.time (); -- get current server time (UTC) in seconds; used to determine when dst adjustment should be applied
 
local timestamp = utc_timestamp + get_utc_offset (); -- make local time timestamp (in seconds)
local utc_offset;
 
local DST = first_set (cfg.aliases['dst'], args_t) or true; -- string 'always' or boolean
if 'always' == DST then -- if needed to always display dst time
utc_offset = apply_dst_ajdust (tz.utc_offset); -- return dst-adjusted timezone-offset from utc
elseif not yesno (DST) then -- for timezones that DO observe dst but for this ___location ...
utc_offset = tz.utc_offset; -- ... dst is not observed (|dst=no) show time as standard time
else
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
local dst_begin_ts, dst_end_ts, invert = make_dst_timestamps (timestamp); -- get begin and end dst timestamps and <invert> flag
if nil == dst_begin_ts or nil == dst_end_ts then -- if either of these are nil
return error_msg ({'UTC offset', cfg.err_text['bad_dst']}); -- abandon with error message
end
if invert then -- southern hemisphere; use beginning and ending of standard time in the comparison
if utc_timestamp >= dst_end_ts and utc_timestamp < dst_begin_ts then -- is current date time standard time?
utc_offset = tz.utc_offset; -- return timezone-offset from utc
else
utc_offset = apply_dst_ajdust (tz.utc_offset); -- return dst-adjusted timezone-offset from utc
end
else -- northern hemisphere
if utc_timestamp >= dst_begin_ts and utc_timestamp < dst_end_ts then -- is current date time daylight time?
utc_offset = apply_dst_ajdust (tz.utc_offset); -- return dst-adjusted timezone-offset from utc
else
utc_offset = tz.utc_offset; -- return timezone-offset from utc
end
end
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
return error_msg ({'UTC offset', substitute (cfg.err_text['bad_def'], args_t[1]:upper())});
else -- timezone does not use dst
utc_offset = tz.utc_offset; -- return timezone-offset from utc
end
end
local sign, hours, minutes = utc_offset:match ('^([%-%+]?)(%d%d?):(%d%d)')
if '' == sign then
sign = '+';
end
if 0 ~= tonumber (minutes) then
return string.format ('%s%s %s %s%s minutes', sign, tonumber(hours), ('1' == hours) and 'hour' or 'hours', sign, tonumber(minutes));
else
return string.format ('%s%s %s', sign, tonumber(hours), ('1' == hours) and 'hour' or 'hours');
end
end
 
 
return p; ]=]
--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]
 
return {
time = time,
utc_offset = utc_offset,
}