Skip to content

Instantly share code, notes, and snippets.

@xjdrew
Last active November 13, 2017 02:19
Show Gist options
  • Save xjdrew/ba8aabb7a9c4a1b41957f6ebc5c89ad9 to your computer and use it in GitHub Desktop.
Save xjdrew/ba8aabb7a9c4a1b41957f6ebc5c89ad9 to your computer and use it in GitHub Desktop.
pure lua version of os.date("!*t", time)
-- port from golang: https://golang.org/src/time/time.go
--
local secondsPerMinute = 60
local secondsPerHour = 60 * 60
local secondsPerDay = 24 * secondsPerHour
local secondsPerWeek = 7 * secondsPerDay
local daysPer400Years = 365*400 + 97
local daysPer100Years = 365*100 + 24
local daysPer4Years = 365*4 + 1
local unixBase = (1969*365 + 1969//4 - 1969//100 + 1969//400) * secondsPerDay
-- daysBefore[m] counts the number of days in a non-leap year
-- before month m begins. There is an entry for m=12, counting
-- the number of days before January of next year (365).
local daysBefore = {
0,
31,
31 + 28,
31 + 28 + 31,
31 + 28 + 31 + 30,
31 + 28 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
}
local January = 1
local February = 2
local March = 3
local April = 4
local May = 5
local June = 6
local July = 7
local August = 8
local September = 9
local October = 10
local November = 11
local December = 12
local Sunday = 1
local Monday = 2
local Tuesday = 3
local Wednesday = 4
local Thursday = 5
local Friday = 6
local Saturday = 7
-- absTimestamp returns the absolute second from {year=1, month=1, day=1, hour=0, mintue=0, second=0}
local function absTimestamp(ts)
return ts + unixBase
end
local function isLeap(year)
return year%4 == 0 and (year%100 ~= 0 or year%400 == 0)
end
local function absWeekday(abs)
-- January 1 of the absolute year, like January 1 of 2001, was a Monday.
local sec = (abs+1*secondsPerDay) % secondsPerWeek
return (sec // secondsPerDay) + 1
end
-- convert timestamp to year, month, day, yday
local function absDate(abs)
local day = abs // secondsPerDay
local n, y
-- Account for 400 year cycles.
n = day // daysPer400Years
y = 400 * n
day = day - daysPer400Years * n
-- Cut off 100-year cycles.
-- The last cycle has one extra leap year, so on the last day
-- of that year, day / daysPer100Years will be 4 instead of 3.
-- Cut it back down to 3 by subtracting n>>2.
n = day // daysPer100Years
n = n - (n >> 2)
y = y + 100 *n
day = day - daysPer100Years * n
-- Cut off 4-year cycles.
-- The last cycle has a missing leap year, which does not
-- affect the computation.
n = day // daysPer4Years
y = y + 4 * n
day = day - daysPer4Years * n
-- Cut off years within a 4-year cycle.
-- The last year is a leap year, so on the last day of that year,
-- day / 365 will be 4 instead of 3. Cut it back down to 3
-- by subtracting n>>2.
n = day // 365
n = n - (n >> 2)
y = y + n
day = day - 365 * n
local year = y + 1
local yday = day + 1
if isLeap(year) then
-- Leap year
if day > 31+29-1 then
-- After leap day; pretend it wasn't there.
day = day - 1
elseif day == 31+29-1 then
return year, February, 29, yday
end
end
-- Estimate month on assumption that every month has 31 days.
-- The estimate may be too low by at most one month, so adjust.
local month = (day // 31) + 1
local monthDayEnd = daysBefore[month + 1]
local monthDayBegin
if day >= monthDayEnd then
month = month + 1
monthDayBegin = monthDayEnd
else
monthDayBegin = daysBefore[month]
end
day = day - monthDayBegin + 1
return year, month, day, yday
end
local M = {}
M.TZ = 8
function M.localdate(sec, tz, t)
local tz = tz or M.TZ
return M.date(sec + tz*secondsPerHour, t)
end
function M.date(sec, t)
local abs = absTimestamp(sec)
local t = t or {}
t.year, t.month, t.day, t.yday = absDate(abs)
t.wday = absWeekday(abs)
local seconds = abs % secondsPerDay
t.hour = seconds // secondsPerHour
seconds = seconds - (t.hour * secondsPerHour)
t.min = seconds // secondsPerMinute
t.sec = seconds - (t.min * secondsPerMinute)
return t
end
local tmp = {}
function M.format(sec, utc)
if utc then
M.date(sec, tmp)
else
M.localdate(sec, nil, tmp)
end
return string.format("%4d-%02d-%02d %02d:%02d:%02d",
tmp.year, tmp.month, tmp.day, tmp.hour, tmp.min, tmp.sec)
end
-- test functions
--
function M.run_random_test()
for i=1, 100 do
local ts = math.random(0, 2^31)
local t1 = os.date("!*t", ts)
local t2 = M.date(ts)
for k,v in pairs(t1) do
if k ~= "isdst" then
assert(v == t2[k], k)
end
end
end
end
function M.run_full_test()
local total = 2^31
for i=1, total do
local t1 = os.date("!*t", i)
local t2 = M.date(i, tmp)
for k,v in pairs(t1) do
if k ~= "isdst" then
assert(v == t2[k], k)
end
end
if i % 100000 == 0 then
print("------------------------:", total, i)
end
end
end
function M.run_profile_test(times)
local times = times and times > 0 or 1000000
local from = math.random(2^31 - times)
local to = from + times
local t1 = os.clock()
for ts = from, to do
os.date("!*t", ts)
end
local t = {}
local t2 = os.clock()
for ts = from, to do
M.date(ts, t)
end
local t3 = os.clock()
print(string.format("%d times call %s cost %s seconds", times, "os.date", t2-t1))
print(string.format("%d times call %s cost %s seconds", times, "M.date", t3-t2))
end
return M
@raindylong
Copy link

我是@xjdrew的脑残粉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment