I’ve learned to always use workspace:GetServerTimeNow() to get consistent times across server and client. So daily rewards, etc, anything that needs to store a time that something occurred on the server and client needs to display.
I’ve since started doing os.time()/os.clock() on server and workspace:GetServerTimeNow() on client, as they return more or less the same number (os.time() just floored and the decimals may not be millisecond accurate across both, but I’m not after millisecond accuracy when we talking hour/day long timers)
Is there a better method though? As I know workspace:GetServerTimeNow() is expensive. I just want a clean/effective way to get time. if the server says this thing happened at 1756093129, then I want a way for client to be able to do
clientTime - serverTime = timeElapsed
and with os.time() varying across client/server, timeElapsed could end up being hours off
Note to add, wish Roblox would make os.time() return differing results when testing in studio don’t know how many times I’ve used and everything’s been great in studio, only to go play in live game and all of a sudden client and server have vastly different numbers
You really should be calculating delta time from (os.time() - startTime). I can’t really think of a reason why these would be hours off unless you were slowly adding the delta time.
After testing it looks like the client and server are around a half second delayed from each other for some reason but that shouldn’t be an issue.
Player claims the reward, we save the time they claimed it on the server using
-- set and saved on the server to players data
Data.ClaimedReward = os.time()
Now on the client we want to get how long before they can claim their next days rewards
local DAY = 24 * 60 * 60
local TimeLeft = (Data.ClaimedReward + DAY) - workspace:GetServerTimeNow()
This works. If I did
local TimeLeft = (Data.ClaimedReward + DAY) - os.time()
on the client, it could be any sort of time, as os.time() is different on the client and can be manipulated by just setting your systems time
this is server based, my concern is tracking across any server, players leaving/rejoining later/etc. needing to display correct time as mentioned in example above ^ and is also a different number across client/server
Why don’t you just use a RemoteFunction, have the client ask the server what workspace:GetServerTimeNow() is. If they’re desperate enough to make it appear to be claimed, who cares, they’re not actually claiming it.
It really doesn’t matter if the client time is manipulated because if this is for visual properties (which it should be), an exploiter won’t care about this anyways. os.time() is not different on the client unless hacked by the way.
Calling workspace:GetServerTimeNow() every frame shouldn’t be too expensive, it should only take a tiny fraction of a microsecond for the call. Just cache the result for one frame for the computations you need it for if performance is a concern.
I do a lot of work with networking. No, I don’t think there is a better way of doing it, and it’s not that expensive either. 99% of the time Roblox’s methods will be way more optimized than what LuaU can achieve.
Additionally, if you’re looking to work out the latency, then you can send the server time in the remote call, such as
Not sure if this is the kind of stuff you’re looking for but this is the logic I use for my daily offer code. Both the client and server use this module to determine which item is included in the daily offer.
Module
local DailyOffer = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemData = require(ReplicatedStorage.SharedModules.itemData)
local getDictSize = require(ReplicatedStorage.SharedModules.getDictSize)
local currentDaysFromEpoch
local epoch = 1715634000 -- 13 May 2024 3pm MST
function DailyOffer.getDaysFromEpoch()
return (os.time() - epoch)/60/60/24
end
local defaults = {"Defualt", "Cheer", "Impact"}
local function getRares(itemType)
local data = {}
for itemName, itemInfo in pairs(itemType) do
if itemInfo.Rarity == "Rare" and not table.find(defaults, itemName) then
data[itemName] = itemInfo
end
end
return data
end
local function getOfferedItem()
local days = math.floor(DailyOffer.getDaysFromEpoch()) print(days)
local types = {"BallTrails", "Celebrations", "TackleEffects"}
local selectedType = types[days % 3 + 1] -- +1 bc arrays start at 1 instead of 0
local count = 0
local items = getRares(itemData[selectedType])
local index = days % getDictSize(items)
for itemName in pairs(items) do
if count == index then
return selectedType, itemName
end
count += 1
end
end
local function updateOffer()
currentDaysFromEpoch = math.floor(DailyOffer.getDaysFromEpoch())
DailyOffer.itemType, DailyOffer.itemName = getOfferedItem()
local offerValueObj = ReplicatedStorage.ReplicatedValues.DailyOffer
offerValueObj.Value = DailyOffer.itemName
offerValueObj.Type.Value = DailyOffer.itemType
end
local function checkForNewDay()
local newDaysFromEpoch = math.floor(DailyOffer.getDaysFromEpoch())
if newDaysFromEpoch ~= currentDaysFromEpoch then
updateOffer()
ReplicatedStorage.Networking.DailyOfferChanged:FireAllClients()
end
task.delay(1, checkForNewDay)
end
function DailyOffer.init() -- Called once by the server.
updateOffer()
checkForNewDay()
end
return DailyOffer