Working with time can be very tricky in any language or environment.
Luckily a lot of the complicated stuff is handled (or irrelevant) for us in Garry's Mod. Still, timing is an important aspect of Garry's Mod. It's a hard subject to avoid.
Timeouts, cooldowns, timing calculations, time comparisons, etc. Eventually you'll find yourself calculating stuff with time.
gm_timelib
aims to make this inevitable and relatively annoying task easy, readable, and simple.
gm_timelib
offers structures and tools for handling:
- Time Instances (a single moment in, or amount of time)
- Time Ranges (a range of duration/times)
🏫 Here are some examples:
local extraTime = Time.Hours( 3 )
-- Extend a given ban's unban time by 3 hours
local function extendBan( ban )
ban.unban = (ban.unban + extraTime).As.Timestamp
end
-- Throttle a function to once per second
local lastRun = Time.Now
local delay = (1).Second
local function _doStuff()
-- Run expensive stuff
end
local function doStuff()
if Time.Since( lastRun ) < delay then return end
lastRun = Time.Now + delay
return _doStuff()
end
-- Reward people who joined during an event
-- Create a TimeRange between two timestamps
local eventRange = event.Start .. event.End
local function checkPly( ply )
local joinedAt = ply:TimeConnected().Seconds.Ago
-- Check if joinedAt is inside the eventRange
if eventRange[joinedAt] then
ply:GiveMoney( 5000 )
ply:ChatPrint( "Thanks for playing our event!" )
end
end
Basic Installation instructions
Simply download or clone the repositry into your addons directory - all done!
You may also repackage this addon within your addon if you prefer, though I highly discourage this.
Click here to read my rant about re-packaging dependencies inside addons
Dependency management in Garry's Mod is garbage. If we had a proper system for dependency management, it would be a lot easier to share projects like this.
Popular libraries like NetStream have been reasonably successful with their use in Starfall and others, but they hit an issue too: How do you update it?
They released netstream2 but not all of the developers who used the tool realized or bothered to update it.
So now what do you do when two addons use two different versions of netstream? It kind of sucks.
As-is, the best way to use lua libraries is to make them a dependency on your workshop page, and to print a good error if the dependency doesn't exist on the server.
A full set of GLuaTest specs have been included with this project. If you learn better by reading the code, I suggest you check them out
Simply require gtimelib
in your script:
require( "gtimelib" )
Amounts of time can be created as follows:
Time.Seconds( 5 )
Time.Minutes( 10 )
Time.Hours( 15 )
Time.Days( 20 )
Time.Weeks( 25 )
Time.Months( 30 )
Time.Years( 35 )
You can also use the cursed other syntax:
(5).Seconds
(10).Minutes
local extraTime = 5
extraTime.Hours
-- Until
local nextEvent = Time.Now + Time.Hours( 5 )
local timeRemaining = Time.Until( nextEvent )
-- Since
local lastEvent = Time.Now - Time.Hours( 3 )
local timeSince = Time.Since( lastEvent )
-- Ago
local fiveHoursAgo = os.time() - ( 5 * 60 * 60 )
-- These two line do the same thing
local timeInstance = (5).Hours.Ago
-- Hence
-- (The opposite of "ago")
local fiveHoursHence = os.time() + ( 5 * 60 * 60 )
-- Again, these two lines are effectively the same
local timeInstance = (5).Hours.Hence
-- Supports "step-down" conversions
local a = Time.Minutes( 5 ).As.Seconds
assert( a == ( 5 * 60 ) )
-- Also supports "step-up" conversions
local a = Time.Minutes( 5 ).As.Hours
assert( a == ( 5 / 60 ) )
local a = Time.Hours( 2 ) + Time.Minutes( 5 )
assert( a.As.Seconds == (60 * 2) + 5 )
local a = Time.Minutes( 5 ) - Time.Minutes( 1 )
assert( a.As.Minutes == 4 )
-- Adding/subtracting normal integers works fine,
-- but the integers are treated as Seconds
local a = Time.Seconds( 10 ) + 10
assert( a.As.Seconds == 20 )
local a = Time.Minutes( 5 ) * 2
assert( a.As.Minutes == 10 )
local a = Time.Hours( 5 ) / Time.Minutes( 60 )
assert( a == 5 )
local a = Time.Minute( 5 )
local b = Time.Minute( 10 )
assert( a < b )
local a = Time.Minutes( 3 )
local b = Time.Minutes( 3 )
assert( a == b )
A TimeRange
describes a duration, or a range between two TimeInstances
local a = (5).Hours.Ago
local b = Time.Now
local range = a .. b -- You now have a TimeRange object!
You can check if a TimeInstance
is contained within a TimeRange
:
local range = (5).Hours.Ago .. Time.Now
local timeInstance = (10).Minutes.Ago
assert( range[timeInstance] == true )
You can also check if a TimeRange
is entirely contained within another TimeRange
:
local rangeA = (5).Hours.Ago .. Time.Now
local rangeB = (20).Minutes.Ago .. (5).Minutes.Ago
assert( rangeA[rangeB] == true )
When working with timestamps, sometimes you don't want to have everything be relative to os.time()
.
There are some circumstances where something like CurTime()
is more applicable to your situation.
You can actually create an entirely new Time
object relative to your preferred time function. Take a look:
local MyTime = Time.Basis( CurTime )
assert( MyTime.Now == CurTime() )
This is a very flexible way to use a Time
object. You can pass any function you want into Time.Basis
:
local myTimeFunc = function() return 5 end
local MyTime = Time.Basis( myTimeFunc )
assert( MyTime.Now == 5 )
You can use your generated Time
object the exact same way you use the normal Time
object:
local MyTime = Time.Basis( CurTime )
MyTime.Seconds( 5 ) -- Still just 5 seconds, the same as `Time.Seconds( 5 )`
The only difference is the relative time functions will be based on the function you passed into Basis
.