RblxTimer — Timer Library by Mubinet

RblxTimer, Timer Library


Introducing RblxTimer

:grey_question: What is RblxTimer?

  • RblxTimer is a simple, small library, with full support for type-checking (Luau) designed by @Mubinets. RblxTimer makes creating a timer EASY! Use it for your simulator, round-system or even lobby! Anything that needs time!

‎ ‎


‎❔ Why should I use RblxTimer?

  • Full Support For Typechecking! :white_check_mark:
  • Designed with OOP in mind! :brain:
  • Easy To Use! :+1:
  • Listen for Events for Certain Actions (OnStarted, OnStopped, etc) :zap:
  • Your bugs are my bugs! It will be maintained by me, for long time!

‎ ‎


:grey_question:How To Use RblxTimer!

  • Get the source code directly!
  • Create new ‎ ModuleScript ‎ script in ReplicatedStorage or place of your choice
  • Paste the source code in the ModuleScript script.
  • Require the module and use!

‎ ‎


:grey_question: How To Actually Use RblxTimer?

  • Once you have required the module. You can start by creating a new timer. Here is the example:
local RblxTimer = require(...)

local timer : RblxTimer.Timer = RblxTimer.createTimer(60, 5) -- 60 Seconds with 5 Seconds interval
timer:start() -- Immediately starts the Timer object!

