1--------------------------------------------------------------------------------------- 2-- Module for date and time calculations 3-- 4-- Version 2.1.1 5-- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com) 6-- Copyright (C) 2013-2014, by Thijs Schreijer 7-- Licensed under MIT, http://opensource.org/licenses/MIT 8 9--[[ CONSTANTS ]]-- 10local HOURPERDAY = 24 11local MINPERHOUR = 60 12local MINPERDAY = 1440 -- 24*60 13local SECPERMIN = 60 14local SECPERHOUR = 3600 -- 60*60 15local SECPERDAY = 86400 -- 24*60*60 16local TICKSPERSEC = 1000000 17local TICKSPERDAY = 86400000000 18local TICKSPERHOUR = 3600000000 19local TICKSPERMIN = 60000000 20local DAYNUM_MAX = 365242500 -- Sat Jan 01 1000000 00:00:00 21local DAYNUM_MIN = -365242500 -- Mon Jan 01 1000000 BCE 00:00:00 22local DAYNUM_DEF = 0 -- Mon Jan 01 0001 00:00:00 23local _; 24--[[ LOCAL ARE FASTER ]]-- 25local type = type 26local pairs = pairs 27local error = error 28local assert = assert 29local tonumber = tonumber 30local tostring = tostring 31local string = string 32local math = math 33local os = os 34local unpack = unpack or table.unpack 35local setmetatable = setmetatable 36local getmetatable = getmetatable 37--[[ EXTRA FUNCTIONS ]]-- 38local fmt = string.format 39local lwr = string.lower 40local rep = string.rep 41local len = string.len -- luacheck: ignore 42local sub = string.sub 43local gsub = string.gsub 44local gmatch = string.gmatch or string.gfind 45local find = string.find 46local ostime = os.time 47local osdate = os.date 48local floor = math.floor 49local ceil = math.ceil 50local abs = math.abs 51-- removes the decimal part of a number 52local function fix(n) n = tonumber(n) return n and ((n > 0 and floor or ceil)(n)) end 53-- returns the modulo n % d; 54local function mod(n,d) return n - d*floor(n/d) end 55-- is `str` in string list `tbl`, `ml` is the minimun len 56local function inlist(str, tbl, ml, tn) 57 local sl = len(str) 58 if sl < (ml or 0) then return nil end 59 str = lwr(str) 60 for k, v in pairs(tbl) do 61 if str == lwr(sub(v, 1, sl)) then 62 if tn then tn[0] = k end 63 return k 64 end 65 end 66end 67local function fnil() end 68--[[ DATE FUNCTIONS ]]-- 69local DATE_EPOCH -- to be set later 70local sl_weekdays = { 71 [0]="Sunday",[1]="Monday",[2]="Tuesday",[3]="Wednesday",[4]="Thursday",[5]="Friday",[6]="Saturday", 72 [7]="Sun",[8]="Mon",[9]="Tue",[10]="Wed",[11]="Thu",[12]="Fri",[13]="Sat", 73} 74local sl_meridian = {[-1]="AM", [1]="PM"} 75local sl_months = { 76 [00]="January", [01]="February", [02]="March", 77 [03]="April", [04]="May", [05]="June", 78 [06]="July", [07]="August", [08]="September", 79 [09]="October", [10]="November", [11]="December", 80 [12]="Jan", [13]="Feb", [14]="Mar", 81 [15]="Apr", [16]="May", [17]="Jun", 82 [18]="Jul", [19]="Aug", [20]="Sep", 83 [21]="Oct", [22]="Nov", [23]="Dec", 84} 85-- added the '.2' to avoid collision, use `fix` to remove 86local sl_timezone = { 87 [000]="utc", [0.2]="gmt", 88 [300]="est", [240]="edt", 89 [360]="cst", [300.2]="cdt", 90 [420]="mst", [360.2]="mdt", 91 [480]="pst", [420.2]="pdt", 92} 93-- set the day fraction resolution 94local function setticks(t) 95 TICKSPERSEC = t; 96 TICKSPERDAY = SECPERDAY*TICKSPERSEC 97 TICKSPERHOUR= SECPERHOUR*TICKSPERSEC 98 TICKSPERMIN = SECPERMIN*TICKSPERSEC 99end 100-- is year y leap year? 101local function isleapyear(y) -- y must be int! 102 return (mod(y, 4) == 0 and (mod(y, 100) ~= 0 or mod(y, 400) == 0)) 103end 104-- day since year 0 105local function dayfromyear(y) -- y must be int! 106 return 365*y + floor(y/4) - floor(y/100) + floor(y/400) 107end 108-- day number from date, month is zero base 109local function makedaynum(y, m, d) 110 local mm = mod(mod(m,12) + 10, 12) 111 return dayfromyear(y + floor(m/12) - floor(mm/10)) + floor((mm*306 + 5)/10) + d - 307 112 --local yy = y + floor(m/12) - floor(mm/10) 113 --return dayfromyear(yy) + floor((mm*306 + 5)/10) + (d - 1) 114end 115-- date from day number, month is zero base 116local function breakdaynum(g) 117 local g = g + 306 118 local y = floor((10000*g + 14780)/3652425) 119 local d = g - dayfromyear(y) 120 if d < 0 then y = y - 1; d = g - dayfromyear(y) end 121 local mi = floor((100*d + 52)/3060) 122 return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) 123end 124--[[ for floats or int32 Lua Number data type 125local function breakdaynum2(g) 126 local g, n = g + 306; 127 local n400 = floor(g/DI400Y);n = mod(g,DI400Y); 128 local n100 = floor(n/DI100Y);n = mod(n,DI100Y); 129 local n004 = floor(n/DI4Y); n = mod(n,DI4Y); 130 local n001 = floor(n/365); n = mod(n,365); 131 local y = (n400*400) + (n100*100) + (n004*4) + n001 - ((n001 == 4 or n100 == 4) and 1 or 0) 132 local d = g - dayfromyear(y) 133 local mi = floor((100*d + 52)/3060) 134 return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) 135end 136]] 137-- day fraction from time 138local function makedayfrc(h,r,s,t) 139 return ((h*60 + r)*60 + s)*TICKSPERSEC + t 140end 141-- time from day fraction 142local function breakdayfrc(df) 143 return 144 mod(floor(df/TICKSPERHOUR),HOURPERDAY), 145 mod(floor(df/TICKSPERMIN ),MINPERHOUR), 146 mod(floor(df/TICKSPERSEC ),SECPERMIN), 147 mod(df,TICKSPERSEC) 148end 149-- weekday sunday = 0, monday = 1 ... 150local function weekday(dn) return mod(dn + 1, 7) end 151-- yearday 0 based ... 152local function yearday(dn) 153 return dn - dayfromyear((breakdaynum(dn))-1) 154end 155-- parse v as a month 156local function getmontharg(v) 157 local m = tonumber(v); 158 return (m and fix(m - 1)) or inlist(tostring(v) or "", sl_months, 2) 159end 160-- get daynum of isoweek one of year y 161local function isow1(y) 162 local f = makedaynum(y, 0, 4) -- get the date for the 4-Jan of year `y` 163 local d = weekday(f) 164 d = d == 0 and 7 or d -- get the ISO day number, 1 == Monday, 7 == Sunday 165 return f + (1 - d) 166end 167local function isowy(dn) 168 local w1; 169 local y = (breakdaynum(dn)) 170 if dn >= makedaynum(y, 11, 29) then 171 w1 = isow1(y + 1); 172 if dn < w1 then 173 w1 = isow1(y); 174 else 175 y = y + 1; 176 end 177 else 178 w1 = isow1(y); 179 if dn < w1 then 180 w1 = isow1(y-1) 181 y = y - 1 182 end 183 end 184 return floor((dn-w1)/7)+1, y 185end 186local function isoy(dn) 187 local y = (breakdaynum(dn)) 188 return y + (((dn >= makedaynum(y, 11, 29)) and (dn >= isow1(y + 1))) and 1 or (dn < isow1(y) and -1 or 0)) 189end 190local function makedaynum_isoywd(y,w,d) 191 return isow1(y) + 7*w + d - 8 -- simplified: isow1(y) + ((w-1)*7) + (d-1) 192end 193--[[ THE DATE MODULE ]]-- 194local fmtstr = "%x %X"; 195--#if not DATE_OBJECT_AFX then 196local date = {} 197setmetatable(date, date) 198-- Version: VMMMRRRR; V-Major, M-Minor, R-Revision; e.g. 5.45.321 == 50450321 199date.version = 20010001 -- 2.1.1 200--#end -- not DATE_OBJECT_AFX 201--[[ THE DATE OBJECT ]]-- 202local dobj = {} 203dobj.__index = dobj 204dobj.__metatable = dobj 205-- shout invalid arg 206local function date_error_arg() return error("invalid argument(s)",0) end 207-- create new date object 208local function date_new(dn, df) 209 return setmetatable({daynum=dn, dayfrc=df}, dobj) 210end 211 212--#if not NO_LOCAL_TIME_SUPPORT then 213-- magic year table 214local date_epoch, yt; 215local function getequivyear(y) 216 assert(not yt) 217 yt = {} 218 local de = date_epoch:copy() 219 local dw, dy 220 for _ = 0, 3000 do 221 de:setyear(de:getyear() + 1, 1, 1) 222 dy = de:getyear() 223 dw = de:getweekday() * (isleapyear(dy) and -1 or 1) 224 if not yt[dw] then yt[dw] = dy end --print(de) 225 if yt[1] and yt[2] and yt[3] and yt[4] and yt[5] and yt[6] and yt[7] and yt[-1] and yt[-2] and yt[-3] and yt[-4] and yt[-5] and yt[-6] and yt[-7] then 226 getequivyear = function(y) return yt[ (weekday(makedaynum(y, 0, 1)) + 1) * (isleapyear(y) and -1 or 1) ] end 227 return getequivyear(y) 228 end 229 end 230end 231-- TimeValue from date and time 232local function totv(y,m,d,h,r,s) 233 return (makedaynum(y, m, d) - DATE_EPOCH) * SECPERDAY + ((h*60 + r)*60 + s) 234end 235-- TimeValue from TimeTable 236local function tmtotv(tm) 237 return tm and totv(tm.year, tm.month - 1, tm.day, tm.hour, tm.min, tm.sec) 238end 239-- Returns the bias in seconds of utc time daynum and dayfrc 240local function getbiasutc2(self) 241 local y,m,d = breakdaynum(self.daynum) 242 local h,r,s = breakdayfrc(self.dayfrc) 243 local tvu = totv(y,m,d,h,r,s) -- get the utc TimeValue of date and time 244 local tml = osdate("*t", tvu) -- get the local TimeTable of tvu 245 if (not tml) or (tml.year > (y+1) or tml.year < (y-1)) then -- failed try the magic 246 y = getequivyear(y) 247 tvu = totv(y,m,d,h,r,s) 248 tml = osdate("*t", tvu) 249 end 250 local tvl = tmtotv(tml) 251 if tvu and tvl then 252 return tvu - tvl, tvu, tvl 253 else 254 return error("failed to get bias from utc time") 255 end 256end 257-- Returns the bias in seconds of local time daynum and dayfrc 258local function getbiasloc2(daynum, dayfrc) 259 local tvu 260 -- extract date and time 261 local y,m,d = breakdaynum(daynum) 262 local h,r,s = breakdayfrc(dayfrc) 263 -- get equivalent TimeTable 264 local tml = {year=y, month=m+1, day=d, hour=h, min=r, sec=s} 265 -- get equivalent TimeValue 266 local tvl = tmtotv(tml) 267 268 local function chkutc() 269 tml.isdst = nil; local tvug = ostime(tml) if tvug and (tvl == tmtotv(osdate("*t", tvug))) then tvu = tvug return end 270 tml.isdst = true; local tvud = ostime(tml) if tvud and (tvl == tmtotv(osdate("*t", tvud))) then tvu = tvud return end 271 tvu = tvud or tvug 272 end 273 chkutc() 274 if not tvu then 275 tml.year = getequivyear(y) 276 tvl = tmtotv(tml) 277 chkutc() 278 end 279 return ((tvu and tvl) and (tvu - tvl)) or error("failed to get bias from local time"), tvu, tvl 280end 281--#end -- not NO_LOCAL_TIME_SUPPORT 282 283--#if not DATE_OBJECT_AFX then 284-- the date parser 285local strwalker = {} -- ^Lua regular expression is not as powerful as Perl$ 286strwalker.__index = strwalker 287local function newstrwalker(s)return setmetatable({s=s, i=1, e=1, c=len(s)}, strwalker) end 288function strwalker:aimchr() return "\n" .. self.s .. "\n" .. rep(".",self.e-1) .. "^" end 289function strwalker:finish() return self.i > self.c end 290function strwalker:back() self.i = self.e return self end 291function strwalker:restart() self.i, self.e = 1, 1 return self end 292function strwalker:match(s) return (find(self.s, s, self.i)) end 293function strwalker:__call(s, f)-- print("strwalker:__call "..s..self:aimchr()) 294 local is, ie; is, ie, self[1], self[2], self[3], self[4], self[5] = find(self.s, s, self.i) 295 if is then self.e, self.i = self.i, 1+ie; if f then f(unpack(self)) end return self end 296end 297 local function date_parse(str) 298 local y,m,d, h,r,s, z, w,u, j, e, x,c, dn,df 299 local sw = newstrwalker(gsub(gsub(str, "(%b())", ""),"^(%s*)","")) -- remove comment, trim leading space 300 --local function error_out() print(y,m,d,h,r,s) end 301 local function error_dup(q) --[[error_out()]] error("duplicate value: " .. (q or "") .. sw:aimchr()) end 302 local function error_syn(q) --[[error_out()]] error("syntax error: " .. (q or "") .. sw:aimchr()) end 303 local function error_inv(q) --[[error_out()]] error("invalid date: " .. (q or "") .. sw:aimchr()) end 304 local function sety(q) y = y and error_dup() or tonumber(q); end 305 local function setm(q) m = (m or w or j) and error_dup(m or w or j) or tonumber(q) end 306 local function setd(q) d = d and error_dup() or tonumber(q) end 307 local function seth(q) h = h and error_dup() or tonumber(q) end 308 local function setr(q) r = r and error_dup() or tonumber(q) end 309 local function sets(q) s = s and error_dup() or tonumber(q) end 310 local function adds(q) s = s + tonumber(q) end 311 local function setj(q) j = (m or w or j) and error_dup() or tonumber(q); end 312 local function setz(q) z = (z ~= 0 and z) and error_dup() or q end 313 local function setzn(zs,zn) zn = tonumber(zn); setz( ((zn<24) and (zn*60) or (mod(zn,100) + floor(zn/100) * 60))*( zs=='+' and -1 or 1) ) end 314 local function setzc(zs,zh,zm) setz( ((tonumber(zh)*60) + tonumber(zm))*( zs=='+' and -1 or 1) ) end 315 316 if not (sw("^(%d%d%d%d)",sety) and (sw("^(%-?)(%d%d)%1(%d%d)",function(_,a,b) setm(tonumber(a)); setd(tonumber(b)) end) or sw("^(%-?)[Ww](%d%d)%1(%d?)",function(_,a,b) w, u = tonumber(a), tonumber(b or 1) end) or sw("^%-?(%d%d%d)",setj) or sw("^%-?(%d%d)",function(a) setm(a);setd(1) end)) 317 and ((sw("^%s*[Tt]?(%d%d):?",seth) and sw("^(%d%d):?",setr) and sw("^(%d%d)",sets) and sw("^(%.%d+)",adds)) 318 or sw:finish() or (sw"^%s*$" or sw"^%s*[Zz]%s*$" or sw("^%s-([%+%-])(%d%d):?(%d%d)%s*$",setzc) or sw("^%s*([%+%-])(%d%d)%s*$",setzn)) 319 ) ) 320 then --print(y,m,d,h,r,s,z,w,u,j) 321 sw:restart(); y,m,d,h,r,s,z,w,u,j = nil,nil,nil,nil,nil,nil,nil,nil,nil,nil 322 repeat -- print(sw:aimchr()) 323 if sw("^[tT:]?%s*(%d%d?):",seth) then --print("$Time") 324 _ = sw("^%s*(%d%d?)",setr) and sw("^%s*:%s*(%d%d?)",sets) and sw("^(%.%d+)",adds) 325 elseif sw("^(%d+)[/\\%s,-]?%s*") then --print("$Digits") 326 x, c = tonumber(sw[1]), len(sw[1]) 327 if (x >= 70) or (m and d and (not y)) or (c > 3) then 328 sety( x + ((x >= 100 or c>3)and 0 or 1900) ) 329 else 330 if m then setd(x) else m = x end 331 end 332 elseif sw("^(%a+)[/\\%s,-]?%s*") then --print("$Words") 333 x = sw[1] 334 if inlist(x, sl_months, 2, sw) then 335 if m and (not d) and (not y) then d, m = m, false end 336 setm(mod(sw[0],12)+1) 337 elseif inlist(x, sl_timezone, 2, sw) then 338 c = fix(sw[0]) -- ignore gmt and utc 339 if c ~= 0 then setz(c, x) end 340 elseif not inlist(x, sl_weekdays, 2, sw) then 341 sw:back() 342 -- am pm bce ad ce bc 343 if sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*[Ee]%s*(%2)%s*") or sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*") then 344 e = e and error_dup() or -1 345 elseif sw("^([aA])%s*(%.?)%s*[Dd]%s*(%2)%s*") or sw("^([cC])%s*(%.?)%s*[Ee]%s*(%2)%s*") then 346 e = e and error_dup() or 1 347 elseif sw("^([PApa])%s*(%.?)%s*[Mm]?%s*(%2)%s*") then 348 x = lwr(sw[1]) -- there should be hour and it must be correct 349 if (not h) or (h > 12) or (h < 0) then return error_inv() end 350 if x == 'a' and h == 12 then h = 0 end -- am 351 if x == 'p' and h ~= 12 then h = h + 12 end -- pm 352 else error_syn() end 353 end 354 elseif not(sw("^([+-])(%d%d?):(%d%d)",setzc) or sw("^([+-])(%d+)",setzn) or sw("^[Zz]%s*$")) then -- sw{"([+-])",{"(%d%d?):(%d%d)","(%d+)"}} 355 error_syn("?") 356 end 357 sw("^%s*") until sw:finish() 358 --else print("$Iso(Date|Time|Zone)") 359 end 360 -- if date is given, it must be complete year, month & day 361 if (not y and not h) or ((m and not d) or (d and not m)) or ((m and w) or (m and j) or (j and w)) then return error_inv("!") end 362 -- fix month 363 if m then m = m - 1 end 364 -- fix year if we are on BCE 365 if e and e < 0 and y > 0 then y = 1 - y end 366 -- create date object 367 dn = (y and ((w and makedaynum_isoywd(y,w,u)) or (j and makedaynum(y, 0, j)) or makedaynum(y, m, d))) or DAYNUM_DEF 368 df = makedayfrc(h or 0, r or 0, s or 0, 0) + ((z or 0)*TICKSPERMIN) 369 --print("Zone",h,r,s,z,m,d,y,df) 370 return date_new(dn, df) -- no need to :normalize(); 371 end 372local function date_fromtable(v) 373 local y, m, d = fix(v.year), getmontharg(v.month), fix(v.day) 374 local h, r, s, t = tonumber(v.hour), tonumber(v.min), tonumber(v.sec), tonumber(v.ticks) 375 -- atleast there is time or complete date 376 if (y or m or d) and (not(y and m and d)) then return error("incomplete table") end 377 return (y or h or r or s or t) and date_new(y and makedaynum(y, m, d) or DAYNUM_DEF, makedayfrc(h or 0, r or 0, s or 0, t or 0)) 378end 379local tmap = { 380 ['number'] = function(v) return date_epoch:copy():addseconds(v) end, 381 ['string'] = function(v) return date_parse(v) end, 382 ['boolean']= function(v) return date_fromtable(osdate(v and "!*t" or "*t")) end, 383 ['table'] = function(v) local ref = getmetatable(v) == dobj; return ref and v or date_fromtable(v), ref end 384} 385local function date_getdobj(v) 386 local o, r = (tmap[type(v)] or fnil)(v); 387 return (o and o:normalize() or error"invalid date time value"), r -- if r is true then o is a reference to a date obj 388end 389--#end -- not DATE_OBJECT_AFX 390local function date_from(arg1, arg2, arg3, arg4, arg5, arg6, arg7) 391 local y, m, d = fix(arg1), getmontharg(arg2), fix(arg3) 392 local h, r, s, t = tonumber(arg4 or 0), tonumber(arg5 or 0), tonumber(arg6 or 0), tonumber(arg7 or 0) 393 if y and m and d and h and r and s and t then 394 return date_new(makedaynum(y, m, d), makedayfrc(h, r, s, t)):normalize() 395 else 396 return date_error_arg() 397 end 398end 399 400--[[ THE DATE OBJECT METHODS ]]-- 401function dobj:normalize() 402 local dn, df = fix(self.daynum), self.dayfrc 403 self.daynum, self.dayfrc = dn + floor(df/TICKSPERDAY), mod(df, TICKSPERDAY) 404 return (dn >= DAYNUM_MIN and dn <= DAYNUM_MAX) and self or error("date beyond imposed limits:"..self) 405end 406 407function dobj:getdate() local y, m, d = breakdaynum(self.daynum) return y, m+1, d end 408function dobj:gettime() return breakdayfrc(self.dayfrc) end 409 410function dobj:getclockhour() local h = self:gethours() return h>12 and mod(h,12) or (h==0 and 12 or h) end 411 412function dobj:getyearday() return yearday(self.daynum) + 1 end 413function dobj:getweekday() return weekday(self.daynum) + 1 end -- in lua weekday is sunday = 1, monday = 2 ... 414 415function dobj:getyear() local r,_,_ = breakdaynum(self.daynum) return r end 416function dobj:getmonth() local _,r,_ = breakdaynum(self.daynum) return r+1 end-- in lua month is 1 base 417function dobj:getday() local _,_,r = breakdaynum(self.daynum) return r end 418function dobj:gethours() return mod(floor(self.dayfrc/TICKSPERHOUR),HOURPERDAY) end 419function dobj:getminutes() return mod(floor(self.dayfrc/TICKSPERMIN), MINPERHOUR) end 420function dobj:getseconds() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN) end 421function dobj:getfracsec() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN)+(mod(self.dayfrc,TICKSPERSEC)/TICKSPERSEC) end 422function dobj:getticks(u) local x = mod(self.dayfrc,TICKSPERSEC) return u and ((x*u)/TICKSPERSEC) or x end 423 424function dobj:getweeknumber(wdb) 425 local wd, yd = weekday(self.daynum), yearday(self.daynum) 426 if wdb then 427 wdb = tonumber(wdb) 428 if wdb then 429 wd = mod(wd-(wdb-1),7)-- shift the week day base 430 else 431 return date_error_arg() 432 end 433 end 434 return (yd < wd and 0) or (floor(yd/7) + ((mod(yd, 7)>=wd) and 1 or 0)) 435end 436 437function dobj:getisoweekday() return mod(weekday(self.daynum)-1,7)+1 end -- sunday = 7, monday = 1 ... 438function dobj:getisoweeknumber() return (isowy(self.daynum)) end 439function dobj:getisoyear() return isoy(self.daynum) end 440function dobj:getisodate() 441 local w, y = isowy(self.daynum) 442 return y, w, self:getisoweekday() 443end 444function dobj:setisoyear(y, w, d) 445 local cy, cw, cd = self:getisodate() 446 if y then cy = fix(tonumber(y))end 447 if w then cw = fix(tonumber(w))end 448 if d then cd = fix(tonumber(d))end 449 if cy and cw and cd then 450 self.daynum = makedaynum_isoywd(cy, cw, cd) 451 return self:normalize() 452 else 453 return date_error_arg() 454 end 455end 456 457function dobj:setisoweekday(d) return self:setisoyear(nil, nil, d) end 458function dobj:setisoweeknumber(w,d) return self:setisoyear(nil, w, d) end 459 460function dobj:setyear(y, m, d) 461 local cy, cm, cd = breakdaynum(self.daynum) 462 if y then cy = fix(tonumber(y))end 463 if m then cm = getmontharg(m) end 464 if d then cd = fix(tonumber(d))end 465 if cy and cm and cd then 466 self.daynum = makedaynum(cy, cm, cd) 467 return self:normalize() 468 else 469 return date_error_arg() 470 end 471end 472 473function dobj:setmonth(m, d)return self:setyear(nil, m, d) end 474function dobj:setday(d) return self:setyear(nil, nil, d) end 475 476function dobj:sethours(h, m, s, t) 477 local ch,cm,cs,ck = breakdayfrc(self.dayfrc) 478 ch, cm, cs, ck = tonumber(h or ch), tonumber(m or cm), tonumber(s or cs), tonumber(t or ck) 479 if ch and cm and cs and ck then 480 self.dayfrc = makedayfrc(ch, cm, cs, ck) 481 return self:normalize() 482 else 483 return date_error_arg() 484 end 485end 486 487function dobj:setminutes(m,s,t) return self:sethours(nil, m, s, t) end 488function dobj:setseconds(s, t) return self:sethours(nil, nil, s, t) end 489function dobj:setticks(t) return self:sethours(nil, nil, nil, t) end 490 491function dobj:spanticks() return (self.daynum*TICKSPERDAY + self.dayfrc) end 492function dobj:spanseconds() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERSEC end 493function dobj:spanminutes() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERMIN end 494function dobj:spanhours() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERHOUR end 495function dobj:spandays() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERDAY end 496 497function dobj:addyears(y, m, d) 498 local cy, cm, cd = breakdaynum(self.daynum) 499 if y then y = fix(tonumber(y))else y = 0 end 500 if m then m = fix(tonumber(m))else m = 0 end 501 if d then d = fix(tonumber(d))else d = 0 end 502 if y and m and d then 503 self.daynum = makedaynum(cy+y, cm+m, cd+d) 504 return self:normalize() 505 else 506 return date_error_arg() 507 end 508end 509 510function dobj:addmonths(m, d) 511 return self:addyears(nil, m, d) 512end 513 514local function dobj_adddayfrc(self,n,pt,pd) 515 n = tonumber(n) 516 if n then 517 local x = floor(n/pd); 518 self.daynum = self.daynum + x; 519 self.dayfrc = self.dayfrc + (n-x*pd)*pt; 520 return self:normalize() 521 else 522 return date_error_arg() 523 end 524end 525function dobj:adddays(n) return dobj_adddayfrc(self,n,TICKSPERDAY,1) end 526function dobj:addhours(n) return dobj_adddayfrc(self,n,TICKSPERHOUR,HOURPERDAY) end 527function dobj:addminutes(n) return dobj_adddayfrc(self,n,TICKSPERMIN,MINPERDAY) end 528function dobj:addseconds(n) return dobj_adddayfrc(self,n,TICKSPERSEC,SECPERDAY) end 529function dobj:addticks(n) return dobj_adddayfrc(self,n,1,TICKSPERDAY) end 530local tvspec = { 531 -- Abbreviated weekday name (Sun) 532 ['%a']=function(self) return sl_weekdays[weekday(self.daynum) + 7] end, 533 -- Full weekday name (Sunday) 534 ['%A']=function(self) return sl_weekdays[weekday(self.daynum)] end, 535 -- Abbreviated month name (Dec) 536 ['%b']=function(self) return sl_months[self:getmonth() - 1 + 12] end, 537 -- Full month name (December) 538 ['%B']=function(self) return sl_months[self:getmonth() - 1] end, 539 -- Year/100 (19, 20, 30) 540 ['%C']=function(self) return fmt("%.2d", fix(self:getyear()/100)) end, 541 -- The day of the month as a number (range 1 - 31) 542 ['%d']=function(self) return fmt("%.2d", self:getday()) end, 543 -- year for ISO 8601 week, from 00 (79) 544 ['%g']=function(self) return fmt("%.2d", mod(self:getisoyear() ,100)) end, 545 -- year for ISO 8601 week, from 0000 (1979) 546 ['%G']=function(self) return fmt("%.4d", self:getisoyear()) end, 547 -- same as %b 548 ['%h']=function(self) return self:fmt0("%b") end, 549 -- hour of the 24-hour day, from 00 (06) 550 ['%H']=function(self) return fmt("%.2d", self:gethours()) end, 551 -- The hour as a number using a 12-hour clock (01 - 12) 552 ['%I']=function(self) return fmt("%.2d", self:getclockhour()) end, 553 -- The day of the year as a number (001 - 366) 554 ['%j']=function(self) return fmt("%.3d", self:getyearday()) end, 555 -- Month of the year, from 01 to 12 556 ['%m']=function(self) return fmt("%.2d", self:getmonth()) end, 557 -- Minutes after the hour 55 558 ['%M']=function(self) return fmt("%.2d", self:getminutes())end, 559 -- AM/PM indicator (AM) 560 ['%p']=function(self) return sl_meridian[self:gethours() > 11 and 1 or -1] end, --AM/PM indicator (AM) 561 -- The second as a number (59, 20 , 01) 562 ['%S']=function(self) return fmt("%.2d", self:getseconds()) end, 563 -- ISO 8601 day of the week, to 7 for Sunday (7, 1) 564 ['%u']=function(self) return self:getisoweekday() end, 565 -- Sunday week of the year, from 00 (48) 566 ['%U']=function(self) return fmt("%.2d", self:getweeknumber()) end, 567 -- ISO 8601 week of the year, from 01 (48) 568 ['%V']=function(self) return fmt("%.2d", self:getisoweeknumber()) end, 569 -- The day of the week as a decimal, Sunday being 0 570 ['%w']=function(self) return self:getweekday() - 1 end, 571 -- Monday week of the year, from 00 (48) 572 ['%W']=function(self) return fmt("%.2d", self:getweeknumber(2)) end, 573 -- The year as a number without a century (range 00 to 99) 574 ['%y']=function(self) return fmt("%.2d", mod(self:getyear() ,100)) end, 575 -- Year with century (2000, 1914, 0325, 0001) 576 ['%Y']=function(self) return fmt("%.4d", self:getyear()) end, 577 -- Time zone offset, the date object is assumed local time (+1000, -0230) 578 ['%z']=function(self) local b = -self:getbias(); local x = abs(b); return fmt("%s%.4d", b < 0 and "-" or "+", fix(x/60)*100 + floor(mod(x,60))) end, 579 -- Time zone name, the date object is assumed local time 580 ['%Z']=function(self) return self:gettzname() end, 581 -- Misc -- 582 -- Year, if year is in BCE, prints the BCE Year representation, otherwise result is similar to "%Y" (1 BCE, 40 BCE) 583 ['%\b']=function(self) local x = self:getyear() return fmt("%.4d%s", x>0 and x or (-x+1), x>0 and "" or " BCE") end, 584 -- Seconds including fraction (59.998, 01.123) 585 ['%\f']=function(self) local x = self:getfracsec() return fmt("%s%.9f",x >= 10 and "" or "0", x) end, 586 -- percent character % 587 ['%%']=function(self) return "%" end, 588 -- Group Spec -- 589 -- 12-hour time, from 01:00:00 AM (06:55:15 AM); same as "%I:%M:%S %p" 590 ['%r']=function(self) return self:fmt0("%I:%M:%S %p") end, 591 -- hour:minute, from 01:00 (06:55); same as "%I:%M" 592 ['%R']=function(self) return self:fmt0("%I:%M") end, 593 -- 24-hour time, from 00:00:00 (06:55:15); same as "%H:%M:%S" 594 ['%T']=function(self) return self:fmt0("%H:%M:%S") end, 595 -- month/day/year from 01/01/00 (12/02/79); same as "%m/%d/%y" 596 ['%D']=function(self) return self:fmt0("%m/%d/%y") end, 597 -- year-month-day (1979-12-02); same as "%Y-%m-%d" 598 ['%F']=function(self) return self:fmt0("%Y-%m-%d") end, 599 -- The preferred date and time representation; same as "%x %X" 600 ['%c']=function(self) return self:fmt0("%x %X") end, 601 -- The preferred date representation, same as "%a %b %d %\b" 602 ['%x']=function(self) return self:fmt0("%a %b %d %\b") end, 603 -- The preferred time representation, same as "%H:%M:%\f" 604 ['%X']=function(self) return self:fmt0("%H:%M:%\f") end, 605 -- GroupSpec -- 606 -- Iso format, same as "%Y-%m-%dT%T" 607 ['${iso}'] = function(self) return self:fmt0("%Y-%m-%dT%T") end, 608 -- http format, same as "%a, %d %b %Y %T GMT" 609 ['${http}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, 610 -- ctime format, same as "%a %b %d %T GMT %Y" 611 ['${ctime}'] = function(self) return self:fmt0("%a %b %d %T GMT %Y") end, 612 -- RFC850 format, same as "%A, %d-%b-%y %T GMT" 613 ['${rfc850}'] = function(self) return self:fmt0("%A, %d-%b-%y %T GMT") end, 614 -- RFC1123 format, same as "%a, %d %b %Y %T GMT" 615 ['${rfc1123}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, 616 -- asctime format, same as "%a %b %d %T %Y" 617 ['${asctime}'] = function(self) return self:fmt0("%a %b %d %T %Y") end, 618} 619function dobj:fmt0(str) return (gsub(str, "%%[%a%%\b\f]", function(x) local f = tvspec[x];return (f and f(self)) or x end)) end 620function dobj:fmt(str) 621 str = str or self.fmtstr or fmtstr 622 return self:fmt0((gmatch(str, "${%w+}")) and (gsub(str, "${%w+}", function(x)local f=tvspec[x];return (f and f(self)) or x end)) or str) 623end 624 625function dobj.__lt(a, b) if (a.daynum == b.daynum) then return (a.dayfrc < b.dayfrc) else return (a.daynum < b.daynum) end end 626function dobj.__le(a, b) if (a.daynum == b.daynum) then return (a.dayfrc <= b.dayfrc) else return (a.daynum <= b.daynum) end end 627function dobj.__eq(a, b)return (a.daynum == b.daynum) and (a.dayfrc == b.dayfrc) end 628function dobj.__sub(a,b) 629 local d1, d2 = date_getdobj(a), date_getdobj(b) 630 local d0 = d1 and d2 and date_new(d1.daynum - d2.daynum, d1.dayfrc - d2.dayfrc) 631 return d0 and d0:normalize() 632end 633function dobj.__add(a,b) 634 local d1, d2 = date_getdobj(a), date_getdobj(b) 635 local d0 = d1 and d2 and date_new(d1.daynum + d2.daynum, d1.dayfrc + d2.dayfrc) 636 return d0 and d0:normalize() 637end 638function dobj.__concat(a, b) return tostring(a) .. tostring(b) end 639function dobj:__tostring() return self:fmt() end 640 641function dobj:copy() return date_new(self.daynum, self.dayfrc) end 642 643--[[ THE LOCAL DATE OBJECT METHODS ]]-- 644function dobj:tolocal() 645 local dn,df = self.daynum, self.dayfrc 646 local bias = getbiasutc2(self) 647 if bias then 648 -- utc = local + bias; local = utc - bias 649 self.daynum = dn 650 self.dayfrc = df - bias*TICKSPERSEC 651 return self:normalize() 652 else 653 return nil 654 end 655end 656 657function dobj:toutc() 658 local dn,df = self.daynum, self.dayfrc 659 local bias = getbiasloc2(dn, df) 660 if bias then 661 -- utc = local + bias; 662 self.daynum = dn 663 self.dayfrc = df + bias*TICKSPERSEC 664 return self:normalize() 665 else 666 return nil 667 end 668end 669 670function dobj:getbias() return (getbiasloc2(self.daynum, self.dayfrc))/SECPERMIN end 671 672function dobj:gettzname() 673 local _, tvu, _ = getbiasloc2(self.daynum, self.dayfrc) 674 return tvu and osdate("%Z",tvu) or "" 675end 676 677--#if not DATE_OBJECT_AFX then 678function date.time(h, r, s, t) 679 h, r, s, t = tonumber(h or 0), tonumber(r or 0), tonumber(s or 0), tonumber(t or 0) 680 if h and r and s and t then 681 return date_new(DAYNUM_DEF, makedayfrc(h, r, s, t)) 682 else 683 return date_error_arg() 684 end 685end 686 687function date:__call(arg1, ...) 688 local arg_count = select("#", ...) + (arg1 == nil and 0 or 1) 689 if arg_count > 1 then return (date_from(arg1, ...)) 690 elseif arg_count == 0 then return (date_getdobj(false)) 691 else local o, r = date_getdobj(arg1); return r and o:copy() or o end 692end 693 694date.diff = dobj.__sub 695 696function date.isleapyear(v) 697 local y = fix(v); 698 if not y then 699 y = date_getdobj(v) 700 y = y and y:getyear() 701 end 702 return isleapyear(y+0) 703end 704 705function date.epoch() return date_epoch:copy() end 706 707function date.isodate(y,w,d) return date_new(makedaynum_isoywd(y + 0, w and (w+0) or 1, d and (d+0) or 1), 0) end 708 709-- Internal functions 710function date.fmt(str) if str then fmtstr = str end; return fmtstr end 711function date.daynummin(n) DAYNUM_MIN = (n and n < DAYNUM_MAX) and n or DAYNUM_MIN return n and DAYNUM_MIN or date_new(DAYNUM_MIN, 0):normalize()end 712function date.daynummax(n) DAYNUM_MAX = (n and n > DAYNUM_MIN) and n or DAYNUM_MAX return n and DAYNUM_MAX or date_new(DAYNUM_MAX, 0):normalize()end 713function date.ticks(t) if t then setticks(t) end return TICKSPERSEC end 714--#end -- not DATE_OBJECT_AFX 715 716local tm = osdate("!*t", 0); 717if tm then 718 date_epoch = date_new(makedaynum(tm.year, tm.month - 1, tm.day), makedayfrc(tm.hour, tm.min, tm.sec, 0)) 719 -- the distance from our epoch to os epoch in daynum 720 DATE_EPOCH = date_epoch and date_epoch:spandays() 721else -- error will be raise only if called! 722 date_epoch = setmetatable({},{__index = function() error("failed to get the epoch date") end}) 723end 724 725--#if not DATE_OBJECT_AFX then 726return date 727--#else 728--$return date_from 729--#end 730