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