Skip to content

Latest commit

 

History

History
268 lines (200 loc) · 7.06 KB

README.md

File metadata and controls

268 lines (200 loc) · 7.06 KB

gm_timelib

⌛ A convenience library for working with Time in Garry's Mod


Intro

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

Installation

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.

Usage

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

Setup

Simply require gtimelib in your script:

require( "gtimelib" )

TimeInstance

A TimeInstance can be a certain amount of time

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

They can also refer to a specific moment in time

Until / Since
-- 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 / Hence
-- 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

A TimeInstance supports time-conversions too!

-- 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 ) )


Time Maths

A TimeInstance supports all basic mathematic operators

Addition / Subtraction
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 )
Multiplication / Division
local a = Time.Minutes( 5 ) * 2
assert( a.As.Minutes == 10 )

local a = Time.Hours( 5 ) / Time.Minutes( 60 )
assert( a == 5 )

You can also compare a TimeInstance against another

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 )


TimeRange

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!

TimeRange inclusion

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 )


Advanced Tools

Time.Basis

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.