Module:Time

From IdleOn MMO Wiki

Warning: This module is still a work in progress.

The Time module is a utility module which provides many functions for easily working with time. The Instant class encapsulates a specific point in time, and the Duration class encapsulates a length of time with no particular point.

Including the Module

To use the Time module, it must first be included at the top of the module that wishes to use it.

local Time = require('Module:Time')

require('strict')

--- @alias TimeUnit 's'|'m'|'h'|'d'|'w'

--- The number of minutes in an hour.
local MINUTES_PER_HOUR = 60

--- The number of hours in a day.
local HOURS_PER_DAY = 24

--- The number of days in a week.
local DAYS_PER_WEEK = 7

--- The number of seconds in one second.
local SECONDS_PER_SECOND = 1

--- The number of seconds in one minute.
local SECONDS_PER_MINUTE = SECONDS_PER_SECOND * 60

--- The number of seconds in one hour.
local SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR

--- The number of seconds in one day.
local SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY

--- The number of seconds in one day.
local SECONDS_PER_WEEK = SECONDS_PER_DAY * DAYS_PER_WEEK

--- Lookup table for converting time units to seconds.
local UNIT_TO_SECONDS = {
    ['s'] = SECONDS_PER_SECOND,
    ['m'] = SECONDS_PER_MINUTE,
    ['h'] = SECONDS_PER_HOUR,
    ['d'] = SECONDS_PER_DAY,
    ['w'] = SECONDS_PER_WEEK
}

--- Truncates a value to the largest multiple of mod.
--- @param value integer
--- @param mod integer
--- @return integer
local function truncate(value, mod)
    return value - (value % mod)
end

--- Rounds a number towards zero.
--- @param value number The value to round
--- @return integer
local function roundToZero(value)
    return value < 0 and math.ceil(value) or math.floor(value)
end

-------------------------------------------------------------------------------
-- Duration class
-------------------------------------------------------------------------------

--- @alias DurationArgs { s: integer?, m: integer?, h: integer?, d: integer?, w: integer? }

--- @class Duration
--- @field seconds integer
local Duration = { _discriminator = 'Duration' }

--- Creates a new Duration instance from the provided arguments.
--- @param args DurationArgs
--- @return Duration
function Duration.new(args)
    --- Convert all units to seconds for easy conversions.
    local seconds = 0
    for unit, value in pairs(args or {}) do
        seconds = seconds + UNIT_TO_SECONDS[unit] * roundToZero(value)
    end

    return setmetatable({
        seconds = seconds
    }, { __index = Duration })
end

--- Creates a new Duration from the specified seconds.
--- @param seconds integer The number of seconds
--- @return Duration
function Duration.fromSeconds(seconds)
    return Duration.new({ s = seconds })
end

--- Creates a new Duration from the specified number of minutes.
--- @param minutes integer The number of minutes.
--- @return Duration
function Duration.fromMinutes(minutes)
    return Duration.new({ m = minutes })
end

--- Creates a new Duration from the specified number of hours.
--- @param hours integer The number of hours.
--- @return Duration
function Duration.fromHours(hours)
    return Duration.new({ h = hours })
end

--- Creates a new Duration from the specified number of days.
--- @param days integer The number of days.
--- @return Duration
function Duration.fromDays(days)
    return Duration.new({ d = days })
end

--- Creates a new Duration from the specified number of weeks.
--- @param weeks integer The number of weeks.
--- @return Duration
function Duration.fromWeeks(weeks)
    return Duration.new({ w = weeks })
end

--- Returns a copy of this Duration truncated to the nearest multiple of the
--- specified Duration.
--- @param duration Duration The Duration to truncate to.
--- @return Duration
function Duration:truncate(duration)
    return Duration.fromSeconds(truncate(self.seconds, duration.seconds))
end

--- Returns a copy of this Duration truncated to the nearest multiple of the
--- specified seconds.
--- @param seconds integer The number of seconds to truncate to.
--- @return Duration
function Duration:truncateSeconds(seconds)
    return self:truncate(Duration.fromSeconds(seconds))
end

--- Returns a copy of this Duration truncated to the nearest multiple of the
--- specified minutes.
--- @param minutes integer The number of minutes to truncate to.
--- @return Duration
function Duration:truncateMinutes(minutes)
    return self:truncate(Duration.fromMinutes(minutes))
end

