Lua metamodule containing functions for datetime formatting. All datetimes are assumed to be in UTC.
t
: A Unix timestamp; that is, the number of seconds since the Unix epoch ().
Constants
- MIN_TIME =
'1970-01-01T00:00:00Z'
- ISO 8601 representation of the Unix epoch.
- MAX_TIME =
'2038-01-19T03:14:07Z'
- ISO 8601 representation of the furthest time that can fit into a signed 32-bit Unix timestamp. Only datetimes between MIN_TIME and MAX_TIME (inclusive) are considered safe for use.
- MONTHS_FULL =
{'January', 'February', 'March', ...}
- Full English names of the months as a Lua array.
- MONTHS_SHORT =
{'Jan', 'Feb', 'Mar', ...}
- Abbreviated English names of the months as a Lua array.
ISO 8601 formatting
- is_iso8601 (s)
- Returns whether s is a valid ISO 8601 datetime.
- to_iso8601 (t)
- Converts a Unix timestamp into its ISO 8601 representation.
- from_iso8601 (s)
- If the given argument is a valid ISO 8601 datetime, returns its corresponding Unix timestamp.
- parse_iso8601 (s)
- If the given argument is a valid ISO 8601 datetime, parses it and returns 6 values: year, month, day, hours, minutes, and seconds, in that order.
- make_iso8601 (y, m, d, hh = 0, mm = 0, ss = 0)
- Returns the ISO 8601 datetime with the given year, month, day, hours, minutes, and seconds.
Cargo datetime formatting
- is_cargo (s)
- Returns whether s is a valid Cargo datetime (same as ISO 8601, except without the
T
and theZ
).
- to_cargo (t)
- Converts a Unix timestamp into its Cargo representation.
- from_cargo (s)
- If the given argument is a valid Cargo datetime, returns its corresponding Unix timestamp. Otherwise, if it is a valid Cargo date (just the
YYYY-MM-DD
part), returns a Unix timestamp representing 00:00 UTC on the given date.
- parse_cargo (s)
- If the given argument is a valid Cargo datetime, parses it and returns 6 values: year, month, day, hours, minutes, and seconds, in that order. If it is a valid Cargo date instead, the three time values are 0.
- make_cargo (y, m, d, hh = 0, mm = 0, ss = 0)
- Returns the Cargo datetime with the given year, month, day, hours, minutes, and seconds. This function always produces a datetime and never a date even if the time values are all omitted.
Wiki formatting
- ht (t_or_s)
- Formats a time according to ISO 8601 and attaches a Pacific Time tooltip to it. If the argument is a Lua number, it is treated as a Unix timestamp; otherwise, it must be either an ISO 8601 or Cargo datetime. A warning is generated for any invalid datetime arguments. This implementation properly handles Daylight Saving Time changes.
- HT {t_or_s}
- For use from wikitext. Notice the uppercase function name. Implements Template:HT.
- ht_range (t_or_s1, t_or_s2)
- Formats a datetime range. Converts both arguments to ISO 8601 tooltips and joins them with a hyphen. Either datetime can be nil in order to support indefinite datetime ranges.
The above documentation is transcluded from Module:DatetimeUtil/doc. (edit | history)
local LibraryUtil = require 'libraryUtil'
local MONTHS_FULL = {'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'}
local MONTHS_SHORT = {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'}
local PDT_RANGES = {
[2017] = {1489312800, 1509872400}, -- Sun Mar 12 10:00:00 2017 - Sun Nov 5 09:00:00 2017
[2018] = {1520762400, 1541322000}, -- Sun Mar 11 10:00:00 2018 - Sun Nov 4 09:00:00 2018
[2019] = {1552212000, 1572771600}, -- Sun Mar 10 10:00:00 2019 - Sun Nov 3 09:00:00 2019
[2020] = {1583661600, 1604221200}, -- Sun Mar 8 10:00:00 2020 - Sun Nov 1 09:00:00 2020
[2021] = {1615716000, 1636275600}, -- Sun Mar 14 10:00:00 2021 - Sun Nov 7 09:00:00 2021
[2022] = {1647165600, 1667725200}, -- Sun Mar 13 10:00:00 2022 - Sun Nov 6 09:00:00 2022
[2023] = {1678615200, 1699174800}, -- Sun Mar 12 10:00:00 2023 - Sun Nov 5 09:00:00 2023
[2024] = {1710064800, 1730624400}, -- Sun Mar 10 10:00:00 2024 - Sun Nov 3 09:00:00 2024
[2025] = {1741514400, 1762074000}, -- Sun Mar 9 10:00:00 2025 - Sun Nov 2 09:00:00 2025
}
local get_dst_ranges = function (s, e)
mw.log('local PDT_RANGES = {')
for year = s, e do
-- 2nd Sunday in March, 02:00 PST
local datetime = {year = year, month = 3, day = 1, hour = 10}
while os.date('!%w', os.time(datetime)) ~= '0' do
datetime.day = datetime.day + 1
end
datetime.day = datetime.day + 7
local pdt_start = os.time(datetime)
-- 1st Sunday in November, 02:00 PDT
datetime.month = 11
datetime.day = 1
datetime.hour = 9
while os.date('!%w', os.time(datetime)) ~= '0' do
datetime.day = datetime.day + 1
end
local pdt_end = os.time(datetime)
mw.log(('\t[%d] = {%d, %d}, -- %s - %s'):format(year, pdt_start, pdt_end, os.date('!%c', pdt_start), os.date('!%c', pdt_end)))
end
mw.log('}')
end
local is_iso8601 = function (str)
return type(str) == 'string' and string.find(str, '^(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z$') ~= nil
end
local to_iso8601 = function (t)
return os.date('!%Y-%m-%dT%H:%M:%SZ', t)
end
local from_iso8601 = function (str)
local y, m, d, hh, mm, ss = string.match(str, '^(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z$')
if ss then
return os.time {year = y, month = m, day = d, hour = hh, min = mm, sec = ss}
end
end
local parse_iso8601 = function (str)
local y, m, d, hh, mm, ss = string.match(str, '^(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z$')
if ss then
return tonumber(y), tonumber(m), tonumber(d), tonumber(hh), tonumber(mm), tonumber(ss)
end
end
local make_iso8601 = function (y, m, d, hh, mm, ss)
return os.date('!%Y-%m-%dT%H:%M:%SZ', os.time {year = y, month = m, day = d, hour = hh or 0, min = mm or 0, sec = ss or 0})
end
local MIN_TIME = to_iso8601(0)
local MAX_TIME = to_iso8601(0x7FFFFFFF)
local is_cargo = function (str)
return type(str) == 'string' and string.find(str, '^(%d+)%-(%d+)%-(%d+) (%d+):(%d+):(%d+)$') ~= nil
end
local to_cargo = function (t)
return os.date('!%Y-%m-%d %H:%M:%S', t)
end
local from_cargo = function (str)
local y, m, d, hh, mm, ss = string.match(str, '^(%d+)%-(%d+)%-(%d+) (%d+):(%d+):(%d+)$')
if ss then
return os.time {year = y, month = m, day = d, hour = hh, min = mm, sec = ss}
end
y, m, d = string.match(str, '^(%d+)%-(%d+)%-(%d+)$')
if d then
return os.time {year = y, month = m, day = d, hour = 0}
end
end
local parse_cargo = function (str)
local y, m, d, hh, mm, ss = string.match(str, '^(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)$')
if ss then
return tonumber(y), tonumber(m), tonumber(d), tonumber(hh), tonumber(mm), tonumber(ss)
end
y, m, d = string.match(str, '^(%d+)-(%d+)-(%d+)$')
if d then
return tonumber(y), tonumber(m), tonumber(d), 0, 0, 0
end
end
local make_cargo = function (y, m, d, hh, mm, ss)
return os.date('!%Y-%m-%d %H:%M:%S', os.time {year = y, month = m, day = d, hour = hh or 0, min = mm or 0, sec = ss or 0})
end
local Template_Hover_nocargo = function (text, title)
if title == nil or title == '' then
return text
end
return tostring(mw.html.create('span'):attr('title', mw.text.decode(title)) -- mw.html automatically HTML-escapes attributes
:css('border-bottom', '0'):css('text-decoration', 'underline dotted'):css('cursor', 'help')
:wikitext(text))
end
local ht1 = function (t)
LibraryUtil.checkTypeMulti('t', 1, t, {'number', 'string', 'nil'})
if t == nil or t == '' then
return '<i>(Unknown datetime)</i>'
end
local timestamp = type(t) == 'number' and t or from_iso8601(t) or from_cargo(t)
if timestamp then
local datetime = os.date('!*t', timestamp)
local pdt_range = PDT_RANGES[datetime.year] -- done manually as datetime.isdst is always false
local is_dst = pdt_range and timestamp >= pdt_range[1] and timestamp < pdt_range[2]
local pdt_datetime = os.date('!*t', timestamp - 60 * 60 * (is_dst and 7 or 8))
-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time:
-- "If the element does not have a datetime attribute, it must not have any
-- element descendants, and the datetime value is the element's child text content."
local text = tostring(mw.html.create('time'):wikitext(to_iso8601(timestamp)))
local title = ('Pacific %s Time: %d/%d/%02d at %02d:%02d %s'):format(
is_dst and 'Daylight' or 'Standard',
pdt_datetime.month, pdt_datetime.day, pdt_datetime.year % 100,
(pdt_datetime.hour - 1) % 12 + 1, pdt_datetime.min, pdt_datetime.hour >= 12 and 'p.m.' or 'a.m.')
return Template_Hover_nocargo(text, title)
else
mw.addWarning('Invalid timestamp: ' .. t)
return require 'Module:Error'.error(t)
end
end
local ht_range = function (t1, t2)
return ('%s – %s'):format(t1 ~= nil and ht1(t1) or '', t2 ~= nil and ht1(t2) or '')
end
local ht = function (args)
return ht1(tonumber(args[1]) or args[1])
end
local p = require 'Module:MakeMWModule'.makeMWModule {ht = ht}
return {
MIN_TIME = MIN_TIME,
MAX_TIME = MAX_TIME,
MONTHS_FULL = MONTHS_FULL,
MONTHS_SHORT = MONTHS_SHORT,
is_iso8601 = is_iso8601,
to_iso8601 = to_iso8601,
from_iso8601 = from_iso8601,
parse_iso8601 = parse_iso8601,
make_iso8601 = make_iso8601,
is_cargo = is_cargo,
to_cargo = to_cargo,
from_cargo = from_cargo,
parse_cargo = parse_cargo,
make_cargo = make_cargo,
HT = p.ht,
HT_ = p.ht_,
ht = ht1,
ht_range = ht_range,
}