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