I created a wrapper class for os.date()
that gives it similar functionality to the JavaScript Date
class. I also implemented most of the GNU date formatting patterns. I would love if people could give feedback and let me know if they spot anything wrong.
Examples:
-- Current time:
local now = Date.new()
print("Hour: " .. now.Hour)
-- Specific time:
local aMinuteAgo = Date.new(tick() - 60)
print(aMinuteAgo)
-- Save:
local now = Date.new()
dataStore:SetAsync("date", now:ToSeconds())
-- Load:
local nowSec = dataStore:GetAsync("date")
local now = Date.new(nowSec)
-- JSON to/from:
local now = Date.new()
local json = now:ToJSON()
local now2 = Date.fromJSON(now)
-- Format:
local now = Date.new()
print(now:Format("The time is %I:%M %p"))
print(now:Format("The date is %B %d, %Y"))
Basic documentation:
Constructors
date = Date.new([seconds [, useUtc]])
date = Date.fromJSON(jsonString)
Important note: The useUtc
parameter defaults to true
on the server and false
on the client.
Methods
date:ToJSON()
date:ToSeconds()
date:GetTimezoneHourOffset()
date:Format(strFormat)
date:ToUTC()
date:ToLocal()
date:ToISOString()
date:ToDateString()
date:ToTimeString()
date:ToString()
Properties
date.Hour
date.Minute
date.Weekday
date.Day
date.Month
date.Year
date.Second
date.Millisecond
date.Yearday
date.IsDST
Code & model:
-- Date
-- Crazyman32
-- September 12, 2017
--[=[
Represents a date at a specific time. On the server, this will
return UTC time. On the client, this will return local time.
Note that the server-side in Play-Solo testing will also return
local time.
You can optionally force UTC within the Date.new constructor.
REQUIRE:
local Date = require(thisModule)
CONSTRUCTORS:
local date = Date.new([seconds [, useUtc]])
local date = Date.fromJSON(jsonString)
METHODS:
date:ToJSON()
date:ToSeconds()
date:GetTimezoneHourOffset()
date:Format(strFormat)
date:ToUTC()
date:ToLocal()
date:ToISOString()
date:ToDateString()
date:ToTimeString()
date:ToString()
PROPERTIES:
date.Hour
date.Minute
date.Weekday
date.Day
date.Month
date.Year
date.Second
date.Millisecond
date.Yearday
date.IsDST
NOTE ON SAVING:
You should use 'date:ToSeconds()' for saving. It can
represent the date in the smallest format. While using
'date:ToJSON()' will work too, it has a higher data
footprint. Example:
SAVE:
local date = Date.new()
dataStore:SetAsync("myDate", date:ToSeconds())
LOAD:
local myDateSeconds = dataStore:GetAsync("myDate")
local date = Date.new(myDateSeconds)
--]=]
local Date = {}
Date.__index = Date
local useUTC = game:GetService("RunService"):IsServer()
local WEEKDAYS = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
local WEEKDAYS_SHORT = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
local MONTHS = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
local MONTHS_SHORT = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
-- Single-level table copy:
function CopyTable(t)
local tCopy = {}
for k,v in pairs(t) do
tCopy[k] = v
end
return tCopy
end
function Date.new(seconds, useUtcOverride)
if (seconds ~= nil) then
assert(type(seconds) == "number", "'seconds' argument #1 must be a number")
else
seconds = tick()
end
local utc = useUTC
if (useUtcOverride ~= nil) then
utc = useUtcOverride
end
local d = os.date(utc and "!*t" or "*t", seconds)
local self = setmetatable({
Hour = d.hour;
Minute = d.min;
Weekday = d.wday;
Day = d.day;
Month = d.month;
Year = d.year;
Second = d.sec;
Millisecond = math.floor((seconds % 1) * 1000);
Yearday = d.yday;
IsDST = d.isdst;
_d = d;
_s = seconds;
}, Date)
return self
end
function Date.fromJSON(jsonStr)
assert(type(jsonStr) == "string", "'jsonStr' argument #1 must be a string")
local success, data = pcall(function()
return game:GetService("HttpService"):JSONDecode(jsonStr)
end)
if (not success) then
error("Failed to decode JSON string: " .. tostring(data))
end
local seconds
if (data._s) then
seconds = data._s
else
seconds = os.time(data)
end
return Date.new(seconds)
end
function Date:ToJSON()
local data = CopyTable(self._d)
data._s = self._s
end
function Date:ToSeconds()
return self._s
end
function Date:GetTimezoneHourOffset()
local dUTC = os.date("!*t", self._s)
return (self._d.hour - dUTC.hour)
end
function Date:ToISOString()
local utc = self:ToUTC()
local d = utc._d
return ("%.2i-%.2i-%.2iT%.2i:%.2i:%.2i.%.3i"):format(
d.year,
d.month,
d.day,
d.hour,
d.min,
d.sec,
math.floor((utc._s % 1) * 1000)
)
end
function Date:ToDateString()
local d = self._d
return ("%s %s %i %i"):format(
WEEKDAYS_SHORT[d.wday],
MONTHS_SHORT[d.month],
d.day,
d.year
)
end
function Date:ToTimeString()
local d = self._d
return ("%.2i:%.2i:%.2i"):format(
d.hour,
d.min,
d.sec
)
end
function Date:ToString()
return (self:ToDateString() .. " " .. self:ToTimeString())
end
function Date:ToUTC()
return Date.new(self._s, true)
end
function Date:ToLocal()
return Date.new(self._s, false)
end
-- See GNU date commands:
-- https://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
function Date:Format(str)
local d = self._d
local h12 = d.hour
if (h12 > 12) then
h12 = h12 - 12
end
if (h12 == 0) then
h12 = 0
end
str = str
:gsub("%%a", WEEKDAYS_SHORT[d.wday])
:gsub("%%A", WEEKDAYS[d.wday])
:gsub("%%b", MONTHS_SHORT[d.month])
:gsub("%%B", MONTHS[d.month])
:gsub("%%c", self:ToString())
:gsub("%%C", ((d.year - (d.year % 1000)) / 100) + 1)
:gsub("%%d", ("%.2i"):format(d.day))
:gsub("%%D", ("%.2i/%.2i/%s"):format(d.month, d.day, tostring(d.year):sub(-2)))
:gsub("%%F", ("%i-%.2i-%.2i"):format(d.year, d.month, d.day))
:gsub("%%H", ("%.2i"):format(d.hour))
:gsub("%%k", ("%.2i"):format(d.hour))
:gsub("%%I", ("%.2i"):format(h12))
:gsub("%%l", ("%.2i"):format(h12))
:gsub("%%j", ("%.3i"):format(d.yday))
:gsub("%%m", ("%.2i"):format(d.month))
:gsub("%%M", ("%.2i"):format(d.min))
:gsub("%%n", "\n")
:gsub("%%p", (d.hour >= 12 and "PM" or "AM"))
:gsub("%%P", (d.hour >= 12 and "pm" or "am"))
:gsub("%%r", ("%.2i:%.2i:%.2i %s"):format(h12, d.min, d.sec, (d.hour >= 12 and "PM" or "AM")))
:gsub("%%R", ("%.2i:%.2i"):format(d.hour, d.min))
:gsub("%%s", math.floor(self._s))
:gsub("%%S", ("%.2i"):format(d.sec))
:gsub("%%t", "\t")
:gsub("%%T", ("%.2i:%.2i:%.2i"):format(d.hour, d.min, d.sec))
:gsub("%%w", ("%.2i"):format(d.wday))
:gsub("%%y", tostring(d.year):sub(-2))
:gsub("%%Y", tostring(d.year))
return str
end
Date.New = Date.new
Date.FromJSON = Date.fromJSON
Date.__tostring = Date.ToString
Date.__metatable = "locked"
function Date.__lt(d1, d2)
return (d1._s < d2._s)
end
function Date.__le(d1, d2)
return (d1._s <= d2._s)
end
function Date.__eq(d1, d2)
return (d1._s == d2._s)
end
function Date.__unm(d)
return Date.new(-d._s)
end
return Date