Content deleted Content added
No edit summary |
No edit summary |
||
(71 intermediate revisions by 5 users not shown) | |||
Line 1:
require
local yesno = require('Module:Yesno')
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{}
Line 12 ⟶ 15:
local function is_set( var )
return not (
end
--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------
Populates numbered arguments in a message string using an argument table.
]]
local function substitute (msg, args_t)
return args_t and mw.message.newRawMessage (msg, args_t):plain() or msg;
end
--[[--------------------------< E R R O R _ M S G >------------------------------------------------------------
create an error message
<args_t> is a sequence where [1] is template name and [2] is substituted error message
]]
local function error_msg (args_t)
return substitute (cfg.err_msg, args_t);
end
Line 30 ⟶ 55:
local function decode_dst_event (dst_event_string)
local ord, day, month;
dst_event_string = dst_event_string:lower(); -- force the string to lower case because that is how the tables above are indexed
Line 43 ⟶ 63:
end
return cfg.ordinals[ord], cfg.days[day], cfg.months[month];
end
Line 102 ⟶ 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 =
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 129 ⟶ 149:
return sign * ((hours * 3600) + (minutes * 60));
else
return nil; -- we require that all timezone
end
end
Line 206 ⟶ 226:
--[[--------------------------< G E T _ T E S T _ T I M E >----------------------------------------------------
decode ISO formatted date/time into a table suitable for os.time().
For testing, this time is UTC just as is returned by the os.time() function.
]]
Line 214 ⟶ 234:
local year, month, day, hour, minute, second;
year, month, day, hour, minute, second = iso_date:match ('(%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)$');
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};
Line 261 ⟶ 284:
return string.format ('utc%s%02d:%02d', sign, h, m);
end
--[[--------------------------< T A B L E _ L E N >------------------------------------------------------------
return number of elements in table
]]
local function table_len (tbl)
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 >----------------------------------------------------------------------
Line 278 ⟶ 337:
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]]
]=]
Line 289 ⟶ 351:
local utc_offset;
local invert; -- true when southern hemisphere
local
local is_dst_tz;
local
local
local Timeonly = yesno(first_set (cfg.aliases['timeonly'], args)); -- boolean
local Dateonly = yesno(first_set (cfg.aliases['dateonly'], args)); -- boolean
if Timeonly and Dateonly then -- invalid condition when both are set
Timeonly, Dateonly = false;
end
local Hide_refresh = yesno(first_set (cfg.aliases['hide-refresh'], args)); -- boolean
local Hide_tz = yesno(first_set (cfg.aliases['hide-tz'], args)); -- boolean
local Unlink_tz = yesno(first_set (cfg.aliases['unlink-tz'], args)); -- boolean
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
Line 317 ⟶ 392:
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 =
if not tz then
return
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
DF = DF:lower(); -- normalize to lower case
if not cfg.df_vals[DF] then
return error_msg ({'Time', substitute (cfg.err_text['bad_format'], DF)});
end
Line 338 ⟶ 412:
local test_time = get_test_time (args._TEST_TIME_);
if not test_time then
return error_msg ({'Time', cfg.err_text['test_time']});
end
Line 348 ⟶ 422:
timestamp = utc_timestamp + utc_offset; -- make local time timestamp
if '
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.dst_begins) and is_set (tz.dst_ends) and is_set (tz.dst_time) then -- make sure we have all of the parts
Line 358 ⟶ 432:
if nil == dst_begin_ts or nil == dst_end_ts then
return error_msg ({'Time', cfg.err_text['bad_dst']});
end
Line 377 ⟶ 451:
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 ({'Time', substitute (cfg.err_text['bad_def'], args_t[1]:upper())});
else
tz_abbr = tz.abbr; -- dst not observed for this timezone
Line 383 ⟶ 457:
end
if
if 'iso' ==
elseif
else
end
elseif
elseif 'y' ==
elseif 'mdy24' ==
end
local
if is_set (DF_cust) then
dformat=DF_cust;
elseif is_set (DF_cust_a) then -- custom format is am/pm sensitive?
dformat = DF_cust_a; -- use custom am format
else
dformat = DF_cust_p; -- use custom pm format
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 = (
table.concat ({
' <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 = (
((
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
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
Line 448 ⟶ 615:
]]
return {
time = time utc_offset = utc_offset,
}
|