--- Returns a copy of this Duration truncated to the nearest multiple of the
--- specified hours.
--- @param hours integer The number of hours to truncate to.
--- @return Duration
function Duration:truncateHours(hours)
    return self:truncate(Duration.fromHours(hours))
end

--- Returns a copy of this Duration truncated to the nearest multiple of the
--- specified days.
--- @param days integer The number of days to truncate to.
--- @return Duration
function Duration:truncateDays(days)
    return self:truncate(Duration.fromDays(days))
end

--- Returns a copy of this Duration truncated to the nearest multiple of the
-- specified weeks.
--- @param weeks integer The number of weeks to truncate to.
--- @return Duration
function Duration:truncateWeeks(weeks)
    return self:truncate(Duration.fromWeeks(weeks))
end

--- Returns a copy of this Duration plus the specified Duration.
--- @param duration Duration The Duration to add.
--- @return Duration
function Duration:add(duration)
    return Duration.fromSeconds(self.seconds + duration.seconds)
end

--- Returns a copy of this Duration plus the specified number of seconds.
--- @param seconds integer The number of seconds to add.
--- @return Duration
function Duration:addSeconds(seconds)
    return self:add(Duration.fromSeconds(seconds))
end

--- Returns a copy of this Duration plus the specified number of minutes.
--- @param minutes integer The number of minutes to add.
--- @return Duration
function Duration:addMinutes(minutes)
    return self:add(Duration.fromMinutes(minutes))
end

--- Returns a copy of this Duration plus the specified number of hours.
--- @param hours integer The number of hours to add.
--- @return Duration
function Duration:addHours(hours)
    return self:add(Duration.fromHours(hours))
end

--- Returns a copy of this Duration plus the specified number of days.
--- @param days integer The number of days to add.
--- @return Duration
function Duration:addDays(days)
    return self:add(Duration.fromDays(days))
end

--- Returns a copy of this Duration plus the specified number of weeks.
--- @param weeks integer The number of weeks to add.
--- @return Duration
function Duration:addWeeks(weeks)
    return self:add(Duration.fromWeeks(weeks))
end

--- Returns a copy of this Duration minus the specified Duration.
--- @param duration Duration The duration to subtracted.
--- @return Duration
function Duration:subtract(duration)
    return Duration.fromSeconds(self.seconds - duration.seconds)
end

--- Returns a copy of this Duration minus the specified number of seconds.
--- @param seconds integer The number of seconds to subtract.
--- @return Duration
function Duration:subtractSeconds(seconds)
    return self:subtract(Duration.fromSeconds(-seconds))
end

--- Returns a copy of this Duration minus the specified number of minutes.
--- @param minutes integer The number of minutes to subtract.
--- @return Duration
function Duration:subtractMinutes(minutes)
    return self:subtract(Duration.fromMinutes(-minutes))
end

--- Returns a copy of this Duration minus the specified number of hours.
--- @param hours integer The number of hours to subtract.
--- @return Duration
function Duration:subtractHours(hours)
    return self:subtract(Duration.fromHours(hours))
end

--- Returns a copy of this Duration minus the specified number of days.
--- @param days integer The number of days to subtract.
--- @return Duration
function Duration:subtractDays(days)
    return self:subtract(Duration.fromDays(days))
end

--- Returns a copy of this Duration minus the specified number of weeks.
--- @param weeks integer The number of weeks to subtract.
--- @return Duration
function Duration:subtracteWeeks(weeks)
    return self:subtract(Duration.fromWeeks(weeks))
end

--- Returns a copy of this Duration multiplied by the specified scalar.
--- @param scalar integer The scalar to multiply by.
--- @return Duration
function Duration:multiply(scalar)
    return Duration.fromSeconds(self.seconds * scalar)
end

--- Returns the number of times a specified Duration occurs within this
--- Duration.

--- Returns the result of dividing this duration by another duration or scalar
--- value. If divisor is a Duration, then the result is the whole number of
--- times that this Duration divides into it. If the divisor is a number, then
--- the result is this duration scaled down by the divisor.
--- @param divisor Duration|integer
--- @return integer|Duration
function Duration:divide(divisor)
    if type(divisor == 'number') then
        return Duration.fromSeconds(self.seconds / divisor)
    elseif type(divisor == 'table') then
        return math.floor(math.abs(self.seconds / divisor.seconds))
    else
        error('Cannot divide duration by: ' .. type(divisor))
    end
end

