lua-users home
lua-l archive

Re: date arithmetic?

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


PA wrote:
How does one do date arithmetic in Lua?
Some time ago, I save Philipp Janda's date class, and slightly improved it. I didn't used it much since then, but I keep it around just in case :-)
Here is my version, the link to Philipp's home page is inside.
I send it to the list, because I believe it can be of interest for other people. The file is quite small.
HTH.
--
Philippe Lhoste
-- (near) Paris -- France
-- http://Phi.Lho.free.fr
-- -- -- -- -- -- -- -- -- -- -- -- -- --
-- Simple class for date/time calculations in Lua 5.0
-- author: Philipp Janda <philipp.janda@web.de>
-- http://www.ratnet.stw.uni-erlangen.de/~siphjand/lua50/date.lua
--
-- The algorithms for date calculations were from here:
-- http://home.capecod.net/~pbaum/date/date0.htm
-- (PL) which is now located there:
-- http://vsg.cape.com/~pbaum/date/date0.htm
--
-- Some minor improvements (?) by Philippe Lhoste:
-- - Accepts '/' as date separator and use it in the default __tostring metamethod.
-- - Weekday member becomes pure number, it is up to the user to convert it
-- to string using its own, localized, table.
-- Usage:
-- Create a date object using e.g.:
-- local d1 = date:parse("31-12-2000")
-- local d2 = date:parse("29_2_2000/10:52:44")
-- local d3 = date:parse("01.10.1582 11:11:11")
-- local d4 = date:now()
-- -- ...
-- Date delimiters can be `/', `.', `_' or `-'. The only allowed time
-- delimiter is `:'. Between date and time strings there must be
-- exactly one (arbitrary) character.
--
-- You can output the date using:
-- print(d1)
--
-- This is mainly for debugging. If you need finer control, you
-- will probably use the member variables of the date object to
-- do your own formatting:
-- print(d1.weekday, d1.day, d1.month, d1.year)
-- print(d1.hour, d1.minute, d1.second)
--
-- You can do date calculations assigning to the members of the
-- date objects. E.g.:
-- d1.day = d1.day + 10 -- add 10 days to date d1
-- d1.year = d1.year - 20 -- 20 years ago...
-- -- ...
-- Note that you cannot assign to the weekday member of date objects!
-- You can also calculate the number of seconds between two dates:
-- local nsecs = d1 - d2
-- Unresolved issues:
-- There are some unintuitive behaviours when subtracting months
-- (or maybe even leap years), e.g.:
-- local d = date:parse("31.3.2003")
-- d.month = d.month - 1
-- print(d)
-- --> 03.03.2003/00:00:00
-- 31.03.2003 minus one month is the 31.02.2003, but this date
-- doesn't exist, so we get 3 days after the 28th of february, which
-- is 03.03.2003!
-- This isn't beautiful, but kind of logic. I don't known if I should
-- change this behaviour since it is kind of implicit in the
-- calculation formulas.
local Public, Private, Meta = {}, {}, {}
date = Public
----------------------------------------------------------------------
-- Public calculation functions
function Public.gregorian2daynumber(d, m, y)
 if m < 3 then
 m = m + 12
 y = y - 1
 end
 local a = math.floor((153*m - 457) / 5)
 local b = math.floor(y / 4)
 local c = math.floor(y / 100)
 local e = math.floor(y / 400)
 return d + a + 365*y + b - c + e + 1721118.5
end
function Public.daynumber2gregorian(jdn)
 local temp = jdn - 1721118.5
 local z = math.floor(temp)
 local r = temp - z
 local g = z - 0.25
 local a = math.floor(g / 36524.25)
 local b = a - math.floor(a / 4)
 local year = math.floor((b + g) / 365.25)
 local c = b + z - math.floor( 365.25 * year)
 local month = math.trunc((5*c + 456) / 153)
 local day = c - math.trunc((153*month - 457) / 5) + r
 if month > 12 then
 year = year + 1
 month = month - 12
 end
 return day, month, year
