]> git.madduck.net Git - etc/awesome.git/blob - luatz/tzfile.lua

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

luatz/parse.lua: Pass base to tonumber explicitly
[etc/awesome.git] / luatz / tzfile.lua
1 local tz_info_mt = require "luatz.tzinfo".tz_info_mt
2 local tt_info_mt = require "luatz.tzinfo".tt_info_mt
3
4 local read_int32be, read_int64be
5
6 -- luacheck: push std max
7 if string.unpack then
8         -- Only available in Lua 5.3+
9         function read_int32be(fd)
10                 local data, err = fd:read(4)
11                 if data == nil then return nil, err end
12                 return string.unpack(">i4", data)
13         end
14
15         function read_int64be(fd)
16                 local data, err = fd:read(8)
17                 if data == nil then return nil, err end
18                 return string.unpack(">i8", data)
19         end
20 else -- luacheck: pop
21         function read_int32be(fd)
22                 local data, err = fd:read(4)
23                 if data == nil then return nil, err end
24                 local o1, o2, o3, o4 = data:byte(1, 4)
25
26                 local unsigned = o4 + o3*2^8 + o2*2^16 + o1*2^24
27                 if unsigned >= 2^31 then
28                         return unsigned - 2^32
29                 else
30                         return unsigned
31                 end
32         end
33
34         function read_int64be(fd)
35                 local data, err = fd:read(8)
36                 if data == nil then return nil, err end
37                 local o1, o2, o3, o4, o5, o6, o7, o8 = data:byte(1, 8)
38
39                 local unsigned = o8 + o7*2^8 + o6*2^16 + o5*2^24 + o4*2^32 + o3*2^40 + o2*2^48 + o1*2^56
40                 if unsigned >= 2^63 then
41                         return unsigned - 2^64
42                 else
43                         return unsigned
44                 end
45         end
46 end
47
48 local function read_flags(fd, n)
49         local data, err = fd:read(n)
50         if data == nil then return nil, err end
51
52         local res = {}
53         for i=1, n do
54                 res[i] = data:byte(i,i) ~= 0
55         end
56         return res
57 end
58
59 local fifteen_nulls = ("\0"):rep(15)
60 local function read_tz(fd)
61         assert(fd:read(4) == "TZif", "Invalid TZ file")
62         local version = assert(fd:read(1))
63         if version == "\0" or version == "2" or version == "3" then
64                 local MIN_TIME = -2^32+1
65
66                 assert(assert(fd:read(15)) == fifteen_nulls, "Expected 15 nulls")
67
68                 -- The number of UTC/local indicators stored in the file.
69                 local tzh_ttisgmtcnt = assert(read_int32be(fd))
70
71                 -- The number of standard/wall indicators stored in the file.
72                 local tzh_ttisstdcnt = assert(read_int32be(fd))
73
74                 -- The number of leap seconds for which data is stored in the file.
75                 local tzh_leapcnt = assert(read_int32be(fd))
76
77                 -- The number of "transition times" for which data is stored in the file.
78                 local tzh_timecnt = assert(read_int32be(fd))
79
80                 -- The number of "local time types" for which data is stored in the file (must not be zero).
81                 local tzh_typecnt = assert(read_int32be(fd))
82
83                 -- The number of characters of "timezone abbreviation strings" stored in the file.
84                 local tzh_charcnt = assert(read_int32be(fd))
85
86                 local transition_times = {}
87                 for i=1, tzh_timecnt do
88                         transition_times[i] = assert(read_int32be(fd))
89                 end
90                 local transition_time_ind = {assert(fd:read(tzh_timecnt)):byte(1, -1)}
91
92                 local ttinfos = {}
93                 for i=1, tzh_typecnt do
94                         ttinfos[i] = {
95                                 gmtoff = assert(read_int32be(fd));
96                                 isdst  = assert(fd:read(1)) ~= "\0";
97                                 abbrind = assert(fd:read(1)):byte();
98                         }
99                 end
100
101                 local abbreviations = assert(fd:read(tzh_charcnt))
102
103                 local leap_seconds = {} -- luacheck: ignore 241
104                 for i=1, tzh_leapcnt do
105                         leap_seconds[i] = {
106                                 offset = assert(read_int32be(fd));
107                                 n = assert(read_int32be(fd));
108                         }
109                 end
110
111                 local isstd = assert(read_flags(fd, tzh_ttisstdcnt))
112
113                 local isgmt = assert(read_flags(fd, tzh_ttisgmtcnt))
114
115                 local TZ
116
117                 if version == "2" or version == "3" then
118                         --[[
119                         For version-2-format timezone files, the above header and data is followed by a second header and data,
120                         identical in format except that eight bytes are used for each transition time or leap-second time.
121                         ]]
122                         assert(fd:read(4) == "TZif")
123                         assert(fd:read(1) == version)
124                         assert(assert(fd:read(15)) == fifteen_nulls, "Expected 15 nulls")
125
126                         MIN_TIME = -2^64+1
127
128                         -- The number of UTC/local indicators stored in the file.
129                         tzh_ttisgmtcnt = assert(read_int32be(fd))
130
131                         -- The number of standard/wall indicators stored in the file.
132                         tzh_ttisstdcnt = assert(read_int32be(fd))
133
134                         -- The number of leap seconds for which data is stored in the file.
135                         tzh_leapcnt = assert(read_int32be(fd))
136
137                         -- The number of "transition times" for which data is stored in the file.
138                         tzh_timecnt = assert(read_int32be(fd))
139
140                         -- The number of "local time types" for which data is stored in the file (must not be zero).
141                         tzh_typecnt = assert(read_int32be(fd))
142
143                         -- The number of characters of "timezone abbreviation strings" stored in the file.
144                         tzh_charcnt = assert(read_int32be(fd))
145
146                         transition_times = {}
147                         for i=1, tzh_timecnt do
148                                 transition_times[i] = assert(read_int64be(fd))
149                         end
150                         transition_time_ind = {assert(fd:read(tzh_timecnt)):byte(1, -1)}
151
152                         ttinfos = {}
153                         for i=1, tzh_typecnt do
154                                 ttinfos[i] = {
155                                         gmtoff = assert(read_int32be(fd));
156                                         isdst  = assert(fd:read(1)) ~= "\0";
157                                         abbrind = assert(fd:read(1)):byte();
158                                 }
159                         end
160
161                         abbreviations = assert(fd:read(tzh_charcnt))
162
163                         leap_seconds = {}
164                         for i=1, tzh_leapcnt do
165                                 leap_seconds[i] = {
166                                         offset = assert(read_int64be(fd));
167                                         n = assert(read_int32be(fd));
168                                 }
169                         end
170
171                         isstd = assert(read_flags(fd, tzh_ttisstdcnt))
172
173                         isgmt = assert(read_flags(fd, tzh_ttisgmtcnt))
174
175                         --[[
176                         After the second header and data comes a newline-enclosed, POSIX-TZ-environment-variable-style string
177                         for use in handling instants after the last transition time stored in the file
178                         (with nothing between the newlines if there is no POSIX representation for such instants).
179                         ]]
180
181                         --[[
182                         For version-3-format time zone files, the POSIX-TZ-style string may
183                         use two minor extensions to the POSIX TZ format, as described in newtzset (3).
184                         First, the hours part of its transition times may be signed and range from
185                         -167 through 167 instead of the POSIX-required unsigned values
186                         from 0 through 24.  Second, DST is in effect all year if it starts
187                         January 1 at 00:00 and ends December 31 at 24:00 plus the difference
188                         between daylight saving and standard time.
189                         ]]
190
191                         assert(assert(fd:read(1)) == "\n", "Expected newline at end of version 2 header")
192
193                         TZ = assert(fd:read("*l"))
194                         if #TZ == 0 then
195                                 TZ = nil
196                         end
197                 end
198
199                 for i=1, tzh_typecnt do
200                         local v = ttinfos[i]
201                         v.abbr = abbreviations:sub(v.abbrind+1, v.abbrind+3)
202                         v.isstd = isstd[i] or false
203                         v.isgmt = isgmt[i] or false
204                         setmetatable(v, tt_info_mt)
205                 end
206
207                 --[[
208                 Use the first standard-time ttinfo structure in the file
209                 (or simply the first ttinfo structure in the absence of a standard-time structure)
210                 if either tzh_timecnt is zero or the time argument is less than the first transition time recorded in the file.
211                 ]]
212                 local first = 1
213                 do
214                         for i=1, tzh_ttisstdcnt do
215                                 if isstd[i] then
216                                         first = i
217                                         break
218                                 end
219                         end
220                 end
221
222                 local res = {
223                         future = TZ;
224                         [0] = {
225                                 transition_time = MIN_TIME;
226                                 info = ttinfos[first];
227                         }
228                 }
229                 for i=1, tzh_timecnt do
230                         res[i] = {
231                                 transition_time = transition_times[i];
232                                 info = ttinfos[transition_time_ind[i]+1];
233                         }
234                 end
235                 return setmetatable(res, tz_info_mt)
236         else
237                 error("Unsupported version")
238         end
239 end
240
241 local function read_tzfile(path)
242         local fd = assert(io.open(path, "rb"))
243         local tzinfo = read_tz(fd)
244         fd:close()
245         return tzinfo
246 end
247
248 return {
249         read_tz = read_tz;
250         read_tzfile = read_tzfile;
251 }