add gs
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
local Modules = game:GetService("CorePackages").AppTempCommon
|
||||
local Common = Modules.Common
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(convo)
|
||||
return {
|
||||
conversation = convo,
|
||||
}
|
||||
end)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
local Modules = game:GetService("CorePackages").AppTempCommon
|
||||
|
||||
local Common = Modules.Common
|
||||
local Action = require(Common.Action)
|
||||
|
||||
return Action(script.Name, function(placeInfos)
|
||||
return {
|
||||
placeInfos = placeInfos,
|
||||
}
|
||||
end)
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
local Modules = game:GetService("CorePackages").AppTempCommon
|
||||
|
||||
local Action = require(Modules.Common.Action)
|
||||
local DateTime = require(Modules.LuaChat.DateTime)
|
||||
|
||||
local luaChatUseNewFriendsAndPresenceEndpoint = settings():GetFFlag("LuaChatUseNewFriendsAndPresenceEndpointV356")
|
||||
local luaChatPlayTogetherUseRootPresence = settings():GetFFlag("LuaChatPlayTogetherUseRootPresence")
|
||||
local luaChatRootPresenceEnabled = luaChatUseNewFriendsAndPresenceEndpoint and luaChatPlayTogetherUseRootPresence
|
||||
|
||||
if luaChatRootPresenceEnabled then
|
||||
return Action(script.Name, function(userId,
|
||||
presence, lastLocation,
|
||||
placeId, rootPlaceId,
|
||||
gameInstanceId, lastOnlineISO, universeId, previousUniverseId)
|
||||
|
||||
local lastOnline = 0
|
||||
if lastOnlineISO ~= nil then
|
||||
local lastDateTime = DateTime.fromIsoDate(lastOnlineISO)
|
||||
if lastDateTime ~= nil then
|
||||
lastOnline = lastDateTime:GetUnixTimestamp()
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
userId = userId,
|
||||
presence = presence,
|
||||
lastLocation = lastLocation,
|
||||
placeId = placeId,
|
||||
rootPlaceId = rootPlaceId,
|
||||
gameInstanceId = gameInstanceId,
|
||||
lastOnline = lastOnline,
|
||||
universeId = universeId,
|
||||
previousUniverseId = previousUniverseId,
|
||||
}
|
||||
end)
|
||||
else
|
||||
return Action(script.Name, function(userId,
|
||||
presence, lastLocation,
|
||||
placeId, gameInstanceId,
|
||||
universeId, previousUniverseId)
|
||||
|
||||
return {
|
||||
userId = userId,
|
||||
presence = presence,
|
||||
lastLocation = lastLocation,
|
||||
placeId = placeId,
|
||||
gameInstanceId = gameInstanceId,
|
||||
universeId = universeId,
|
||||
previousUniverseId = previousUniverseId,
|
||||
}
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,487 @@
|
||||
--[[
|
||||
This is a Lua implementation of the DateTime API proposal. It'll eventually
|
||||
be implemented in C++ and merged into the rest of the codebase if this model
|
||||
of working with dates ends up being useful.
|
||||
]]
|
||||
|
||||
local TimeZone = require(script.Parent.TimeZone)
|
||||
local TimeUnit = require(script.Parent.TimeUnit)
|
||||
|
||||
local DateTime = {}
|
||||
|
||||
local monthShortNames = {
|
||||
"Jan", "Feb", "Mar", "Apr",
|
||||
"May", "Jun", "Jul", "Aug",
|
||||
"Sep", "Oct", "Nov", "Dec"
|
||||
}
|
||||
|
||||
local monthLongNames = {
|
||||
"January", "February", "March", "April",
|
||||
"May", "June", "July", "August",
|
||||
"September", "October", "November", "December"
|
||||
}
|
||||
|
||||
local dayShortNames = {
|
||||
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
||||
}
|
||||
|
||||
local dayLongNames = {
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday",
|
||||
"Thursday", "Friday", "Saturday"
|
||||
}
|
||||
|
||||
--[[
|
||||
We structure tokens like this to preserve order, since Lua associative
|
||||
arrays have no inherent order.
|
||||
]]
|
||||
local tokens = {
|
||||
{"YYYY", function(values)
|
||||
return tostring(values.Year)
|
||||
end},
|
||||
{"MMMM", function(values)
|
||||
return monthLongNames[values.Month]
|
||||
end},
|
||||
{"MMM", function(values)
|
||||
return monthShortNames[values.Month]
|
||||
end},
|
||||
{"MM", function(values)
|
||||
return ("%02d"):format(values.Month)
|
||||
end},
|
||||
{"M", function(values)
|
||||
return tostring(values.Month)
|
||||
end},
|
||||
{"DDDD", function(values)
|
||||
return dayLongNames[values.WeekDay]
|
||||
end},
|
||||
{"DDD", function(values)
|
||||
return dayShortNames[values.WeekDay]
|
||||
end},
|
||||
{"DD", function(values)
|
||||
return ("%02d"):format(values.Day)
|
||||
end},
|
||||
{"D", function(values)
|
||||
return tostring(values.Day)
|
||||
end},
|
||||
{"HH", function(values)
|
||||
local hour = values.Hour
|
||||
|
||||
return ("%02d"):format(hour)
|
||||
end},
|
||||
{"H", function(values)
|
||||
local hour = values.Hour
|
||||
|
||||
return tostring(hour)
|
||||
end},
|
||||
{"hh", function(values)
|
||||
local hour = values.Hour % 12
|
||||
if hour == 0 then
|
||||
hour = 12
|
||||
end
|
||||
|
||||
return ("%02d"):format(hour)
|
||||
end},
|
||||
{"h", function(values)
|
||||
local hour = values.Hour % 12
|
||||
if hour == 0 then
|
||||
hour = 12
|
||||
end
|
||||
|
||||
return tostring(hour)
|
||||
end},
|
||||
{"mm", function(values)
|
||||
return ("%02d"):format(values.Minute)
|
||||
end},
|
||||
{"m", function(values)
|
||||
return tostring(values.Minute)
|
||||
end},
|
||||
{"ss", function(values)
|
||||
return ("%02d"):format(values.Seconds)
|
||||
end},
|
||||
{"s", function(values)
|
||||
return tostring(values.Seconds)
|
||||
end},
|
||||
{"A", function(values)
|
||||
return values.Hour >= 12 and "PM" or "AM"
|
||||
end},
|
||||
{"a", function(values)
|
||||
return values.Hour >= 12 and "pm" or "am"
|
||||
end}
|
||||
}
|
||||
|
||||
local tokenKeys = {}
|
||||
for _, pair in ipairs(tokens) do
|
||||
table.insert(tokenKeys, pair[1])
|
||||
end
|
||||
|
||||
local tokenMap = {}
|
||||
for _, pair in ipairs(tokens) do
|
||||
tokenMap[pair[1]] = pair[2]
|
||||
end
|
||||
|
||||
--[[
|
||||
What's the next token in this source?
|
||||
]]
|
||||
local function getToken(source, i)
|
||||
local char = source:sub(i, i)
|
||||
|
||||
for _, token in ipairs(tokenKeys) do
|
||||
-- Only keep checking if the first character matches the token
|
||||
if token:sub(1, 1) == char then
|
||||
local match = source:sub(i, i + token:len() - 1)
|
||||
|
||||
if match == token then
|
||||
return token
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
An estimate of the current time zone's offset from UTC in seconds.
|
||||
|
||||
This might fail for weird timezones (UTC +/- 14), but we can fix that by
|
||||
picking a reference time that's further away from the Unix epoch.
|
||||
]]
|
||||
local function getTimeZoneOffset()
|
||||
local actualEpoch = 86400 + 43200
|
||||
local epoch = os.time({year = 1970, month = 1, day = 2, isdst = -1})
|
||||
if epoch then
|
||||
return actualEpoch - epoch
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a DateTime with the given values in UTC.
|
||||
|
||||
All values are optional!
|
||||
]]
|
||||
function DateTime.new(year, month, day, hour, minute, seconds)
|
||||
local tzOffset = getTimeZoneOffset()
|
||||
local timestamp = os.time({
|
||||
year = year or 1970,
|
||||
month = month or 1,
|
||||
day = day or 1,
|
||||
hour = hour or 0,
|
||||
min = minute or 0,
|
||||
sec = seconds or 0,
|
||||
isdst = -1
|
||||
})
|
||||
if timestamp == nil then
|
||||
timestamp = 0
|
||||
end
|
||||
|
||||
if seconds then
|
||||
local subseconds = seconds - math.floor(seconds)
|
||||
timestamp = timestamp + subseconds
|
||||
end
|
||||
|
||||
return DateTime.fromUnixTimestamp(timestamp + tzOffset)
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a DateTime representing now.
|
||||
]]
|
||||
function DateTime.now()
|
||||
return DateTime.fromUnixTimestamp(os.time())
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a Datetime from the given Unix timestamp.
|
||||
|
||||
Limited to the range [0, 2^32), which lets us represent dates out to about
|
||||
2038.
|
||||
]]
|
||||
function DateTime.fromUnixTimestamp(timestamp)
|
||||
assert(type(timestamp) == "number", "Invalid argument #1 to fromUnixTimestamp, expected number.")
|
||||
|
||||
local self = {}
|
||||
|
||||
self.value = timestamp
|
||||
|
||||
setmetatable(self, DateTime)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[
|
||||
Attempt to create a DateTime from an ISO 8601 date-time string.
|
||||
|
||||
Will return nil on failure and output a warning to a console denoting what
|
||||
went wrong. This can probably turned into a second return value if we need
|
||||
to handle that data programmatically.
|
||||
]]
|
||||
function DateTime.fromIsoDate(isoDate)
|
||||
assert(type(isoDate) == "string", "Invalid argument #1 to DateTime.fromIsoDate, expected string.")
|
||||
|
||||
local datePattern = "^(%d+)%-(%d+)%-(%d+)" -- 0000-00-00
|
||||
local timePattern = "T(%d+):(%d+):(%d+%.?%d*)" -- T00:00:00
|
||||
local utcPattern = "Z$"
|
||||
local timeZonePattern = "([+-]%d+):(%d+)$" -- either Z or +/- followed by "00:00"
|
||||
|
||||
local timezone = 0
|
||||
local values = {1970, 1, 1, 0, 0, 0}
|
||||
local year, month, day = isoDate:match(datePattern)
|
||||
|
||||
if not year then
|
||||
warn(("Invalid ISO 8601 date: %q"):format(isoDate))
|
||||
return nil
|
||||
end
|
||||
|
||||
values[1] = tonumber(year)
|
||||
values[2] = tonumber(month)
|
||||
values[3] = tonumber(day)
|
||||
|
||||
local hour, minute, seconds = isoDate:match(timePattern)
|
||||
|
||||
if hour then
|
||||
values[4] = tonumber(hour)
|
||||
values[5] = tonumber(minute)
|
||||
values[6] = tonumber(seconds)
|
||||
|
||||
local isUtc = isoDate:match(utcPattern)
|
||||
|
||||
if not isUtc then
|
||||
local offsetHours, offsetMinutes = isoDate:match(timeZonePattern)
|
||||
|
||||
if not offsetHours then
|
||||
local offsetTotal = getTimeZoneOffset()
|
||||
offsetHours = offsetTotal / 3600
|
||||
offsetMinutes = 0
|
||||
|
||||
warn(("Invalid time zone in ISO 8601 date: %q -- falling back to local time"):format(isoDate))
|
||||
end
|
||||
|
||||
timezone = 3600 * tonumber(offsetHours) + 60 * tonumber(offsetMinutes)
|
||||
end
|
||||
end
|
||||
|
||||
local date = DateTime.new(unpack(values))
|
||||
date.value = date.value - timezone
|
||||
|
||||
return date
|
||||
end
|
||||
|
||||
--[[
|
||||
Format our current date using a formatting string. Look at the DateTime
|
||||
proposal to see information about the different formatting tokens.
|
||||
Generally, they try to resemble LDML and/or Moment.js-style formatting.
|
||||
|
||||
The time zone parameter is optional and defaults to the current time zone,
|
||||
TimeZone.Current.
|
||||
]]
|
||||
function DateTime:Format(formatString, tz)
|
||||
assert(type(formatString) == "string", "Invalid argument #1 to Format, expected string.")
|
||||
|
||||
tz = tz or TimeZone.Current
|
||||
|
||||
local values = self:GetValues(tz)
|
||||
|
||||
local buffer = {}
|
||||
|
||||
local i = 1
|
||||
while i <= formatString:len() do
|
||||
local char = formatString:sub(i, i)
|
||||
local token = getToken(formatString, i)
|
||||
|
||||
if token then
|
||||
table.insert(buffer, tokenMap[token](values))
|
||||
i = i + token:len()
|
||||
elseif char == "[" then
|
||||
-- Crawl forward until the next ] and interpret that text literally
|
||||
local j = i
|
||||
while j <= formatString:len() do
|
||||
j = j + 1
|
||||
|
||||
if formatString:sub(j, j) == "]" then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(buffer, formatString:sub(i + 1, j - 1))
|
||||
i = j + 1
|
||||
else
|
||||
table.insert(buffer, char)
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
local result = table.concat(buffer)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--[[
|
||||
Get a table of values representing the date-time in the given timezone.
|
||||
|
||||
The time zone parameter is optional and defaults to the current zime zone,
|
||||
TimeZone.Current.
|
||||
]]
|
||||
function DateTime:GetValues(tz)
|
||||
tz = tz or TimeZone.Current
|
||||
|
||||
local reference
|
||||
|
||||
if tz == TimeZone.Current then
|
||||
reference = os.date("*t", self.value)
|
||||
elseif tz == TimeZone.UTC then
|
||||
reference = os.date("!*t", self.value)
|
||||
end
|
||||
|
||||
if not reference then
|
||||
error(("Invalid TimeZone \"%s\""):format(tostring(tz)), 2)
|
||||
end
|
||||
|
||||
return {
|
||||
Year = reference.year,
|
||||
Month = reference.month,
|
||||
Day = reference.day,
|
||||
Hour = reference.hour,
|
||||
Minute = reference.min,
|
||||
Seconds = reference.sec,
|
||||
WeekDay = reference.wday
|
||||
}
|
||||
end
|
||||
|
||||
--[[
|
||||
Recover a Unix timestamp representing the DateTime's value.
|
||||
]]
|
||||
function DateTime:GetUnixTimestamp()
|
||||
return self.value
|
||||
end
|
||||
|
||||
--[[
|
||||
Format the DateTime as an ISO 8601 date string with time attached.
|
||||
|
||||
Always formats the time as UTC. There generally aren't many reasons to
|
||||
generate an ISO 8601 date in another time zone.
|
||||
]]
|
||||
function DateTime:GetIsoDate()
|
||||
return self:Format("YYYY-MM-DD[T]HH:mm:ss[Z]", TimeZone.UTC)
|
||||
end
|
||||
|
||||
-- Used by IsSame
|
||||
local descendingGranularityUnits = {
|
||||
{
|
||||
unit = TimeUnit.Years,
|
||||
key = "Year"
|
||||
},
|
||||
{
|
||||
unit = TimeUnit.Months,
|
||||
key = "Month"
|
||||
},
|
||||
{
|
||||
unit = TimeUnit.Days,
|
||||
key = "Day"
|
||||
},
|
||||
{
|
||||
unit = TimeUnit.Hours,
|
||||
key = "Hour"
|
||||
},
|
||||
{
|
||||
unit = TimeUnit.Minutes,
|
||||
key = "Minute"
|
||||
},
|
||||
{
|
||||
unit = TimeUnit.Seconds,
|
||||
key = "Seconds"
|
||||
}
|
||||
}
|
||||
|
||||
--[[
|
||||
Checks whether two DateTime values are the same, given a granularity and
|
||||
timezone value.
|
||||
|
||||
Granularity defaults to seconds and time zone defaults to the current local
|
||||
time zone.
|
||||
]]
|
||||
function DateTime:IsSame(other, granularity, timezone)
|
||||
granularity = granularity or TimeUnit.Seconds
|
||||
timezone = timezone or TimeZone.Current
|
||||
|
||||
local selfUnix = self:GetUnixTimestamp()
|
||||
local otherUnix = other:GetUnixTimestamp()
|
||||
|
||||
if selfUnix == otherUnix then
|
||||
return true
|
||||
end
|
||||
|
||||
local selfValues = self:GetValues(timezone)
|
||||
local otherValues = other:GetValues(timezone)
|
||||
|
||||
-- Week logic is special
|
||||
if granularity == TimeUnit.Weeks then
|
||||
local diff = math.abs(selfUnix - otherUnix)
|
||||
local diffDays = diff / (60 * 60 * 24)
|
||||
|
||||
-- Two dates separated by 7 or more whole days are never in the same week
|
||||
if diffDays >= 7 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Two dates separated by less than 7 days will be sorted monotonically
|
||||
-- if they're in the same week
|
||||
-- TODO: Use start-of-week value to shift WeekDay for locale
|
||||
if selfUnix > otherUnix then
|
||||
return selfValues.WeekDay >= otherValues.WeekDay
|
||||
else
|
||||
return selfValues.WeekDay <= otherValues.WeekDay
|
||||
end
|
||||
end
|
||||
|
||||
for _, unit in ipairs(descendingGranularityUnits) do
|
||||
local selfValue = selfValues[unit.key]
|
||||
local otherValue = otherValues[unit.key]
|
||||
|
||||
if selfValue ~= otherValue then
|
||||
return false
|
||||
end
|
||||
|
||||
if unit.unit == granularity then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
Get a human-readable timestamp relative to the given epoch, which defaults
|
||||
to now. The format of the time is contextual to how far away the times are.
|
||||
]]
|
||||
function DateTime:GetLongRelativeTime(epoch)
|
||||
epoch = epoch or DateTime.now()
|
||||
|
||||
if self:IsSame(epoch, TimeUnit.Days) then
|
||||
return self:Format("h:mm A")
|
||||
elseif self:IsSame(epoch, TimeUnit.Weeks) then
|
||||
return self:Format("DDD | h:mm A")
|
||||
elseif self:IsSame(epoch, TimeUnit.Years) then
|
||||
return self:Format("MMM D | h:mm A")
|
||||
else
|
||||
return self:Format("MMM D, YYYY | h:mm A")
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Get a human-readable timestamp relative to the given epoch, which defaults
|
||||
to now. The format of the time is contextual to how far away the times are.
|
||||
]]
|
||||
function DateTime:GetShortRelativeTime(epoch)
|
||||
epoch = epoch or DateTime.now()
|
||||
|
||||
if self:IsSame(epoch, TimeUnit.Days) then
|
||||
return self:Format("h:mm A")
|
||||
elseif self:IsSame(epoch, TimeUnit.Weeks) then
|
||||
return self:Format("DDD")
|
||||
elseif self:IsSame(epoch, TimeUnit.Years) then
|
||||
return self:Format("MMM D")
|
||||
else
|
||||
return self:Format("MMM D, YYYY")
|
||||
end
|
||||
end
|
||||
|
||||
DateTime.__index = DateTime
|
||||
|
||||
return DateTime
|
||||
@@ -0,0 +1,355 @@
|
||||
return function()
|
||||
local DateTime = require(script.Parent.DateTime)
|
||||
local TimeZone = require(script.Parent.TimeZone)
|
||||
local TimeUnit = require(script.Parent.TimeUnit)
|
||||
|
||||
describe("Constructors", function()
|
||||
it("should construct with 'new'", function()
|
||||
expect(DateTime.new()).to.be.ok()
|
||||
expect(DateTime.new(2017)).to.be.ok()
|
||||
expect(DateTime.new(2017, 5)).to.be.ok()
|
||||
expect(DateTime.new(2017, 5, 3)).to.be.ok()
|
||||
expect(DateTime.new(2017, 5, 3, 12)).to.be.ok()
|
||||
expect(DateTime.new(2017, 5, 3, 12, 34)).to.be.ok()
|
||||
expect(DateTime.new(2017, 5, 3, 12, 34, 51)).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should construct with 'now'", function()
|
||||
expect(DateTime.now()).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should construct from a Unix timestamp", function()
|
||||
expect(DateTime.fromUnixTimestamp(0)).to.be.ok()
|
||||
expect(DateTime.fromUnixTimestamp(os.time())).to.be.ok()
|
||||
end)
|
||||
|
||||
it("should construct from an ISO 8601 date", function()
|
||||
-- Basic date
|
||||
do
|
||||
local date = DateTime.fromIsoDate("1988-03-17")
|
||||
expect(date).to.be.ok()
|
||||
end
|
||||
|
||||
-- Date and time
|
||||
do
|
||||
local date = DateTime.fromIsoDate("2017-04-10T20:40:16Z")
|
||||
expect(date).to.be.ok()
|
||||
expect(date:GetUnixTimestamp()).to.equal(1491856816)
|
||||
end
|
||||
|
||||
-- Date and time with no time zone
|
||||
do
|
||||
local date = DateTime.fromIsoDate("2017-04-10T20:40:16")
|
||||
expect(date).to.be.ok()
|
||||
end
|
||||
|
||||
-- Date, time, and time zone offset
|
||||
do
|
||||
local date = DateTime.fromIsoDate("2017-04-10T20:40:16+01:00")
|
||||
expect(date).to.be.ok()
|
||||
expect(date:GetUnixTimestamp()).to.equal(1491856816 - 3600)
|
||||
end
|
||||
|
||||
-- Date, time, and negative time zone offset
|
||||
do
|
||||
local date = DateTime.fromIsoDate("2017-04-10T20:40:16-01:00")
|
||||
expect(date).to.be.ok()
|
||||
expect(date:GetUnixTimestamp()).to.equal(1491856816 + 3600)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Measurements", function()
|
||||
it("should get values in UTC", function()
|
||||
local date = DateTime.new()
|
||||
local values = date:GetValues(TimeZone.UTC)
|
||||
|
||||
expect(values).to.be.ok()
|
||||
expect(values.Year).to.be.a("number")
|
||||
expect(values.Month).to.be.a("number")
|
||||
expect(values.Day).to.be.a("number")
|
||||
expect(values.Hour).to.be.a("number")
|
||||
expect(values.Minute).to.be.a("number")
|
||||
expect(values.Seconds).to.be.a("number")
|
||||
|
||||
-- Locale specific!
|
||||
expect(values.WeekDay).to.be.a("number")
|
||||
end)
|
||||
|
||||
it("should get values in local time", function()
|
||||
local date = DateTime.new()
|
||||
local values = date:GetValues(TimeZone.Current)
|
||||
|
||||
expect(values).to.be.ok()
|
||||
expect(values.Year).to.be.a("number")
|
||||
expect(values.Month).to.be.a("number")
|
||||
expect(values.Day).to.be.a("number")
|
||||
expect(values.Hour).to.be.a("number")
|
||||
expect(values.Minute).to.be.a("number")
|
||||
expect(values.Seconds).to.be.a("number")
|
||||
|
||||
-- Locale specific!
|
||||
expect(values.WeekDay).to.be.a("number")
|
||||
end)
|
||||
|
||||
it("should preserve values from 'new' constructor", function()
|
||||
local date = DateTime.new(2017, 11, 3, 12, 34, 51)
|
||||
local values = date:GetValues(TimeZone.UTC)
|
||||
|
||||
expect(values.Year).to.equal(2017)
|
||||
expect(values.Month).to.equal(11)
|
||||
expect(values.Day).to.equal(3)
|
||||
expect(values.Hour).to.equal(12)
|
||||
expect(values.Minute).to.equal(34)
|
||||
expect(values.Seconds).to.equal(51)
|
||||
end)
|
||||
|
||||
it("should preserve Unix timestamp values", function()
|
||||
do
|
||||
local date = DateTime.fromUnixTimestamp(0)
|
||||
expect(date:GetUnixTimestamp()).to.equal(0)
|
||||
end
|
||||
|
||||
do
|
||||
local date = DateTime.fromUnixTimestamp(123456789)
|
||||
expect(date:GetUnixTimestamp()).to.equal(123456789)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Formatting", function()
|
||||
it("should have correct formatting tokens", function()
|
||||
local date = DateTime.new(2016, 1, 2, 15, 8, 9)
|
||||
|
||||
-- Shortcut time zone specification
|
||||
local function format(str)
|
||||
return date:Format(str, TimeZone.UTC)
|
||||
end
|
||||
|
||||
expect(format("YYYY")).to.equal("2016")
|
||||
expect(format("M")).to.equal("1")
|
||||
expect(format("MM")).to.equal("01")
|
||||
expect(format("D")).to.equal("2")
|
||||
expect(format("DD")).to.equal("02")
|
||||
expect(format("H")).to.equal("15")
|
||||
expect(format("HH")).to.equal("15")
|
||||
expect(format("h")).to.equal("3")
|
||||
expect(format("hh")).to.equal("03")
|
||||
expect(format("m")).to.equal("8")
|
||||
expect(format("mm")).to.equal("08")
|
||||
expect(format("s")).to.equal("9")
|
||||
expect(format("ss")).to.equal("09")
|
||||
|
||||
-- Locale-specific tests!
|
||||
expect(format("MMM")).to.equal("Jan")
|
||||
expect(format("MMMM")).to.equal("January")
|
||||
expect(format("A")).to.equal("PM")
|
||||
expect(format("a")).to.equal("pm")
|
||||
end)
|
||||
|
||||
it("should preserve text within brackets", function()
|
||||
local date = DateTime.new(2017, 1, 2, 15, 8, 9)
|
||||
|
||||
local function format(str)
|
||||
return date:Format(str, TimeZone.UTC)
|
||||
end
|
||||
|
||||
expect(format("[Hello, world!]")).to.equal("Hello, world!")
|
||||
expect(format("[YYYY-MM-DD]")).to.equal("YYYY-MM-DD")
|
||||
end)
|
||||
|
||||
it("should create identical ISO 8601 dates for UTC inputs", function()
|
||||
local date = DateTime.fromIsoDate("2017-04-10T20:40:16Z")
|
||||
expect(date:GetIsoDate()).to.equal("2017-04-10T20:40:16Z")
|
||||
end)
|
||||
|
||||
it("should handle dates around midnight", function()
|
||||
local date = DateTime.new(2015, 4, 20, 0, 0, 0)
|
||||
|
||||
expect(date:Format("H", TimeZone.UTC)).to.equal("0")
|
||||
expect(date:Format("HH", TimeZone.UTC)).to.equal("00")
|
||||
expect(date:Format("h", TimeZone.UTC)).to.equal("12")
|
||||
expect(date:Format("hh", TimeZone.UTC)).to.equal("12")
|
||||
expect(date:Format("a", TimeZone.UTC)).to.equal("am")
|
||||
end)
|
||||
|
||||
it("should handle dates around noon", function()
|
||||
local date = DateTime.new(2017, 5, 23, 12, 0, 0)
|
||||
|
||||
expect(date:Format("H", TimeZone.UTC)).to.equal("12")
|
||||
expect(date:Format("HH", TimeZone.UTC)).to.equal("12")
|
||||
expect(date:Format("h", TimeZone.UTC)).to.equal("12")
|
||||
expect(date:Format("hh", TimeZone.UTC)).to.equal("12")
|
||||
expect(date:Format("a", TimeZone.UTC)).to.equal("pm")
|
||||
end)
|
||||
|
||||
it("should return correct 24-hour clock sequences", function()
|
||||
local date = DateTime.new(2017, 9, 13, 0, 0, 0)
|
||||
|
||||
local formatString = "YYYY-MM-DD HH:mm:ss"
|
||||
|
||||
local expected = {
|
||||
"2017-09-13 00:00:00",
|
||||
"2017-09-13 01:00:00",
|
||||
"2017-09-13 02:00:00",
|
||||
"2017-09-13 03:00:00",
|
||||
"2017-09-13 04:00:00",
|
||||
"2017-09-13 05:00:00",
|
||||
"2017-09-13 06:00:00",
|
||||
"2017-09-13 07:00:00",
|
||||
"2017-09-13 08:00:00",
|
||||
"2017-09-13 09:00:00",
|
||||
"2017-09-13 10:00:00",
|
||||
"2017-09-13 11:00:00",
|
||||
"2017-09-13 12:00:00",
|
||||
"2017-09-13 13:00:00",
|
||||
"2017-09-13 14:00:00",
|
||||
"2017-09-13 15:00:00",
|
||||
"2017-09-13 16:00:00",
|
||||
"2017-09-13 17:00:00",
|
||||
"2017-09-13 18:00:00",
|
||||
"2017-09-13 19:00:00",
|
||||
"2017-09-13 20:00:00",
|
||||
"2017-09-13 21:00:00",
|
||||
"2017-09-13 22:00:00",
|
||||
"2017-09-13 23:00:00",
|
||||
"2017-09-14 00:00:00",
|
||||
"2017-09-14 01:00:00",
|
||||
}
|
||||
|
||||
for i = 1, #expected do
|
||||
local result = date:Format(formatString, TimeZone.UTC)
|
||||
expect(result).to.equal(expected[i])
|
||||
|
||||
-- Advance once hour
|
||||
date = date.fromUnixTimestamp(date:GetUnixTimestamp() + 3600)
|
||||
end
|
||||
end)
|
||||
|
||||
it("should return correct 12-hour clock sequences", function()
|
||||
local date = DateTime.new(2017, 9, 13, 0, 0, 0)
|
||||
|
||||
local formatString = "YYYY-MM-DD hh:mm:ss a"
|
||||
|
||||
local expected = {
|
||||
"2017-09-13 12:00:00 am",
|
||||
"2017-09-13 01:00:00 am",
|
||||
"2017-09-13 02:00:00 am",
|
||||
"2017-09-13 03:00:00 am",
|
||||
"2017-09-13 04:00:00 am",
|
||||
"2017-09-13 05:00:00 am",
|
||||
"2017-09-13 06:00:00 am",
|
||||
"2017-09-13 07:00:00 am",
|
||||
"2017-09-13 08:00:00 am",
|
||||
"2017-09-13 09:00:00 am",
|
||||
"2017-09-13 10:00:00 am",
|
||||
"2017-09-13 11:00:00 am",
|
||||
"2017-09-13 12:00:00 pm",
|
||||
"2017-09-13 01:00:00 pm",
|
||||
"2017-09-13 02:00:00 pm",
|
||||
"2017-09-13 03:00:00 pm",
|
||||
"2017-09-13 04:00:00 pm",
|
||||
"2017-09-13 05:00:00 pm",
|
||||
"2017-09-13 06:00:00 pm",
|
||||
"2017-09-13 07:00:00 pm",
|
||||
"2017-09-13 08:00:00 pm",
|
||||
"2017-09-13 09:00:00 pm",
|
||||
"2017-09-13 10:00:00 pm",
|
||||
"2017-09-13 11:00:00 pm",
|
||||
"2017-09-14 12:00:00 am",
|
||||
"2017-09-14 01:00:00 am",
|
||||
}
|
||||
|
||||
for i = 1, #expected do
|
||||
local result = date:Format(formatString, TimeZone.UTC)
|
||||
expect(result).to.equal(expected[i])
|
||||
|
||||
-- Advance once hour
|
||||
date = date.fromUnixTimestamp(date:GetUnixTimestamp() + 3600)
|
||||
end
|
||||
end)
|
||||
|
||||
it("should have LongRelativeTime", function()
|
||||
local date = DateTime.new(2015, 4, 20, 0, 0, 0)
|
||||
expect(date:GetLongRelativeTime()).to.be.a("string")
|
||||
end)
|
||||
|
||||
it("should have ShortRelativeTime", function()
|
||||
local date = DateTime.new(2015, 4, 20, 0, 0, 0)
|
||||
expect(date:GetShortRelativeTime()).to.be.a("string")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Comparisons", function()
|
||||
describe("IsSame", function()
|
||||
it("should equate dates with different granularity", function()
|
||||
local value = DateTime.new(2003, 6, 11, 15, 8, 9)
|
||||
local same = DateTime.new(2003, 6, 11, 15, 8, 9)
|
||||
|
||||
expect(value:IsSame(value)).to.equal(true)
|
||||
expect(value:IsSame(same)).to.equal(true)
|
||||
|
||||
local units = {TimeUnit.Years, TimeUnit.Months, TimeUnit.Days, TimeUnit.Hours, TimeUnit.Minutes}
|
||||
for _, unit in ipairs(units) do
|
||||
expect(value:IsSame(same, unit)).to.equal(true)
|
||||
end
|
||||
|
||||
local sameMinute = DateTime.new(2003, 6, 11, 15, 8, 10)
|
||||
|
||||
expect(value:IsSame(sameMinute)).to.equal(false)
|
||||
expect(value:IsSame(sameMinute, TimeUnit.Minutes)).to.equal(true)
|
||||
expect(value:IsSame(sameMinute, TimeUnit.Years)).to.equal(true)
|
||||
|
||||
local sameHour = DateTime.new(2003, 6, 11, 15, 9, 0)
|
||||
|
||||
expect(value:IsSame(sameHour)).to.equal(false)
|
||||
expect(value:IsSame(sameHour, TimeUnit.Hours)).to.equal(true)
|
||||
expect(value:IsSame(sameHour, TimeUnit.Years)).to.equal(true)
|
||||
|
||||
local sameDay = DateTime.new(2003, 6, 11, 14, 8, 9)
|
||||
|
||||
expect(value:IsSame(sameDay)).to.equal(false)
|
||||
expect(value:IsSame(sameDay, TimeUnit.Days)).to.equal(true)
|
||||
expect(value:IsSame(sameDay, TimeUnit.Years)).to.equal(true)
|
||||
|
||||
local sameMonth = DateTime.new(2003, 6, 12, 15, 8, 9)
|
||||
|
||||
expect(value:IsSame(sameMonth)).to.equal(false)
|
||||
expect(value:IsSame(sameMonth, TimeUnit.Months)).to.equal(true)
|
||||
expect(value:IsSame(sameMonth, TimeUnit.Years)).to.equal(true)
|
||||
|
||||
local sameYear = DateTime.new(2003, 7, 12, 15, 8, 9)
|
||||
|
||||
expect(value:IsSame(sameYear)).to.equal(false)
|
||||
expect(value:IsSame(sameYear, TimeUnit.Years)).to.equal(true)
|
||||
|
||||
local diffYear = DateTime.new(2004, 6, 11, 15, 8, 9)
|
||||
|
||||
expect(value:IsSame(diffYear)).to.equal(false)
|
||||
expect(value:IsSame(diffYear, TimeUnit.Years)).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should equate values using week boundaries", function()
|
||||
local sunday = DateTime.new(2017, 5, 7)
|
||||
local saturday = DateTime.new(2017, 5, 13)
|
||||
local monday = DateTime.new(2017, 5, 8)
|
||||
local tuesday = DateTime.new(2017, 5, 9)
|
||||
|
||||
-- TODO: Specify locale when that lands; default may break tests
|
||||
local function sameWeek(a, b)
|
||||
return a:IsSame(b, TimeUnit.Weeks, TimeZone.UTC)
|
||||
end
|
||||
|
||||
expect(sameWeek(monday, monday)).to.equal(true)
|
||||
|
||||
expect(sameWeek(sunday, monday)).to.equal(true)
|
||||
expect(sameWeek(tuesday, monday)).to.equal(true)
|
||||
expect(sameWeek(saturday, monday)).to.equal(true)
|
||||
|
||||
local nextSunday = DateTime.new(2017, 5, 14)
|
||||
|
||||
expect(sameWeek(nextSunday, monday)).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local MockId = require(CorePackages.AppTempCommon.LuaApp.MockId)
|
||||
|
||||
local PlaceInfoModel = {}
|
||||
|
||||
function PlaceInfoModel.new()
|
||||
local self = {}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function PlaceInfoModel.mock()
|
||||
local self = PlaceInfoModel.new()
|
||||
|
||||
self.builder = "builder"
|
||||
self.builderId = MockId()
|
||||
self.description = "description"
|
||||
self.imageToken = MockId()
|
||||
self.isPlayable = true
|
||||
self.name = "name"
|
||||
self.placeId = MockId()
|
||||
self.price = 0
|
||||
self.reasonProhibited = nil
|
||||
self.universeId = MockId()
|
||||
self.universeRootPlaceId = MockId()
|
||||
self.url = "url"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function PlaceInfoModel.fromWeb(data)
|
||||
local self = data or {}
|
||||
self.placeId = tostring(self.placeId)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return PlaceInfoModel
|
||||
@@ -0,0 +1,12 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local SetFriendCount = require(CorePackages.AppTempCommon.LuaApp.Actions.SetFriendCount)
|
||||
|
||||
return function(state, action)
|
||||
state = state or 0
|
||||
|
||||
if action.type == SetFriendCount.name then
|
||||
state = action.count
|
||||
end
|
||||
|
||||
return state
|
||||
end
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local FriendCount = require(CorePackages.AppTempCommon.LuaChat.Reducers.FriendCount)
|
||||
local SetFriendCount = require(CorePackages.AppTempCommon.LuaApp.Actions.SetFriendCount)
|
||||
|
||||
it("should be zero by default", function()
|
||||
local state = FriendCount(nil, {})
|
||||
|
||||
expect(state).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should respond to SetFriendCount", function()
|
||||
local state = FriendCount(nil, {})
|
||||
state = FriendCount(state, SetFriendCount(520))
|
||||
|
||||
expect(state).to.equal(520)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Common = CorePackages.AppTempCommon.Common
|
||||
local LuaChat = CorePackages.AppTempCommon.LuaChat
|
||||
|
||||
local ReceivedMultiplePlaceInfos = require(LuaChat.Actions.ReceivedMultiplePlaceInfos)
|
||||
|
||||
local Immutable = require(Common.Immutable)
|
||||
|
||||
return function(state, action)
|
||||
state = state or {}
|
||||
if action.type == ReceivedMultiplePlaceInfos.name then
|
||||
|
||||
local newInfos = {}
|
||||
for _, placeInfo in ipairs(action.placeInfos) do
|
||||
newInfos[placeInfo.placeId] = placeInfo
|
||||
end
|
||||
|
||||
state = Immutable.JoinDictionaries(state, newInfos)
|
||||
end
|
||||
return state
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local LuaApp = CorePackages.AppTempCommon.LuaApp
|
||||
local LuaChat = CorePackages.AppTempCommon.LuaChat
|
||||
|
||||
local MockId = require(LuaApp.MockId)
|
||||
local ReceivedMultiplePlaceInfos = require(LuaChat.Actions.ReceivedMultiplePlaceInfos)
|
||||
|
||||
local PlaceInfosReducer = require(script.Parent.PlaceInfos)
|
||||
|
||||
describe("initial state", function()
|
||||
it("should return an initial table when passed nil", function()
|
||||
local state = PlaceInfosReducer(nil, {})
|
||||
expect(state).to.be.a("table")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ReceivedMultiplePlaceInfos", function()
|
||||
it("should add place info to the store", function()
|
||||
local state = PlaceInfosReducer(nil, {})
|
||||
|
||||
local placeId = MockId()
|
||||
local returnedPlaceInfo = ReceivedMultiplePlaceInfos({
|
||||
{
|
||||
placeId = placeId,
|
||||
imageToken = "image-token",
|
||||
},
|
||||
})
|
||||
|
||||
state = PlaceInfosReducer(state, returnedPlaceInfo)
|
||||
|
||||
expect(state[placeId]).to.equal(returnedPlaceInfo.placeInfos[1])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
local TimeUnit = setmetatable({}, {
|
||||
__index = function(self, key)
|
||||
error(("Invalid TimeUnit \"%s\""):format(tostring(key)), 2)
|
||||
end
|
||||
})
|
||||
|
||||
TimeUnit.Seconds = "Seconds"
|
||||
TimeUnit.Minutes = "Minutes"
|
||||
TimeUnit.Hours = "Hours"
|
||||
TimeUnit.Days = "Days"
|
||||
TimeUnit.Months = "Months"
|
||||
TimeUnit.Years = "Years"
|
||||
|
||||
-- Locale-specific
|
||||
TimeUnit.Weeks = "Weeks"
|
||||
|
||||
return TimeUnit
|
||||
@@ -0,0 +1,10 @@
|
||||
local TimeZone = setmetatable({}, {
|
||||
__index = function(self, key)
|
||||
error(("Invalid TimeZone \"%s\""):format(tostring(key)), 2)
|
||||
end
|
||||
})
|
||||
|
||||
TimeZone.UTC = -2
|
||||
TimeZone.Current = -1
|
||||
|
||||
return TimeZone
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local User = require(CorePackages.AppTempCommon.LuaApp.Models.User)
|
||||
local WebPresenceMap = require(CorePackages.AppTempCommon.LuaApp.Enum.WebPresenceMap)
|
||||
|
||||
return function(friendsPresence, store)
|
||||
local placeIds = {}
|
||||
|
||||
for _, presenceModel in pairs(friendsPresence) do
|
||||
if WebPresenceMap[presenceModel.userPresenceType] == User.PresenceType.IN_GAME
|
||||
and (not store:getState().UniversePlaceInfos[presenceModel.universeId]) then
|
||||
table.insert(placeIds, presenceModel.placeId)
|
||||
end
|
||||
end
|
||||
|
||||
return placeIds
|
||||
end
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local ReceivedUserPresence = require(CorePackages.AppTempCommon.LuaChat.Actions.ReceivedUserPresence)
|
||||
local WebPresenceMap = require(CorePackages.AppTempCommon.LuaApp.Enum.WebPresenceMap)
|
||||
|
||||
local luaChatUseNewFriendsAndPresenceEndpoint = settings():GetFFlag("LuaChatUseNewFriendsAndPresenceEndpointV356")
|
||||
local luaChatPlayTogetherUseRootPresence = settings():GetFFlag("LuaChatPlayTogetherUseRootPresence")
|
||||
local luaChatRootPresenceEnabled = luaChatUseNewFriendsAndPresenceEndpoint and luaChatPlayTogetherUseRootPresence
|
||||
|
||||
return function(friendsPresence, store)
|
||||
for _, presenceModel in pairs(friendsPresence) do
|
||||
local userInStore = store:getState().Users[tostring(presenceModel.userId)]
|
||||
local previousUniverseId = userInStore and userInStore.universeId or nil
|
||||
|
||||
if luaChatRootPresenceEnabled then
|
||||
store:dispatch(ReceivedUserPresence(
|
||||
tostring(presenceModel.userId),
|
||||
WebPresenceMap[presenceModel.userPresenceType],
|
||||
presenceModel.lastLocation,
|
||||
presenceModel.placeId and tostring(presenceModel.placeId) or nil,
|
||||
presenceModel.rootPlaceId and tostring(presenceModel.rootPlaceId) or nil,
|
||||
presenceModel.gameId and tostring(presenceModel.gameId) or nil,
|
||||
presenceModel.lastOnline and tostring(presenceModel.lastOnline) or nil,
|
||||
presenceModel.universeId,
|
||||
previousUniverseId
|
||||
))
|
||||
else
|
||||
store:dispatch(ReceivedUserPresence(
|
||||
tostring(presenceModel.userId),
|
||||
WebPresenceMap[presenceModel.userPresenceType],
|
||||
presenceModel.lastLocation,
|
||||
presenceModel.placeId and tostring(presenceModel.placeId) or nil,
|
||||
presenceModel.gameId and tostring(presenceModel.gameId) or nil,
|
||||
presenceModel.universeId,
|
||||
previousUniverseId
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user