--- /dev/null
+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;
+}