Module:Time
Jump to navigation
Jump to search
Details
The Time module offers a Lua class for parsing times in the format used for audio and video links. It's intended to be used by other Lua modules instead of templates.
It has no dependencies and exports a Time
"class" you can use directly. It provides annotations documenting the various functions that are understood by Lua Language Server.
--- Module:Time
--- by User:LtDk, licensed CC-BY-SA 4.0
--- Simple Lua class for parsing times.
local Time = {}
local TimeKeys = {
hours = true,
minutes = true,
seconds = true,
}
--- HH:MM:SS time, allowing both MM:SS and SS as well.
--- Automatically converts overflowed minutes and seconds into larger units.
--- @class Time
--- @field hours number
--- @field minutes number
--- @field seconds number
local TimeProto = {}
local TimeMeta = {}
--- Checks if a value is a Time
--- @param value any
--- @return boolean
function Time.isTime(value)
return type(value) == 'table' and rawequal(getmetatable(value), TimeMeta)
end
function TimeMeta.__eq(lhs, rhs)
if not Time.isTime(rhs) then
return false
end
return lhs.hours == rhs.hours and lhs.minutes == rhs.minutes and lhs.seconds == rhs.seconds
end
function TimeMeta.__lt(lhs, rhs)
if not Time.isTime(rhs) then
error('cannot compare Time with non-Time')
end
local hours = lhs.hours - rhs.hours
if hours ~= 0 then
return hours < 0
end
local minutes = lhs.minutes - rhs.minutes
if minutes ~= 0 then
return minutes < 0
end
local seconds = lhs.seconds - rhs.seconds
return seconds < 0
end
function TimeMeta.__le(lhs, rhs)
if not Time.isTime(rhs) then
error('cannot compare Time with non-Time')
end
local hours = lhs.hours - rhs.hours
if hours ~= 0 then
return hours < 0
end
local minutes = lhs.minutes - rhs.minutes
if minutes ~= 0 then
return minutes < 0
end
local seconds = lhs.seconds - rhs.seconds
return seconds <= 0
end
--- Times automatically convert excess seconds and minutes into the appropriate units.
function TimeMeta:__newindex(k, v) --
if not TimeKeys[k] then
error(string.format('invalid key %s', k))
end
if v ~= nil and (type(v) ~= 'number' or math.floor(v) ~= v or v < 0) then
error(string.format('%s must be whole number, was %s', k, v))
end
if k == 'seconds' and v >= 60 then
local hours = rawget(self, 'hours') or 0
local minutes = rawget(self, 'minutes') or 0
local seconds = v or 0
minutes = minutes + math.floor(seconds / 60)
seconds = seconds % 60
hours = hours + math.floor(minutes / 60)
minutes = minutes % 60
rawset(self, '__hours', hours)
rawset(self, '__minutes', minutes)
rawset(self, '__seconds', seconds)
elseif k == 'minutes' and v >= 60 then
local hours = rawget(self, 'hours') or 0
local minutes = v or 0
hours = hours + math.floor(minutes / 60)
minutes = minutes % 60
rawset(self, '__hours', hours)
rawset(self, '__minutes', minutes)
else
rawset(self, '__hours', v or 0)
end
end
--- We rename the actual keys to ensure newindex always fires.
function TimeMeta:__index(k)
if not TimeKeys[k] then
return TimeProto[k]
end
return rawget(self, '__' .. k) or 0
end
--- Times are formatted as HH:MM:SS, MM:SS, or SS based upon the largest nonzero unit.
function TimeMeta:__tostring()
if self.hours ~= 0 then
return string.format('%02d:%02d:%02d', self.hours, self.minutes, self.seconds)
elseif self.minutes ~= 0 then
return string.format('%02d:%02d', self.minutes, self.seconds)
else
return tostring(self.seconds)
end
end
--- Parses a time from a string. Accepts HH:MM:SS, MM:SS, and SS.
--- @param time string
--- @return Time
function Time.parse(time)
time = tostring(time)
local one = nil
local one_n = nil
local two = nil
local two_n = nil
local three = nil
local three_n = nil
local i, j = string.find(time, ':', 1, true)
if i == nil then
one = time
else
one = string.sub(time, 1, i - 1)
end
one_n = tonumber(one, 10)
if one_n == nil then
error(string.format('%q was not an integer in %q', one, time))
end
if j ~= nil then
local k, l = string.find(time, ':', j + 1, true)
if k == nil then
two = string.sub(time, j + 1)
else
two = string.sub(time, j + 1, k - 1)
three = string.sub(time, l + 1)
end
two_n = tonumber(two, 10)
if two_n == nil then
error(string.format('%q was not an integer in %q', two, time))
end
if three ~= nil then
three_n = tonumber(three, 10)
if three_n == nil then
error(string.format('%q was not an integer in %q', three, time))
end
end
end
return Time.new(one_n, two_n, three_n)
end
--- Creates a new Time from parts.
--- If three values are provided, they're interpreted as hours, minutes, and seconds.
--- If two values are provided, they're interpreted as minutes and seconds.
--- If one value is provided, it's interpreted as seconds.
--- @param hours number?
--- @param minutes number?
--- @param seconds number?
--- @overload fun(hours: number, minutes: number, seconds: number): Time
--- @overload fun(minutes: number, seconds: number): Time
--- @overload fun(seconds: number): Time
--- @overload fun(): Time
--- @return Time
function Time.new(hours, minutes, seconds)
if hours == nil then
seconds = 0
minutes = 0
hours = 0
elseif minutes == nil then
seconds = hours
minutes = 0
hours = 0
elseif seconds == nil then
seconds = minutes
minutes = hours
hours = 0
end
if type(hours) ~= 'number' and (math.floor(hours) ~= hours or hours < 0) then
error(string.format('hours must be whole number, was %q', hours))
end
if type(minutes) ~= 'number' and (math.floor(minutes) ~= minutes or minutes < 0) then
error(string.format('minutes must be whole number, was %q', minutes))
end
if type(seconds) ~= 'number' and (math.floor(seconds) ~= seconds or seconds < 0) then
error(string.format('seconds must be whole number, was %q', seconds))
end
minutes = minutes + math.floor(seconds / 60)
seconds = seconds % 60
hours = hours + math.floor(minutes / 60)
minutes = minutes % 60
return setmetatable(
{ hours = hours, minutes = minutes, seconds = seconds },
TimeMeta
)
end
return Time