--- Returns the whole number of times this Duration can be divided by the
--- specified number of seconds.
--- @param seconds integer The number of seconds.
--- @return integer
function Duration:divideSeconds(seconds)
    --- @type integer
    return self:divide(Duration.fromSeconds(seconds))
end

--- Returns the whole number of times this Duration can be divided by the
--- specified number of minutes.
--- @param minutes integer The number of minutes.
--- @return integer
function Duration:divideMinutes(minutes)
    --- @type integer
    return self:divide(Duration.fromMinutes(minutes))
end

--- Returns the whole number of times this Duration can be divided by the
--- specified number of hours.
--- @param hours integer The number of hours.
--- @return integer
function Duration:divideHours(hours)
    --- @type integer
    return self:divide(Duration.fromHours(hours))
end

--- Returns the whole number of times this Duration can be divided by the
--- specified number of days.
--- @param days integer The number of days.
--- @return integer
function Duration:divideDays(days)
    --- @type integer
    return self:divide(Duration.fromDays(days))
end

--- Returns a copy of this duration with a positive length.
--- @return Duration
function Duration:abs()
    return self.seconds >= 0 and self or Duration.fromSeconds(-self.seconds)
end

--- Returns a copy of this Duration with the length negated.
--- @return Duration
function Duration:negate()
    return Duration.fromSeconds(-self.seconds)
end

--- Returns whether this Duration has a negative length.
--- @return boolean
function Duration:isNegative()
    return self.seconds < 0
end

--- Returns whether this Duration has a positive length.
--- @return boolean
function Duration:isPositive()
    return self.seconds > 0
end

--- Returns whether this Duration has a length of zero.
--- @return boolean
function Duration:isZero()
    return self.seconds == 0
end

--- Returns the whole number of seconds in this duration.
--- @return integer
function Duration:toSeconds()
    return self.seconds
end

--- Returns the whole number of minutes in this duration.
--- @return integer
function Duration:toMinutes()
    return roundToZero(self.seconds / SECONDS_PER_MINUTE)
end

--- Returns the whole number of minutes in this duration.
---@return integer
function Duration:toHours()
    return roundToZero(self.seconds / SECONDS_PER_HOUR)
end

--- Returns the whole number of days in this duration.
--- @return integer
function Duration:toDays()
    return roundToZero(self.seconds / SECONDS_PER_DAY)
end

--- Returns the whole number of weeks in this duration.
--- @return integer
function Duration:toWeeks()
    return roundToZero(self.seconds / SECONDS_PER_WEEK)
end

-------------------------------------------------------------------------------
-- Instant class
-------------------------------------------------------------------------------

--- @class Instant
--- @field timestamp integer
local Instant = { _discriminator = 'Instant' }

--- Creates a new Instant at the specified timestamp.
--- @param when integer|osdateparam? The timestamp or date format.
--- @return Instant
function Instant.new(when)
    return setmetatable({
        timestamp = type(when) == 'table' and os.time(when) or when or os.time()
    }, { __index = Instant })
end

--- Creates a new Instant truncated to the beginning of the current minute.
--- @return Instant
function Instant.fromCurrentMinute()
    return Instant.new():truncateMinutes(1)
end

--- Creates a new Instant truncated to the beginning of the current hour.
--- @return Instant
function Instant.fromCurrentHour()
    return Instant.new():truncateHours(1)
end

--- Creates a new Instant truncated to the beginning of the current day.
--- @return Instant
function Instant.fromCurrentDay()
    return Instant.new():truncateDays(1)
end

--- Creates a new Instant truncated to the beginning of the current week.
--- @return Instant
function Instant.fromCurrentWeek()
    return Instant.new():truncateWeeks(1)
end

--- Returns a copy of this Instant truncated to the nearest multiple of the
--- specified Duration.
--- @param duration Duration The duration to truncate to.
--- @return Instant
function Instant:truncate(duration)
    return Instant.new(truncate(self.timestamp, duration.seconds))
end

--- Returns a copy of this Instant truncated to the nearest multiple of the
--- specified seconds.
--- @param seconds integer The number of seconds
--- @return Instant
function Instant:truncateSeconds(seconds)
    return self:truncate(Duration.fromSeconds(seconds))
end

--- Returns a copy of this Instant truncated to the nearest multiple of the
--- specified minutes.
--- @param minutes integer The number of minutes
--- @return Instant
function Instant:truncateMinutes(minutes)
    return self:truncate(Duration.fromMinutes(minutes))
