--[[ | 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. 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