UPDATE: Added a syncing system between the client and the server, this means UTC time is practically identical between the client and the server. It can thus be used for precise syncing of actions between the client and server, without ping getting in the way
This update also adds a new GetPing method, calculated as client → server → client
So, how do you get UTC time in roblox? Use the DateTime object, but ummm… There are no DateTime.UTC or other constructor for that, the only useful methods to get UTC time are ToUniversalTime and FormatUniversalTime, the first one returns are table containing the year, month, day, second, … all separated, the second returns a string…
(Turns out DateTime.now() (and other things) returns the time in UTC, and I got that wrong, look at the reply below. That doesn’t make this module unless in any way though, you can use it to easily get UTC or LocalTime, and now, as an added bonus, you can avoid the stupidity of os.date() taking UTC time and returning a date in LocalTime)
All I want a simple function that returns the amount of time since the epox, with precision, and that is the same for everyone. Is that too much to ask???
Anyway, made a module for that
Source code
With the addition of the time syncing system, the module now uses a Unreliable Remote Event, and some require scripts (the module must be required on the server for the syncing to work, and on the client it can be nice to have it start syncing before other scripts use it, though requiring it from ReplicatedFirst would be best for that)
local RunService = game:GetService("RunService")
local SyncRemote = script.Sync
-- // Default Offsets // --
local UTC = DateTime.now():ToUniversalTime()
UTC = DateTime.fromUniversalTime(UTC.Year,UTC.Month,UTC.Day,UTC.Hour,UTC.Minute,UTC.Second,UTC.Millisecond).UnixTimestampMillis/1000
local UTCOffset = UTC - os.clock()
local localTime = DateTime.now():ToLocalTime()
localTime = DateTime.fromUniversalTime(localTime.Year,localTime.Month,localTime.Day,localTime.Hour,localTime.Minute,localTime.Second,localTime.Millisecond).UnixTimestampMillis/1000 -- Using fromUniversalTime because that function doesn't mess with the time
local localOffset = localTime - os.clock()
local UTC_Local_Offset = UTCOffset - localOffset
local Ping = 0 -- This will stay 0 on the server
-- // Module // --
local TickModule = {}
function TickModule:GetPing()
if RunService:IsServer() then error("Cannot get the ping from the server") end
return Ping
end
function TickModule:Tick()
return os.clock() + UTCOffset
end
function TickModule:UTC()
return os.clock() + UTCOffset
end
function TickModule:LocalTime()
return os.clock() + localOffset,0
end
function TickModule:ToUTC(t : number)
return t + UTC_Local_Offset
end
function TickModule:ToLocalTime(t : number)
return t - UTC_Local_Offset
end
-- // This function is basically os.date() but it doesn't convert UTC time to LocalTime
-- // You give it UTC time, it will format a UTC date, give it a LocalTime, and you'll get a Local date
function TickModule:FormatDate(FormatString : string, Time : number?)
Time = math.max(Time + UTC_Local_Offset or self:UTC(),0)
return os.date(FormatString, Time)
end
-- // Sync // --
if RunService:IsServer() then
SyncRemote.OnServerEvent:Connect(function(Player)
SyncRemote:FireClient(Player,TickModule:UTC())
end)
end
if RunService:IsClient() then
local UTCOffsets = {}
local Pings = {}
local TIMEOUT = 30
local function PingServer()
local StartTime = os.clock()
local DiffTime
local ServerUTC
local Coroutine = coroutine.running()
local Connection = SyncRemote.OnClientEvent:Once(function(_UTC)
ServerUTC = _UTC
DiffTime = os.clock() - StartTime
coroutine.resume(Coroutine)
end)
task.delay(TIMEOUT,function()
if ServerUTC then return end
Connection:Disconnect()
coroutine.resume(Coroutine)
end)
SyncRemote:FireServer()
coroutine.yield()
if DiffTime and DiffTime > TIMEOUT then return end -- Possible that it is an unrelated remote
return ServerUTC, DiffTime
end
task.defer(function()
local WaitTime = 0
while true do
task.wait(WaitTime)
WaitTime = math.min(WaitTime + .5,10)
-- // Get time from server, plus the ping, and calculate stuff with it
local ServerUTC, DiffTime = PingServer()
if not ServerUTC then continue end
ServerUTC += DiffTime/2 -- Taking ping into account
table.insert(UTCOffsets,1,ServerUTC - os.clock())
UTCOffsets[11] = nil
table.insert(Pings,1,DiffTime)
Pings[11] = nil
-- // Calculate the écart type
local c_UTCOffsets = table.clone(UTCOffsets)
local c_Pings = table.clone(Pings)
table.sort(c_UTCOffsets)
table.sort(c_Pings)
UTCOffset = c_UTCOffsets[math.max(#c_UTCOffsets//2,1)]
Ping = Pings[math.max(#Pings//2,1)]
end
end)
end
return TickModule
TickModule.rbxm (3.3 KB)
https://create.roblox.com/store/asset/18879903572
So you have some functions to get the time, Tick and UTC are the exact same (but depending on the use case one or the other might be more clear, I guess. Just delete one if you don’t want it lol)
There are also the ToUTC and ToLocalTime methods, which basically return a LocalTime/UTC seconds since epox number into UTC/LocalTime seconds since epox
It uses os.clock() under the hood as a baseline time. Well you can read the script, it’s not that complicated
This should provides a time that is identical between clients and servers (if the time on the clients/server’s computer isn’t out of sync…), so it can be used to safely store times in datastores. Hoping DateTime.now():ToUniversalTime() isn’t affected by daylights saving or some other stupid thing… If anything is flawed in my code, please tell me
I hope this simple and short “module” is useful