end

--- Returns a copy of this Instant truncated to the nearest multiple of the
--- specified hours.
--- @param hours integer
--- @return Instant
function Instant:truncateHours(hours)
    return self:truncate(Duration.fromHours(hours))
end

--- Returns a copy of this Instant truncated to the nearest multiple of the
--- specified days.
--- @param days integer The number of days.
--- @return Instant
function Instant:truncateDays(days)
    return self:truncate(Duration.fromDays(days))
end

--- Returns a copy of this Instant truncated to the nearest multiple of the
--- specified weeks.
--- @param weeks integer The number of weeks.
--- @return Instant
function Instant:truncateWeeks(weeks)
    return self:truncate(Duration.fromWeeks(weeks))
end

--- Returns a copy of this Instant plus the specified duration.
--- @param duration Duration The duration to add.
--- @return Instant
function Instant:add(duration)
    return Instant.new(self.timestamp + duration.seconds)
end

--- Returns a copy of this Instant plus the specified seconds.
--- @param seconds integer The number of seconds to add.
--- @return Instant
function Instant:addSeconds(seconds)
    return self:add(Duration.fromSeconds(seconds))
end

--- Returns a copy of this Instant plus the specified minutes.
--- @param minutes integer The number of minutes to add.
--- @return Instant
function Instant:addMinutes(minutes)
    return self:add(Duration.fromMinutes(minutes))
end

--- Returns a copy of this Instant plus the specified hours.
--- @param hours integer The number of hours to add.
--- @return Instant
function Instant:addHours(hours)
    return self:add(Duration.fromHours(hours))
end

--- Returns a copy of this Instant plus the specified days.
--- @param days integer THe number of days to add.
--- @return Instant
function Instant:addDays(days)
    return self:add(Duration.fromDays(days))
end

--- Returns a copy of this Instant plus the specified weeks.
--- @param weeks integer
--- @return Instant
function Instant:addWeeks(weeks)
    return self:add(Duration.fromWeeks(weeks))
end

--- Returns a copy of this Instant minus the specified Duration.
--- @param duration Duration
function Instant:subtract(duration)
    return Instant.new(self.timestamp - duration.seconds)
end

--- Returns a copy of this Instant minus the specified number of seconds.
--- @param seconds integer
--- @return Instant
function Instant:subtractSeconds(seconds)
    return self:subtract(Duration.fromSeconds(seconds))
end

--- Returns a copy of this Instant minus the specified number of minutes.
--- @param minutes integer
--- @return Instant
function Instant:subtractMinutes(minutes)
    return self:subtract(Duration.fromMinutes(minutes))
end

--- Returns a copy of this Instant minus the specified number of hours.
--- @param hours integer
--- @return Instant
function Instant:subtractHours(hours)
    return self:subtract(Duration.fromHours(hours))
end

--- Returns a copy of this Instant minus the specified number of days.
--- @param days integer
--- @return Instant
function Instant:subtractDays(days)
    return self:subtract(Duration.fromDays(days))
end

--- Returns a copy of this Instant minus the specified number of weeks.
--- @param weeks integer
--- @return Instant
function Instant:subtractWeeks(weeks)
    return self:subtract(Duration.fromWeeks(weeks))
end

--- Calculates the Duration from this Instant to the specified Instant.
--- @param other Instant The other Instant.
--- @return Duration
function Instant:between(other)
    return Duration.fromSeconds(self.timestamp - other.timestamp)
end

--- Calculates the Duration from this Instant to the epoch.
--- @return Duration
function Instant:toDuration()
	return Duration.fromSeconds(self.timestamp);
end

--- Checks whether this Instant occurs before another Instant.
---@param other Instant
---@return boolean
function Instant:isBefore(other)
    return self.timestamp < other.timestamp
end

--- Checks whether this Instant occurs after another Instant.
--- @param other Instant The other instant.
--- @return boolean
function Instant:isAfter(other)
    return self.timestamp > other.timestamp
end

--- Checks whether this Instant is equal to another Instant.
--- @param other Instant The other instant.
--- @return boolean
function Instant:equals(other)
    return self.timestamp == other.timestamp
end

--- Gets the Unix timestamp of this Instant.
--- @return integer
function Instant:getTimestamp()
    return self.timestamp
end

return {
    Duration = Duration,
    Instant = Instant
}