end
function Public.julian2daynumber(d, m, y)
 if m < 3 then
 m = m + 12
 y = y - 1
 end
 local a = math.floor((153*m - 457) / 5)
 local b = math.floor(y / 4)
 return d + a + 365*y + b + 1721116.5
end
function Public.daynumber2julian(jdn)
 local temp = jdn - 1721116.5
 local z = math.floor(temp)
 local r = temp - z
 local year = math.floor((z - 0.25) / 365.25)
 local c = z - math.floor(365.25 * year)
 local month = math.trunc((5*c + 456) / 153)
 local day = c - math.trunc((153*month - 457) / 5) + r
 if month > 12 then
 year = year + 1
 month = month - 12
 end
 return day, month, year
end
function Public.date2daynumber(day, month, year)
 if year > 1582 or
 (year == 1582 and month > 10) or
 (year == 1582 and month == 10 and day > 14) then
 return Public.gregorian2daynumber(day, month, year)
 else
 return Public.julian2daynumber(day, month, year)
 end
end
function Public.daynumber2date(jdn)
 local day, month, year
 if jdn > 2299160 then
 day, month, year = Public.daynumber2gregorian(jdn)
 else
 day, month, year = Public.daynumber2julian(jdn)
 end
 return day, month, year
end
----------------------------------------------------------------------
-- Private calculation functions
local seconds_per_minute = 60
local seconds_per_hour = 60 * seconds_per_minute
local seconds_per_day = 24 * seconds_per_hour
--Private.weekday = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }
function Private.update(data)
 local ticks = data.ticks
 local jdn = ticks / seconds_per_day
 jdn_int = math.floor(jdn)
 local jdn_midnight
 if jdn - jdn_int >= 0.5 then
 jdn_midnight = jdn_int + 0.5
 else
 jdn_midnight = jdn_int - 0.5
 end
 local second = ticks - jdn_midnight*seconds_per_day
 -- compute the time since midnight
 local hour = math.floor(second / seconds_per_hour)
 second = second - hour*seconds_per_hour
 local minute = math.floor(second / seconds_per_minute)
 second = second - minute*seconds_per_minute
 -- compute the date
 local day, month, year = Public.daynumber2date(jdn_midnight)
 -- set the values for the DateTime object
 data.second = second
 data.minute = minute
 data.hour = hour
 data.day = day
 data.month = month
 data.year = year
-- data.weekday = Private.weekday[math.mod(jdn_midnight+1, 7)+0.5]
 data.weekday = math.floor(math.mod(jdn_midnight+1, 7)+0.5)
end
----------------------------------------------------------------------
-- the constructors
-- normal constructor
function Public:new(year, month, day, hour, minute, second)
 -- parameters
 year = tonumber(year) or 1970
 month = tonumber(month) or 1
 day = tonumber(day) or 1
 hour = tonumber(hour) or 0
 minute = tonumber(minute) or 0
 second = tonumber(second) or 0
 -- calculate seconds
 local sex = Public.date2daynumber(day, month, year) * seconds_per_day
 sex = sex + hour*seconds_per_hour + minute*seconds_per_minute + second
 local obj = {}
 local data = {
 ticks = sex,
 }
 -- compute the missing fields in data table
 Private.update(data)
 local meta = {
 -- metamethods
 __index = data, -- just return the data (or methods...)
 __newindex = Meta.__newindex,
 __tostring = Meta.__tostring,
 __sub = Meta.__sub,
 __le = Meta.__le,
 __lt = Meta.__lt,
 __eq = Meta.__eq
 }
 setmetatable(obj, meta)
 return obj
end
-- parses a given date string and creates a DateTime object
function Public:parse(datestr)
 local year, month, day, hour, minute, second, i, j, _
 local datepattern = "^(%d+)[%.%-%/_](%d+)[%.%-%/_](%d+)"
 local timepattern = "^(%d+):(%d+):(%d+)"
 -- parse date
 i, j, day, month, year = string.find(datestr, datepattern)
 if not i then -- no date found, assume today
 _, _, day, month, year = string.find(
 os.date("%d.%m.%Y"),
 datepattern
 )
 j = 1
 else
 j = j + 2 -- skip delimiter
 end
 -- parse time
 _, _, hour, minute, second = string.find(datestr, timepattern, j)
 if not hour then
 second = 0
 _, _, hour, minute = string.find(datestr, "^(%d+):(%d+)$", j)
 end
 return Public.new(self, year, month, day, hour, minute, second)
