local gettime = require "luatz.gettime".gettime local timetable_mt = require "luatz.timetable".timetable_mt local function to_timestamp(o) if type(o) == "number" then return o elseif getmetatable(o) == timetable_mt then return o:timestamp() end end local tz_info_methods = { } local tz_info_mt = { __name = "luatz.tz_info"; __index = tz_info_methods; } local tt_info_mt = { __name = "luatz.tt_info"; __tostring = function(self) return string.format("tt_info:%s=%d", self.abbr, self.gmtoff) end; } -- Binary search local function find_current(tzinfo, target, i, j) if i >= j then return j end local half = math.ceil((j+i) / 2) if target >= tzinfo[half].transition_time then return find_current(tzinfo, target, half, j) else return find_current(tzinfo, target, i, half-1) end end local function find_current_local(tzinfo, ts_local) -- Find two best possibilities by searching back and forward a day (assumes transition is never by more than 24 hours) local tz_first = find_current(tzinfo, ts_local-86400, 0, #tzinfo) local tz_last = find_current(tzinfo, ts_local+86400, 0, #tzinfo) local n_candidates = tz_last - tz_first + 1 if n_candidates == 1 then return tz_first elseif n_candidates == 2 then local tz_first_ob = tzinfo[tz_first] local tz_last_ob = tzinfo[tz_last] local first_gmtoffset = tz_first_ob.info.gmtoff local last_gmtoffset = tz_last_ob .info.gmtoff local t_start = tz_last_ob.transition_time + first_gmtoffset local t_end = tz_last_ob.transition_time + last_gmtoffset -- If timestamp is before start or after end if ts_local < t_start then return tz_first elseif ts_local > t_end then return tz_last end -- If we get this far, the local time is ambiguous return tz_first, tz_last else error("Too many transitions in a 2 day period") end end function tz_info_methods:find_current(current) current = assert(to_timestamp(current), "invalid timestamp to :find_current") return self[find_current(self, current, 0, #self)].info end function tz_info_methods:localise(utc_ts) utc_ts = utc_ts or gettime() return utc_ts + self:find_current(utc_ts).gmtoff end tz_info_methods.localize = tz_info_methods.localise function tz_info_methods:utctime(ts_local) ts_local = assert(to_timestamp(ts_local), "invalid timestamp to :utctime") local tz1, tz2 = find_current_local(self, ts_local) tz1 = self[tz1].info if tz2 == nil then return ts_local - tz1.gmtoff else -- Local time is ambiguous tz2 = self[tz2].info return ts_local - tz2.gmtoff, ts_local - tz2.gmtoff end end return { tz_info_mt = tz_info_mt; tt_info_mt = tt_info_mt; }