You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
19 KiB

5 years ago
--===========================================================================--
-- --
-- System.Date --
-- --
--===========================================================================--
--===========================================================================--
-- Author : kurapica125@outlook.com --
-- URL : http://github.com/kurapica/PLoop --
-- Create Date : 2016/03/08 --
-- Update Date : 2021/06/01 --
-- Version : 1.0.3 --
--===========================================================================--
PLoop(function(_ENV)
namespace "System"
import "System.Serialization"
export {
strfind = string.find,
strgmatch = string.gmatch,
-- %a Abbreviated weekday name Thu
-- %A Full weekday namespace Thursday
-- %b Abbreviated month name Aug
-- %B Full month name August
-- %c Date and time representation Thu Aug 23 14:55:02 2001
-- %d Day of the month, zero-padded (01-31) 23
-- %H Hour in 24h format (00-23) 14
-- %I Hour in 12h format (01-12) 02
-- %j Day of the year (001-366) 235
-- %m Month as a decimal number (01-12) 08
-- %M Minute (00-59) 55
-- %p AM or PM designation PM
-- %S Second (00-61) 02
-- %U Week number with the first Sunday as the first day of week one (00-53) 33
-- %w Weekday as a decimal number with Sunday as 0 (0-6) 4
-- %W Week number with the first Monday as the first day of week one (00-53) 34
-- %x Date representation 08/23/01
-- %X Time representation 14:55:02
-- %y Year, last two digits (00-99) 01
-- %Y Year 2001
-- %Z Timezone name or abbreviation CDT
ALLOW_TIMEFORMAT = (function() local r = {} ("aAbBcdHIjmMpSUwWxXyYZ"):gsub("%w", function(w) r[w] = true end) return r end)()
}
--- Represents the time format can be used in date api
__Sealed__() __Base__(String)
struct "TimeFormat" {
function(val, onlyvalid)
if strfind(val, "*t") then
return onlyvalid or "the %s can't contains '*t' as time format"
else
local hasfmt = false
for s in strgmatch(val, "%%(.)") do
if ALLOW_TIMEFORMAT[s] then
hasfmt = true
else
return onlyvalid or "the '" .. s .. "' can't be used as time format"
end
end
if not hasfmt then return "the %s doesn't contains any time formats" end
end
end
}
--- Represents the date object
__Final__() __Sealed__() __Serializable__() __NoRawSet__(false) __NoNilValue__(false) __SerializeFormat__(TargetFormat.NUMBER)
class "Date" (function (_ENV)
extend "ICloneable" "ISerializable"
export {
date = _G.os and os.date or _G.date,
time = _G.os and os.time or _G.time,
diff = _G.os and os.difftime or _G.difftime,
floor = math.floor,
pairs = pairs,
type = type,
getmetatable = getmetatable,
strfind = strfind,
rawset = rawset,
tonumber = tonumber,
Serialization.TargetFormat
}
local offset = diff(time(date("*t", 10^8)), time(date("!*t", 10^8)))
local r2Time = function (self) self.time = time(self) end
local r4Time = function (self) for k, v in pairs(date("*t", self.time)) do rawset(self, k, v) end end
local getnow = time
local parseString = function(s)
local year, month, day, hour, min, sec
local index = 0
for n in s:gmatch("%d+") do
index = index + 1
if index == 1 then
year = tonumber(n)
elseif index == 2 then
month = tonumber(n)
elseif index == 3 then
day = tonumber(n)
elseif index == 4 then
hour = tonumber(n)
elseif index == 5 then
min = tonumber(n)
elseif index == 6 then
sec = tonumber(n)
break
end
end
if (year and month and day and year >= 1970 and month >= 1 and month <= 12 and day >= 1 and day <= 31)
and (not hour or (hour >= 0 and hour < 24))
and (not min or (min >= 0 and min < 60))
and (not sec or (sec >= 0 and sec < 60)) then
return year, month, day, hour, min, sec
end
end
-----------------------------------------------------------
-- static property --
-----------------------------------------------------------
--- Gets a DateTime object that is set to the current date and time on this computer, expressed as the local time.
__Static__()
property "Now" { get = function() return Date() end }
--- Gets and Sets the function that return the current time value(the total second from 1970/1/1 00:00:00)
__Static__()
property "GetTimeOfDay" { type = Function, set = function(self, val) getnow = val end, get = function() return getnow end }
-----------------------------------------------------------
-- property --
-----------------------------------------------------------
--- The year of the date
property "Year" { type = Integer, field = "year", handler = r2Time }
--- The month of the year, 1-12
property "Month" { type = Integer, field = "month",handler = function(self, value) r2Time(self) if value < 1 or value > 12 then r4Time(self) end end }
--- The day of the month, 1-31
property "Day" { type = Integer, field = "day", handler = function(self, value) r2Time(self) if value < 1 or value > 28 then r4Time(self) end end }
--- The hour of the day, 0-23
property "Hour" { type = Integer, field = "hour", handler = function(self, value) r2Time(self) if value < 0 or value > 23 then r4Time(self) end end }
--- The minute of the hour, 0-59
property "Minute" { type = Integer, field = "min", handler = function(self, value) r2Time(self) if value < 0 or value > 59 then r4Time(self) end end }
--- The Second of the minute, 0-61
property "Second" { type = Integer, field = "sec", handler = function(self, value) r2Time(self) if value < 0 or value > 59 then r4Time(self) end end }
--- The week number, with the mondy is the first day
property "Week" { get = function(self) return tonumber(date("%W", self.time)) end}
--- The weekday 1 (for Monday) to 7 (for Sunday)
property "DayOfWeek" { get = function(self) return date("*t", self.time).wday -1 end }
--- The day of the year
property "DayOfYear" { get = function(self) return date("*t", self.time).yday end }
--- Indicates whether this instance of DateTime is within the daylight saving time range for the current time zone.
property "IsDaylightSavingTime" { get = function(self) return date("*t", self.time).isdst end }
--- Gets the time that represent the date and time of this instance.
property "Time" { type = Integer, field = "time", handler = r4Time }
-----------------------------------------------------------
-- static method --
-----------------------------------------------------------
--- Parse a string data to Date object
__Static__() __Arguments__{ NEString, TimeFormat, Boolean/false }
function Parse(s, format, isutc)
local year, month, day, hour, min, sec
local index = 1
if format:find("^!") then
isutc = true
format = format:sub(2)
end
-- %d Day of the month, zero-padded (01-31) 23
-- %H Hour in 24h format (00-23) 14
-- %m Month as a decimal number (01-12) 08
-- %M Minute (00-59) 55
-- %S Second (00-61) 02
-- %X Time representation 14:55:02
-- %Y Year 2001
local pattern = format:gsub("%%(.)", function(w)
if w == "d" then
day = index
index = index + 1
return "(%d?%d)"
elseif w == "H" then
hour = index
index = index + 1
return "(%d?%d)"
elseif w == "m" then
month = index
index = index + 1
return "(%d?%d)"
elseif w == "M" then
min = index
index = index + 1
return "(%d?%d)"
elseif w == "S" then
sec = index
index = index + 1
return "(%d?%d)"
elseif w == "X" then
hour = index
index = index + 1
min = index
index = index + 1
sec = index
index = index + 1
return "(%d?%d):(%d?%d):(%d?%d)"
elseif w == "Y" then
year = index
index = index + 1
return "(%d%d%d%d)"
end
end)
if not (year and month and day) or ((hour or min or sec) and not (hour and min and sec)) then
error("Usage: Date.Parse(s[, format][,isutc]) - the format isn't valid", 2)
end
local rs = { s:match(pattern) }
year = tonumber(rs[year])
month = tonumber(rs[month])
day = tonumber(rs[day])
if hour then
hour = tonumber(rs[hour])
min = tonumber(rs[min])
sec = tonumber(rs[sec])
end
if not (year and month and day) or ((hour or min or sec) and not (hour and min and sec)) then
return
end
return Date(year, month, day, hour, min, sec, isutc)
end
__Static__() __Arguments__{ NEString, Boolean/false, Boolean/false }
function Parse(s, isEndOfDay, isutc)
local year, month, day, hour, min, sec = parseString(s)
if year then
return Date(year, month, day, hour or isEndOfDay and 23 or 0, min or isEndOfDay and 59 or 0, sec or isEndOfDay and 59 or 0, isutc)
end
end
-----------------------------------------------------------
-- method --
-----------------------------------------------------------
--- Serialize the date
function Serialize(self, info)
if info == TargetFormat.STRING then
return self:ToString()
elseif info == TargetFormat.NUMBER then
return self.Time
else
info:SetValue("time", self.Time)
end
end
--- Return the diff second for the two Date object
__Arguments__{ Date }
function Diff(self, obj) return diff(self.time, obj.time) end
--- Converts the value of the current DateTime object to its equivalent string representation using the specified format.
__Arguments__{ TimeFormat/"%Y-%m-%d %X" }
function ToString(self, fmt)
return date(fmt, self.time)
end
--- Converts the value of the current DateTime object to its equivalent UTC string representation using the specified format.
__Arguments__{ TimeFormat/"!%Y-%m-%d %X" }
function ToUTCString(self, fmt)
if not strfind(fmt, "^!") then fmt = "!" .. fmt end
return date(fmt, self.time)
end
--- Adds the specified number of years to the value of this instance, and return a new date object
__Arguments__{ Integer }
function AddYears(self, years)
return Date(self.year + years, self.month, self.day, self.hour, self.min, self.sec)
end
--- Adds the specified number of months to the value of this instance, and return a new date object
__Arguments__{ Integer }
function AddMonths(self, months)
return Date(self.year, self.month + months, self.day, self.hour, self.min, self.sec)
end
--- Adds the specified number of months to the value of this instance, and return a new date object
__Arguments__{ Integer }
function AddDays(self, days)
return Date(self.year, self.month, self.day + days, self.hour, self.min, self.sec)
end
--- Adds the specified number of hours to the value of this instance, and return a new date object
__Arguments__{ Integer }
function AddHours(self, hours)
return Date(self.year, self.month, self.day, self.hour + hours, self.min, self.sec)
end
--- Adds the specified number of minutes to the value of this instance, and return a new date object
__Arguments__{ Integer }
function AddMinutes(self, minutes)
return Date(self.year, self.month, self.day, self.hour, self.min + minutes, self.sec)
end
--- Adds the specified number of seconds to the value of this instance, and return a new date object
__Arguments__{ Integer }
function AddSeconds(self, seconds)
return Date(self.year, self.month, self.day, self.hour, self.min, self.sec + seconds)
end
--- Return a Clone of the date oject
function Clone(self)
return Date(self.Time)
end
-----------------------------------------------------------
-- constructor --
-----------------------------------------------------------
__Arguments__{ TargetFormat, Any }
function __new(_, fmt, value)
local vtype = type(value)
if vtype == "number" then
local self = { time = floor(value) }
r4Time(self)
return self, true
elseif vtype == "string" then
local year, month, day, hour, min, sec = parseString(value)
if year then
local self = {
year = year,
month = month,
day = day,
hour = hour,
min = min,
sec = sec,
}
r2Time(self)
r4Time(self)
return self, true
end
end
throw("The value can't be deserialized to Date object")
end
__Arguments__{ SerializationInfo }
function __new(_, info)
local self = { time = info:GetValue("time") or 0 }
r4Time(self)
return self, true
end
__Arguments__{ RawTable }
function __new(_, time)
-- No more check
return time, true
end
__Arguments__{ Variable("time", Integer, true) }
function __new(_, tm)
local self = { time = tm or getnow() }
r4Time(self)
return self, true
end
__Arguments__{
Variable("year", Integer),
Variable("month", Integer),
Variable("day", Integer),
Variable("hour", Integer, true, 12),
Variable("min", Integer, true, 0),
Variable("sec", Integer, true, 0),
Variable("utc", Boolean, true, false)
}
function __new(_, year, month, day, hour, min, sec, utc)
local self = {
year = year,
month = month,
day = day,
hour = hour,
min = min,
sec = utc and (sec + offset) or sec,
}
r2Time(self)
r4Time(self)
return self, true
end
------------------------------------
-- Meta-method
------------------------------------
__Arguments__{ Date }
function __eq(self, obj) return self.time == obj.time end
__Arguments__{ Date }
function __lt(self, obj) return self.time < obj.time end
__Arguments__{ Date }
function __le(self, obj) return self.time <= obj.time end
__add = AddSeconds
__sub = Diff
__tostring = ToString
export { Date }
end)
end)