end
-- makes a DateTime object from the current date and time
function Public:now()
 local year, month, day, hour, minute, second, _
 local patt = "^(%d+)%.(%d+)%.(%d+)/(%d+):(%d+):(%d+)$"
 _, _, day, month, year, hour, minute, second = string.find(
 os.date("%d.%m.%Y/%H:%M:%S"),
 patt
 )
 return Public.new(self, year, month, day, hour, minute, second)
end
----------------------------------------------------------------------
-- some member functions
-- return a string representation
function Meta.__tostring(self)
 local data = getmetatable(self).__index
 return string.format("%02d/%02d/%04d %02d:%02d:%02d",
 data.day, data.month, data.year,
 data.hour, data.minute, data.second)
end
-- compute the time difference in seconds...
function Meta.__sub(a, b)
 if type(a) == "table" and type(a.ticks) == "number" and
 type(b) == "table" and type(b.ticks) == "number" then
 return a.ticks - b.ticks
 else
 error("can only subtract Date objects")
 end
end
-- compare two Date/Time objects
function Meta.__le(a, b)
 if type(a) == "table" and type(a.ticks) == "number" and
 type(b) == "table" and type(b.ticks) == "number" then
 return a.ticks <= b.ticks
 else
 error("can only compare two Date objects")
 end
end
-- compare two Date/Time objects
function Meta.__lt(a, b)
 if type(a) == "table" and type(a.ticks) == "number" and
 type(b) == "table" and type(b.ticks) == "number" then
 return a.ticks < b.ticks
 else
 error("can only compare two Date objects")
 end
end
-- compare two Date/Time objects
function Meta.__eq(a, b)
 if type(a) == "table" and type(a.ticks) == "number" and
 type(b) == "table" and type(b.ticks) == "number" then
 return a.ticks == b.ticks
 else
 error("can only compare two Date objects")
 end
end
-- set a field, but update the ticks counter and all fields
function Meta.__newindex(self, key, value)
 local data = getmetatable(self).__index
 if key == "ticks" then
 data.ticks = value
 elseif key == "second" then
 data.ticks = data.ticks + (value - data.second)
 elseif key == "minute" then
 data.ticks = data.ticks + (value - data.minute)*seconds_per_minute
 elseif key == "hour" then
 data.ticks = data.ticks + (value - data.hour)*seconds_per_hour
 elseif key == "day" then
 data.ticks = data.ticks + (value - data.day)*seconds_per_day
 elseif key == "month" then
 local hour, minute, second = data.hour, data.minute, data.second
 local day, month, year = data.day, value, data.year
 local addyears = math.floor(month / 12)
 month = month - addyears*12
 year = year + addyears
 if month == 0 then
 year = year - 1
 month = 12
 end
 local sex = Public.date2daynumber(day, month, year) * seconds_per_day
 sex = sex + hour*seconds_per_hour + minute*seconds_per_minute + second
 data.ticks = sex
 elseif key == "year" then
 local day, month, year = data.day, data.month, value
 local hour, minute, second = data.hour, data.minute, data.second
 local sex = Public.date2daynumber(day, month, year) * seconds_per_day
 sex = sex + hour*seconds_per_hour + minute*seconds_per_minute + second
 data.ticks = sex
 elseif key == "weekday" then
 error("cannot set weekday for Date object")
 else
 rawset(self, key, value)
 return
 end
 Private.update(data)
end
----------------------------------------------------------------------
-- a helper function for the math library
function math.trunc(number)
 if number < 0 then
 return -math.floor(-number)
 else
 return math.floor(number)
 end
end

AltStyle によって変換されたページ (->オリジナル) /