Module:DatetimeUtil

From Fire Emblem Heroes Wiki
Jump to: navigation, search
Template-info.svg Documentation

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[edit source]

  • 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[edit source]

  • 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[edit source]

  • is_cargo (s)
Returns whether s is a valid Cargo datetime (same as ISO 8601, except without the T and the Z).
  • 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[edit source]

  • 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.
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&nbsp;– %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,
}