-- This function will run immediately for FIRST TIME and then ONCE every 5 seconds! (Interval)
timer.onElapsed:Connect(function(timerState : RblxTimer.TimerState)
  ‎ print("The clock is ticking...")
end

timer.onFinished:Connect(function(timerState : RblxTimer.TimerState)
 ‎ print("Round is starting!")

 ‎ . . . -- The rest code.
end

Source Code
--	Author  :  Mubinet (@mubinets | 5220307661)
--	Date    :  9/14/2024
--	Version :  2.0.0a

--!strict

------------ [[ ROBLOX SERVICES ]] ------------
local RunService = game:GetService("RunService")

------------ [[ USER MODULE ]] ------------
local RblxTimer = {} :: RblxTimer

------------ [[ USER TYPES ]] ------------
export type TimerStatus = "Pending" | "Running" | "Complete" | "Stopped" | "Paused"

--[[
    An object that provides information about the result of the Timer object.
]]
export type TimerState = {
    --[[
        The value that is provided if the Timer object is either paused or stopped in time.
    ]]
	remainingTime   :  number,

    --[[
        The status value describing the current state of the Timer object.
    ]]
	timerStatus : TimerStatus
}

--[[
    An object representing the date time.
]]
export type RblxDateTime = {
    --[[
        The value representing the year.
    ]]
    year             :     number,
    
    --[[
        The value representing the amount of months in number
    ]]
    month            :     number,

    --[[
        The value representing the amount of days in number
    ]]
    day              :     number,

    --[[
        The value representing the amount of hours of the day in number
    ]]
    hour             :     number,

    --[[
        The value representing the amount of minutes in number
    ]]
    minute           :     number,

    --[[
        The value representing the amount of seconds in number
    ]]
    second           :     number,

    --[[
        The value representing the amount of milliseconds in number
    ]]
    millisecond      :     number,

    --[[
        Generates a string from the DateTime value interpreted as Universal Coordinated Time (UTC) and a format string.
    ]]
    formatDateTime   : (any, string, string) -> string
}

--[[
    An object representing the time.
]] 
export type Timer = {
    --[[
        The inital value in seconds in the construction of the Timer object.
    ]]
	initalTime             : number?,

    --[[
        The event which fires on the completion of the timer, an TimerState object which provides information about the result will be given.
    ]]
	onFinished             : RBXScriptSignal<TimerState>,

    --[[
        The event which fires every interval of specified time. Defaults to 1 second internal if not specified
    ]]
	onElapsed              : RBXScriptSignal<TimerState>,

    --[[
        The event which fires when the Timer object has been stopped
    ]]
    onStopped              : RBXScriptSignal<TimerState>,

    
    --[[
        The event which fires when the Timer object has been paused
    ]]
    onPaused              : RBXScriptSignal<TimerState>,

    --[[
        The event which fires when the Timer object has been resumed
    ]]
    onResumed              : RBXScriptSignal<TimerState>,

    --[[
        The event which fires when the Timer object has started
    ]]
    onStarted              : RBXScriptSignal<TimerState>,

    --[[
        Returns the remaining time of the Timer object.
    ]]
	getRemainingTime       : (any) -> DateTime,

    --[[
        Stops the Timer object if not stopped already.
    ]]
	stop                   : (any) -> TimerState,

    --[[
        Resumes the Timer object if it has been paused. 
    ]]
	resume                 : (any) -> TimerState,

    --[[
        Pauses the Timer object if not paused already.
    ]]
	pause                  : (any) -> TimerState,

    --[[
        Starts the Timer object if not stopped already.
    ]]
	start                  : (any) -> TimerState,

    --[[
        Set the duration of the timer.
    ]]
    setDuration            : (any, number) -> (),

    --[[
        Clean up the object.
    ]]
    destroy         : (any) -> (),

    --[[
        Return a boolean, indicating whether the timer is currently running or not.
    ]]
    isRunning       : boolean
}

--[[
    An object that provides information about the current clock time from the specific clock object.
]]
export type ClockRecord = {
    --[[
        A value which has the current time in seconds since the Unix epoch (1 January 1970, 00:00:00) under current UTC time.
    ]]
    clockTime       :   number,

    --[[
        A value which has the current date in UTC time.
    ]]
    clockDate       :   RblxDateTime
}

--[[
    An object representing the clock.
]]
export type Clock = {
    --[[
        The event which fires every interval of specified time. Defaults to 1 second internal if not specified
    ]]
	onElapsed              : RBXScriptSignal<ClockRecord>,

    --[[
        Returns the current time in seconds since the Unix epoch (1 January 1970, 00:00:00) under current UTC time.
    ]]
    getClockTime           : () -> number,

    --[[
        Returns the current time formatted in date in UTC time.
    ]]
    getClockDate           : () -> RblxDateTime,

    --[[
        Clean up the object.
    ]]
    destroy         : (any) -> ()
}

--[[
    A user service for working with a timer object. 
]] 
export type RblxTimer = {
	createTimer  :   (number, number?)  ->  Timer,
    createClock  :   (number)           ->  Clock,
}

------------ [[ MAIN ]] ------------
local function createRblxDateTime(seconds : number) : RblxDateTime
    local rblxDateTime          = {}                                    :: RblxDateTime
    local dateTimeFromSeconds   = DateTime.fromUnixTimestamp(seconds)   :: DateTime
    local dateTime              = dateTimeFromSeconds:ToUniversalTime() :: any

    rblxDateTime.year   = dateTime.Year
    rblxDateTime.month  = dateTime.Month
    rblxDateTime.day    = dateTime.Day
    rblxDateTime.hour   = dateTime.Hour
    rblxDateTime.minute = dateTime.Minute
    rblxDateTime.second = dateTime.Second
    rblxDateTime.millisecond = dateTime.Millisecond

    rblxDateTime.formatDateTime = function(self : any, format : string, locale : string) : string
        return dateTimeFromSeconds:FormatUniversalTime(format, locale)
    end

    return rblxDateTime
end

--[[
    Creates a new object Timer with specified amount of time and optional interval in seconds.
]]
function RblxTimer.createTimer(seconds : number?, interval: number?) : Timer

	-- Definitions
	local newTimerObject             = {}                            :: Timer
	local newElaspedBindableEvent    = Instance.new("BindableEvent") :: BindableEvent
	local newFinishedBindableEvent   = Instance.new("BindableEvent") :: BindableEvent
    local newStoppedBindableEvent    = Instance.new("BindableEvent") :: BindableEvent
    local newResumedBindableEvent    = Instance.new("BindableEvent") :: BindableEvent
    local newPausedBindableEvent     = Instance.new("BindableEvent") :: BindableEvent
    local newStartedBindableEvent    = Instance.new("BindableEvent") :: BindableEvent

	newTimerObject.onElapsed    = newElaspedBindableEvent.Event
	newTimerObject.onFinished   = newFinishedBindableEvent.Event
    newTimerObject.onStarted    = newStartedBindableEvent.Event
    newTimerObject.onPaused     = newPausedBindableEvent.Event
    newTimerObject.onStopped    = newStoppedBindableEvent.Event
    newTimerObject.onResumed    = newResumedBindableEvent.Event

	-- Object Private Constants
	local DEFAULT_INTERVAL = 1 :: number

	-- Object Private Properties
	local _initalTime        = seconds                            :: number
    local _updatedTime       = seconds                            :: number
	local _currentTime       = seconds                            :: number
	local _timeElapsed       = 0                                  :: number
	local _interval          = interval or DEFAULT_INTERVAL       :: number
	local _currentTimerState = {}                                 :: TimerState
	local _isTicking         = false                              :: boolean
    local _stopTicking       = false                              :: boolean

	-- Object Private Methods
    --[[
        A main private function for the regular operation of the timer.
    ]]
	local function _tick()
        task.spawn(function()
            local heartbeatConnection : RBXScriptConnection do
                heartbeatConnection  = RunService.Heartbeat:Connect(function(deltatime : number)                    
                    if not (_isTicking) then
                        if (newTimerObject.isRunning) then newTimerObject.isRunning = false end
                        return
                    end
                    
                    -- Stop the timer when the ticking has been disabled.
                    if (_stopTicking) then
                        newTimerObject.isRunning = false
                        heartbeatConnection:Disconnect()
                        
                        return
                    end

                    _currentTime -= deltatime
                    _timeElapsed += deltatime
                    
                    if (_currentTime <= 0) then
                        _currentTimerState.remainingTime = 0
                        _currentTimerState.timerStatus   = "Complete"
                        _isTicking                       = false
                        newTimerObject.isRunning         = false
                        
                        newElaspedBindableEvent:Fire(_currentTimerState)
                        newFinishedBindableEvent:Fire(_currentTimerState)
                        heartbeatConnection:Disconnect()
                    end
        
                    if (_timeElapsed >= _interval) then
                        _currentTimerState.remainingTime = math.round(_currentTime)
                        newElaspedBindableEvent:Fire(_currentTimerState)
    
                        _timeElapsed = 0
                    end
                end)
            end
        end)
	end

    --[[
        A private function which toggles either enabling the ticking of the timer or disabling the ticking of the timer.
    ]]
	local function _toggleTick(tickingToggle : boolean)
		if (_isTicking ~= tickingToggle) then
            _isTicking = tickingToggle

            if (_isTicking) and not (_currentTime) then error("[RBLX EXCEPTION]: Attempted to start the timer without any set duration.") end
            if (tickingToggle == true) then
                newTimerObject.isRunning = true
                _tick()
            else
                newTimerObject.isRunning = false
            end
		end
	end

	-- Object Private Properties Initalization
	_currentTimerState.remainingTime = _currentTime
	_currentTimerState.timerStatus   = "Pending"

	-- Object Initalization
	newTimerObject.initalTime = seconds
    newTimerObject.isRunning  = false

	-- Methods Definition
	newTimerObject.getRemainingTime = function() : DateTime
		return DateTime.fromUnixTimestamp(_currentTime)
	end

	newTimerObject.start = function() : TimerState
		if (_currentTimerState.timerStatus == "Pending") or (_currentTimerState.timerStatus == "Complete") or (_currentTimerState.timerStatus == "Stopped") then
            _currentTime = _initalTime or _updatedTime
			_currentTimerState.remainingTime = _initalTime or _updatedTime
			_currentTimerState.timerStatus = "Running"

            _toggleTick(true)

            newStartedBindableEvent:Fire(_currentTimerState)
            newElaspedBindableEvent:Fire(_currentTimerState)

			return _currentTimerState
		end

		return _currentTimerState
	end

	newTimerObject.resume = function() : TimerState
		if (_currentTimerState.timerStatus == "Paused") then
			_toggleTick(true)

			_currentTimerState.timerStatus = "Running"
            newResumedBindableEvent:Fire(_currentTimerState)

			return _currentTimerState
		end

		return _currentTimerState
	end

	newTimerObject.pause = function() : TimerState
		if (_currentTimerState.timerStatus == "Running") then
			_toggleTick(false)

			_currentTimerState.timerStatus = "Paused"
            newPausedBindableEvent:Fire(_currentTimerState)

			return _currentTimerState
		end

		return _currentTimerState
	end

	newTimerObject.stop = function() : TimerState
		if (_currentTimerState.timerStatus == "Running") or (_currentTimerState.timerStatus == "Paused") then
			_toggleTick(false)

			_currentTimerState.timerStatus = "Stopped"
            newStoppedBindableEvent:Fire(_currentTimerState)

			return _currentTimerState
		end

		return _currentTimerState
	end

    newTimerObject.setDuration = function(self, newDuration : number) : ()
        _updatedTime = newDuration
    end

    newTimerObject.destroy = function() : ()
        newElaspedBindableEvent:Destroy()
        newElaspedBindableEvent:Destroy()
	    newFinishedBindableEvent:Destroy()
        newStoppedBindableEvent:Destroy()
        newResumedBindableEvent:Destroy()
        newPausedBindableEvent:Destroy()
        newStartedBindableEvent:Destroy()

        _stopTicking = true
    end

	return newTimerObject
end

--[[
    Creates a new object Clock with specfified amount of interval in seconds.
]]
function RblxTimer.createClock(interval : number) : Clock
    -- Definitions
	local newClockObject             = {}                            :: Clock
	local newElaspedBindableEvent    = Instance.new("BindableEvent") :: BindableEvent

	newClockObject.onElapsed         = newElaspedBindableEvent.Event

    -- Object Private Constants
	local DEFAULT_INTERVAL = 1 :: number

    -- Object Private Properties
    local _clockRunTime      = os.time()                          :: number
	local _timeElapsed       = 0                                  :: number
	local _interval          = interval or DEFAULT_INTERVAL       :: number
    local _stopTicking       = false                              :: boolean

    local function clockTick()
        task.spawn(function()
            local heartbeatConnection : RBXScriptConnection do
                    heartbeatConnection = RunService.Heartbeat:Connect(function(deltaTime : number)
                    if (_stopTicking) then
                        heartbeatConnection:Disconnect()
                        heartbeatConnection = nil :: any

                        return
                    end

                    _clockRunTime = os.time()
                    _timeElapsed += deltaTime

                    if (_timeElapsed >= _interval) then
                        local clockRecord = {} :: ClockRecord
                        clockRecord.clockTime = _clockRunTime
                        clockRecord.clockDate = createRblxDateTime(_clockRunTime)

                        newElaspedBindableEvent:Fire(clockRecord)
                        _timeElapsed = 0
                    end
                end)
            end
        end)
    end

    newClockObject.getClockTime = function()           : number
        return _clockRunTime
    end

    newClockObject.getClockDate = function()           : RblxDateTime
        return createRblxDateTime(_clockRunTime)
    end

    newClockObject.destroy = function()                : ()
        _stopTicking = true

        newElaspedBindableEvent:Destroy()
    end

    clockTick()

    return newClockObject
end

return RblxTimer
📜 Changelogs ( Latest: v.2.0.0 )

RblxTimer v2.0.0 Changelog


  • New method for RblxTimer: SetDuration
    – As requested by someone else, the feature is here! Constructing a new RblxTimer with required parameters is no longer a thing! They are all now optional!
local RblxTimer = require(...)

-- Create a new timer with inital time (OPTIONAL), interval (OPTIONAL)
local localTimer = RblxTimer.createTimer()

-- Connections
localTimer.onElapsed:Connect(function(timerState)
	--// ...	
end)

-- Setting the duration. (REQUIRED when starting a timer)
localTimer:setDuration(10)

-- Starting the timer.
localTimer:start() -- WARNING: This will throw an error if you attempt to call it without a set duration.

localTimer:setDuration(20) -- NOTE: It will only take effort the NEXT time you START the timer again. You can stop and restart the timer for the change to apply.
  • New property for RblxTimer: IsRunning
    – This property returns a boolean value indicating whether the RblxTimer is currently running or not.

  • The RblxTimer now ends at 0 rather than 1.
    – You can now destroy the objects RblxTimer and Clock. This is made as requested by some people.

  • Other fixes + Breaking change``
    – Some properties were renamed to fix some typo issues. If your codebase uses one of them, you should change it before upgrading to the new version.

  • HUGE Update soon!!
    – Even though this is a small update, there is so much that I have done in the next version that will need to be tested until it’s stable!



If you have any questions or issues, please let me know! I will be HAPPY to fix any issue. :sparkling_heart:

/Mub

22 Likes

Here is the use case of RblxTimer! (Creating clock timer using it!)

3 Likes

splendid, i love this module, i started doing my minigames with it

2 Likes

Awesome! :sparkling_heart:

If you found any issues or if you have any question, you can always ask me! If you think that the module has missing features, you can always suggest!

2 Likes

RblxTimer v1.0.1 Release 5/12/2024

Please refer to the updated original post to view the latest changelog. Happy coding! :+1:

/Mub

3 Likes

What about destroying the timer object?

1 Like

Still using this awesome resource, i came to a suggestion, can you add a function to format time?, in minutes, miliseconds and even show them with custom settings? (1.05, 1.005, 12.52), etc

1 Like

RblxTimer v1.1.0 Release 6/2/2024

Please refer to the updated original post to view the latest changelog. Happy coding! :+1:
If you have any question or feedback, please let me know so! :heart:

/Mub

1 Like

You should implement an IsRunning function to check if the timer is running or not. On the other hand, do you think it’s possible to utilize this module to make a day/night cycle?

1 Like

Noted! This is planned for the next update! :+1:

As for day/night, I personally think this is possible. You can create two timers, one for day time and other for night.

Let the time length be 5 minutes. When the dayTimer ends, you set the time to be night, then immediately start the nightTimer.

Want the time to smooth transition slow instead of setting it instantly? Use the remaining time as the percentage of the specified time. So if it’s 3 minutes left, the percentage would be 60%, so assuming that it’s dayTimer, set the time to be 60% night, meaning that it’d be smooth.

Hope that helps!

Hi, @Mubinets

Would it be possible for you to update the module by implementing a :SetDuration() function to the timer after constructing (.new)? This way it won’t be necessary to create new timer every time you want to change the duration of the timer. And if you do decide to do this, can you make it so that the connected connections adapt with the newly changed duration?

local timer = rblxTimer.new() -- can set duration when constructing, or leave it empty and it will be empty.

timer.onFinished:Connect(function()
        -- this will run even though the duration has changed. After the new duration has finished counting, this previous connection which is THIS; will fire until it's disconnected.
end)

timer:start() -- will error or give a warning, why? because there is no duration set for the timer.

timer:setDuration(5) -- okay now it's set to 5 seconds, now you can start it.
timer:start() -- NOW it will actually work and start counting.

I believe this will be a QoL change and everyone using this will be thrilled. I want to thank you for making “timing” easier, God bless you for making this resource for free for everyone to use on the platform. It saves us so much time.

1 Like

Hey @Obsentiel

Thank you so much for your kind words of appreciation :heart:
Seeing these comments made me happy and making RblxTimer free for everyone worth it for me!

You might see less activity here, but behind the scene, I was actively working on a big update for a while now. I can see how adding setDuration can be helpful for you, hence, I will consider that👍

Stay tuned and don’t forget to share the module to the Roblox world!

1 Like

Hello! I’m looking at this module and it looks great!

Is it possible to make it so the timer can also be formatted based on seconds (e.g. if a timer is 3 minutes long you can specify whether it shows up at 3:00 minutes or 180 sceonds)?

1 Like

Hey!

Thanks for using RblxTimer!

As for your question, RblxTimer, the module, currently does not offer a custom time formatting. The video I provided above a while ago, was done using combination of RblxTimer with manual time formatting.

The remaining time from RblxTimer always return in seconds.
With this, you can use it to convert it into minutes (seconds x 60), etc.

Currently, I am deciding whether I should add time formatting or not, but given that the module is created for a specific purpose/goal and it has successfully achieved that, we’ll decide later! :+1:

2 Likes

I’ve got a question. Does onElapsed always end at 1 instead of 0? I’m working on an elevator system and it has a bar. It doesn’t really align properly when it only stops at 1 and so I had to make a fork. Is there another way, or was this intentional? Oh, and by the way if you ever decide to update this awesome module please rename the function names as some of them are misspelled. For example destroy is destory.

1 Like

This is actually accidental bug that I have forgotten to fix at the time of release :sob:

I am working on multiple bug fixes including these bugs, as well as the update that I am also working on! Might take a while but hopefully it will be worth it! :pray:

I have multiple projects on-going that limited me from working on the module!

1 Like

Hello, I am coming back to this topic after 30 days. Still waiting for an update because I heavily rely on using this library over other timer libraries. Just asking how things are going on your end and if you have plans on updating this module any time soon.

RblxTimer v2.0.0 Release 9/14/2024

Please refer to the updated original post to view the latest changelog. Happy coding! :+1:
(I am working on multiple things at once, which is the main reason why this took so long! Hope you enjoy it)

/Mub