Files
wnsrc/lua/niknaks/modules/sh_datetime.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

402 lines
10 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
-- All Rights Reserved. Not allowed to be reuploaded.
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
NikNaks.DateTime = {}
local localvars, os_time, os_date, rawget, tonumber, getmetatable = {}, os.time, os.date, rawget, tonumber, getmetatable
-- TimeZone / Date variables
do
local UTC_DAY = os_date( "%d", 0 ) - os_date( "!%d", 0 )
local UTC_Timezone = tonumber( os_date( "%H", 0 ) ) - tonumber( os_date( "!%H", 0 ) )
if UTC_DAY == 30 then
UTC_Timezone = UTC_Timezone - 24
end
local UTC_Timezone_dst = tonumber( os_date( "%z" ) ) / 100
local DaylightsSaving = UTC_Timezone_dst - UTC_Timezone
NikNaks.DateTime.dst = DaylightsSaving
NikNaks.DateTime.timezone = UTC_Timezone
NikNaks.DateTime.timezone_dst = UTC_Timezone_dst
end
local function is_leap_year( year )
return year % 4 == 0 and ( year % 100 ~= 0 or year % 400 == 0 )
end
function NikNaks.DateTime.IsLeapYear( year )
return is_leap_year( year or NikNaks.DateTime.year )
end
do
local months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
function NikNaks.DateTime.DaysInMonth( month, year )
if month == 2 and is_leap_year( year or NikNaks.DateTime.year ) then
return 29
end
return months[month]
end
function NikNaks.DateTime.Calender( year )
year = year or NikNaks.DateTime.year
local c = {}
c.year = year
c.month = {}
for i = 1, 12 do
if i == 2 and is_leap_year( year ) then
c.month[i] = 29
else
c.month[i] = months[i]
end
end
return c
end
end
-- Date variables
local function updatedate()
local date = string.Explode( ":", os_date( "%H:%M:%S:%d:%m:%Y" ) )
NikNaks.DateTime.day = tonumber( date[4] )
NikNaks.DateTime.month = tonumber( date[5] )
NikNaks.DateTime.year = tonumber( date[6] )
-- Calculates next cycle
local t_seconds = tonumber( date[1] ) * 3600 + tonumber( date[2] ) * 60 + tonumber( date[3] )
local nextUpdate = 86400 - t_seconds
timer.Create( "NikNaks_DateUpdate", math.max( nextUpdate, 1 ), 1, updatedate )
end
updatedate()
-- Branch metatable
setmetatable( NikNaks.DateTime, {
__index = function( _, v )
local l = rawget( localvars, v )
return rawget( NikNaks.DateTime, v ) or l and l()
end,
__call = function( _, var )
return NikNaks.DateTime.Get( var )
end
} )
local string_to_var
do
-- Tries to parse hour, minute and seconds
local function findTime( str )
local h, m, s, ampm = string.match( str:upper(), "([01]?%d):(%d%d?):?(%d*)%s*([AP][M])" )
if not h then
h, m, s = string.match( str, "(%d%d?):(%d%d?):?(%d*)" )
end
if not h then return nil end
h = tonumber( h )
m = tonumber( m )
s = tonumber( s )
if ampm then
if ampm == "AM" then
if h == 12 then h = 0 end
else
if h ~= 12 then h = h + 12 end
end
end
return h, m, s
end
-- Tries to parse year, month, day
local findDate
do
local date_tab = {
"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
}
local date_pattern = "[JFMASOND][AEPUCO][NBRYLGPTVC]"
local function findMonthNameAndDate( str )
if not string.match( str, date_pattern ) then return nil end
for m_id, date in ipairs( date_tab ) do
if string.match( str, date ) then
local d = string.match( str, date .. "%a*%s?(%d%d?)" ) or string.match( str, "(%d%d?)%s?" .. date )
return m_id, d and tonumber( d ) or 1
end
end
end
function findDate( str )
-- The year number tent to mess with the rest, if found replace it if found.
local fy = true
local y = string.match( str, "(%d%d%d%d)" )
local m, d
if y then
str = string.gsub( str, "%d%d%d%d", "", 1 )
y = tonumber( y )
else
-- Check of YY/MM/DD
y, m, d = string.match( str, "(%d+)[/%-](%d%d?)[/%-](%d%d?)" )
if not y then -- Year must be today
y = NikNaks.DateTime.year
fy = false
else
return tonumber( y ), tonumber( m ), tonumber( d )
end
end
-- Find MM/DD
m, d = string.match( str, "(%d%d?)[/%-](%d%d?)" )
if m and d then
return y, tonumber( m ), tonumber( d )
end
-- No date found. Try string-scan for month names
m, d = findMonthNameAndDate( str:upper() ) -- Try parse letters
if m then
return y, m, d
end
-- If only a year is given, then return the first day in that year.
if fy then
return y, 1, 1
end
end
end
-- Tries to parse timezone. Since os.time use the locate time, this will be negative.
local function findOffset( str )
if str:sub( -1 ) == "Z" then return -NikNaks.DateTime.timezone_dst end
local sign, h, m = str:match( "([%-%+])(%d%d?):?(%d?%d?)$" )
if sign then
return ( tonumber( sign .. h ) + tonumber( sign .. m ) / 60 ) - NikNaks.DateTime.timezone_dst
else
-- Use local
return 0
end
end
function string_to_var( str )
str = string.Trim( str )
if #str ~= 4 then
local n = string.match( str, "%d+" )
if #n == #str then return tonumber( n ) end
end
--[[
Sun, 03 Jan 2010 00:00:00 GMT
September 26, 2006 12:12 AM
2012-10-06T04:13:00+00:00
2012/10/6
2008-05-01T07:34:42-5:00
2008-05-01 7:34:42Z
Thu, 01 May 2008 07:34:42 GMT
]]
local h, m, s = findTime( str )
-- Get Time & Date
local year, month, day = findDate( str )
-- Find offset
local offsetH = findOffset( str ) or 0
-- Convert to unix
return os_time( {
day = day or 1,
hour = h or 0,
min = m or 0,
month = month or 1,
sec = tonumber( s ) or 0,
year = year
} ) + offsetH * 3600
end
end
--- @class DateTime
local datetime_obj = {}
datetime_obj.__index = datetime_obj
NikNaks.__metatables["DateTime"] = datetime_obj
function NikNaks.DateTime.Get( var, t_zone )
if not var then
var = os_time()
else
local _type = type( var )
if _type == "string" then
var = string_to_var( var )
elseif _type == "table" then
if var.time then
var = var.time + os_time()
elseif var.unix then
var = var.unix
else -- Unknown
return nil
end
end
end
-- Unable to create
if not var then return nil end
-- Create object and return
--- @class DateTime
local t = {}
t.unix = var
t.timezone = t_zone
return setmetatable( t, datetime_obj )
end
function datetime_obj:GetUnix()
return self.unix
end
function datetime_obj:TimeUntil( var )
local unix
local _type = type( var )
if _type == "string" then
unix = string_to_var( var )
elseif _type == "table" then
if var.time then
return var.time -- Will always be relative
elseif var.unix then
unix = var.unix
end
end
return NikNaks.TimeDelta( unix - self.unix, tonumber( os.date( "%Y", self.unix ) ) )
end
-- Local variable functions: DateTime.<X>
function localvars.now()
return NikNaks.DateTime.Get( os_time() )
end
localvars.today = localvars.now
function localvars.yesterday()
return NikNaks.DateTime.Get( os_time() - NikNaks.TimeDelta.Day )
end
function localvars.tomorrow()
return NikNaks.DateTime.Get( os_time() + NikNaks.TimeDelta.Day )
end
--- Returns the time using os.date
--- @param format string
--- @return string
function datetime_obj:ToDate( format )
return os_date( format, self.unix )
end
datetime_obj.__tostring = function( self )
return os_date( nil, self.unix )
end
-- Operations
function datetime_obj.__sub( a, b )
if not getmetatable( a ) then -- A is most likely a number. Number - Obj = TimeDelta
return NikNaks.TimeDelta( a - b.unix )
elseif not getmetatable( b ) then -- B is most likely a number. Obj - Number = New Obj
return NikNaks.DateTime.Get( a.unix - b )
else -- Both are objects
if a.unix and b.unix then
return NikNaks.TimeDelta( a.unix - b.unix )
elseif a.unix then
return NikNaks.DateTime.Get( a.unix - ( b.time or b ) )
elseif b.unix then
return NikNaks.DateTime.Get( b.unix - ( a.time or a ) )
end
end
end
function datetime_obj.__add( a, b )
if not getmetatable( a ) then -- A is most likely a number. Number + Obj = New Obj
return NikNaks.DateTime.Get( b.unix + a )
elseif not getmetatable( b ) then -- B is most likely a number. Obj - Number = New Obj
return NikNaks.DateTime.Get( a.unix + b )
else -- Both are objects
if a.unix and b.unix then -- Get the higest unix-time and add the delta between the two
return NikNaks.DateTime.Get( math.max( a.unix, b.unix ) + abs( a.unix - b.unix ) )
elseif a.unix then
return NikNaks.DateTime.Get( a.unix + ( b.time or b ) )
elseif b.unix then
return NikNaks.DateTime.Get( b.unix + ( a.time or a ) )
end
end
end
function datetime_obj.__concat( a, b )
return tostring( a ) .. tostring( b )
end
-- Not supported in Gmod!
datetime_obj.__shl = datetime_obj.__sub
datetime_obj.__shr = datetime_obj.__add
-- Sadly Lua doesn't support mixed types for compare-operations
function datetime_obj.__eq( a, b )
return a.unix == b.unix
end
function datetime_obj.__lt( a, b )
return a.unix < b.unix
end
function datetime_obj.__le( a, b )
return a.unix <= b.unix
end
-- TimeDelta functions
for key, var in pairs( NikNaks.TimeDelta ) do
datetime_obj["Add" .. key .. "s"] = function( self, num )
self.unix = self.unix + num * var
return self
end
datetime_obj["Remove" .. key .. "s"] = function( self, num )
self.unix = self.unix - num * var
return self
end
datetime_obj["Sub" .. key .. "s"] = datetime_obj["Remove" .. key .. "s"]
end
-- DateTime string debug test
if true then return end
local t = { "Sun, 01 Sep 2022 00:12:00",
"September 01, 2022 12:12 AM",
"2022-09-01T00:12:00+02:00",
"2022/09/1",
"2022-09-01T07:12:00-5:00",
"2022-09-01 02:12:00Z",
"Thu, 01 Sep 2022 00:12:00" }
function ParseTest()
for _, str in ipairs( t ) do
print( str .. string.rep( " ", 30 - #str ), "=>", NikNaks.DateTime.Get( str ) )
end
end
function SpeedTest()
local n = 20000 * #t
local s = SysTime()
for _, str in ipairs( t ) do
for _ = 1, 20000 do
NikNaks.DateTime.Get( str )
end
end
print( string.format( n .. " took: %fs", SysTime() - s ) )
end