Source file src/pkg/time/zoneinfo_read.go
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Parse "zoneinfo" time zone file.
6 // This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
7 // See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo,
8 // and ftp://munnari.oz.au/pub/oldtz/
9
10 package time
11
12 import "errors"
13
14 const (
15 headerSize = 4 + 16 + 4*7
16 )
17
18 // Simple I/O interface to binary blob of data.
19 type data struct {
20 p []byte
21 error bool
22 }
23
24 func (d *data) read(n int) []byte {
25 if len(d.p) < n {
26 d.p = nil
27 d.error = true
28 return nil
29 }
30 p := d.p[0:n]
31 d.p = d.p[n:]
32 return p
33 }
34
35 func (d *data) big4() (n uint32, ok bool) {
36 p := d.read(4)
37 if len(p) < 4 {
38 d.error = true
39 return 0, false
40 }
41 return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true
42 }
43
44 func (d *data) byte() (n byte, ok bool) {
45 p := d.read(1)
46 if len(p) < 1 {
47 d.error = true
48 return 0, false
49 }
50 return p[0], true
51 }
52
53 // Make a string by stopping at the first NUL
54 func byteString(p []byte) string {
55 for i := 0; i < len(p); i++ {
56 if p[i] == 0 {
57 return string(p[0:i])
58 }
59 }
60 return string(p)
61 }
62
63 var badData = errors.New("malformed time zone information")
64
65 func loadZoneData(bytes []byte) (l *Location, err error) {
66 d := data{bytes, false}
67
68 // 4-byte magic "TZif"
69 if magic := d.read(4); string(magic) != "TZif" {
70 return nil, badData
71 }
72
73 // 1-byte version, then 15 bytes of padding
74 var p []byte
75 if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
76 return nil, badData
77 }
78
79 // six big-endian 32-bit integers:
80 // number of UTC/local indicators
81 // number of standard/wall indicators
82 // number of leap seconds
83 // number of transition times
84 // number of local time zones
85 // number of characters of time zone abbrev strings
86 const (
87 NUTCLocal = iota
88 NStdWall
89 NLeap
90 NTime
91 NZone
92 NChar
93 )
94 var n [6]int
95 for i := 0; i < 6; i++ {
96 nn, ok := d.big4()
97 if !ok {
98 return nil, badData
99 }
100 n[i] = int(nn)
101 }
102
103 // Transition times.
104 txtimes := data{d.read(n[NTime] * 4), false}
105
106 // Time zone indices for transition times.
107 txzones := d.read(n[NTime])
108
109 // Zone info structures
110 zonedata := data{d.read(n[NZone] * 6), false}
111
112 // Time zone abbreviations.
113 abbrev := d.read(n[NChar])
114
115 // Leap-second time pairs
116 d.read(n[NLeap] * 8)
117
118 // Whether tx times associated with local time types
119 // are specified as standard time or wall time.
120 isstd := d.read(n[NStdWall])
121
122 // Whether tx times associated with local time types
123 // are specified as UTC or local time.
124 isutc := d.read(n[NUTCLocal])
125
126 if d.error { // ran out of data
127 return nil, badData
128 }
129
130 // If version == 2, the entire file repeats, this time using
131 // 8-byte ints for txtimes and leap seconds.
132 // We won't need those until 2106.
133
134 // Now we can build up a useful data structure.
135 // First the zone information.
136 // utcoff[4] isdst[1] nameindex[1]
137 zone := make([]zone, n[NZone])
138 for i := range zone {
139 var ok bool
140 var n uint32
141 if n, ok = zonedata.big4(); !ok {
142 return nil, badData
143 }
144 zone[i].offset = int(n)
145 var b byte
146 if b, ok = zonedata.byte(); !ok {
147 return nil, badData
148 }
149 zone[i].isDST = b != 0
150 if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
151 return nil, badData
152 }
153 zone[i].name = byteString(abbrev[b:])
154 }
155
156 // Now the transition time info.
157 tx := make([]zoneTrans, n[NTime])
158 for i := range tx {
159 var ok bool
160 var n uint32
161 if n, ok = txtimes.big4(); !ok {
162 return nil, badData
163 }
164 tx[i].when = int64(int32(n))
165 if int(txzones[i]) >= len(zone) {
166 return nil, badData
167 }
168 tx[i].index = txzones[i]
169 if i < len(isstd) {
170 tx[i].isstd = isstd[i] != 0
171 }
172 if i < len(isutc) {
173 tx[i].isutc = isutc[i] != 0
174 }
175 }
176
177 // Commited to succeed.
178 l = &Location{zone: zone, tx: tx}
179
180 // Fill in the cache with information about right now,
181 // since that will be the most common lookup.
182 sec, _ := now()
183 for i := range tx {
184 if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
185 l.cacheStart = tx[i].when
186 l.cacheEnd = 1<<63 - 1
187 if i+1 < len(tx) {
188 l.cacheEnd = tx[i+1].when
189 }
190 l.cacheZone = &l.zone[tx[i].index]
191 }
192 }
193
194 return l, nil
195 }
196
197 func loadZoneFile(dir, name string) (l *Location, err error) {
198 if len(dir) > 4 && dir[len(dir)-4:] == ".zip" {
199 return loadZoneZip(dir, name)
200 }
201 if dir != "" {
202 name = dir + "/" + name
203 }
204 buf, err := readFile(name)
205 if err != nil {
206 return
207 }
208 return loadZoneData(buf)
209 }
210
211 // There are 500+ zoneinfo files. Rather than distribute them all
212 // individually, we ship them in an uncompressed zip file.
213 // Used this way, the zip file format serves as a commonly readable
214 // container for the individual small files. We choose zip over tar
215 // because zip files have a contiguous table of contents, making
216 // individual file lookups faster, and because the per-file overhead
217 // in a zip file is considerably less than tar's 512 bytes.
218
219 // get4 returns the little-endian 32-bit value in b.
220 func get4(b []byte) int {
221 if len(b) < 4 {
222 return 0
223 }
224 return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24
225 }
226
227 // get2 returns the little-endian 16-bit value in b.
228 func get2(b []byte) int {
229 if len(b) < 2 {
230 return 0
231 }
232 return int(b[0]) | int(b[1])<<8
233 }
234
235 func loadZoneZip(zipfile, name string) (l *Location, err error) {
236 fd, err := open(zipfile)
237 if err != nil {
238 return nil, errors.New("open " + zipfile + ": " + err.Error())
239 }
240 defer closefd(fd)
241
242 const (
243 zecheader = 0x06054b50
244 zcheader = 0x02014b50
245 ztailsize = 22
246
247 zheadersize = 30
248 zheader = 0x04034b50
249 )
250
251 buf := make([]byte, ztailsize)
252 if err := preadn(fd, buf, -ztailsize); err != nil || get4(buf) != zecheader {
253 return nil, errors.New("corrupt zip file " + zipfile)
254 }
255 n := get2(buf[10:])
256 size := get4(buf[12:])
257 off := get4(buf[16:])
258
259 buf = make([]byte, size)
260 if err := preadn(fd, buf, off); err != nil {
261 return nil, errors.New("corrupt zip file " + zipfile)
262 }
263
264 for i := 0; i < n; i++ {
265 // zip entry layout:
266 // 0 magic[4]
267 // 4 madevers[1]
268 // 5 madeos[1]
269 // 6 extvers[1]
270 // 7 extos[1]
271 // 8 flags[2]
272 // 10 meth[2]
273 // 12 modtime[2]
274 // 14 moddate[2]
275 // 16 crc[4]
276 // 20 csize[4]
277 // 24 uncsize[4]
278 // 28 namelen[2]
279 // 30 xlen[2]
280 // 32 fclen[2]
281 // 34 disknum[2]
282 // 36 iattr[2]
283 // 38 eattr[4]
284 // 42 off[4]
285 // 46 name[namelen]
286 // 46+namelen+xlen+fclen - next header
287 //
288 if get4(buf) != zcheader {
289 break
290 }
291 meth := get2(buf[10:])
292 size := get4(buf[24:])
293 namelen := get2(buf[28:])
294 xlen := get2(buf[30:])
295 fclen := get2(buf[32:])
296 off := get4(buf[42:])
297 zname := buf[46 : 46+namelen]
298 buf = buf[46+namelen+xlen+fclen:]
299 if string(zname) != name {
300 continue
301 }
302 if meth != 0 {
303 return nil, errors.New("unsupported compression for " + name + " in " + zipfile)
304 }
305
306 // zip per-file header layout:
307 // 0 magic[4]
308 // 4 extvers[1]
309 // 5 extos[1]
310 // 6 flags[2]
311 // 8 meth[2]
312 // 10 modtime[2]
313 // 12 moddate[2]
314 // 14 crc[4]
315 // 18 csize[4]
316 // 22 uncsize[4]
317 // 26 namelen[2]
318 // 28 xlen[2]
319 // 30 name[namelen]
320 // 30+namelen+xlen - file data
321 //
322 buf = make([]byte, zheadersize+namelen)
323 if err := preadn(fd, buf, off); err != nil ||
324 get4(buf) != zheader ||
325 get2(buf[8:]) != meth ||
326 get2(buf[26:]) != namelen ||
327 string(buf[30:30+namelen]) != name {
328 return nil, errors.New("corrupt zip file " + zipfile)
329 }
330 xlen = get2(buf[28:])
331
332 buf = make([]byte, size)
333 if err := preadn(fd, buf, off+30+namelen+xlen); err != nil {
334 return nil, errors.New("corrupt zip file " + zipfile)
335 }
336
337 return loadZoneData(buf)
338 }
339
340 return nil, errors.New("cannot find " + name + " in zip file " + zipfile)
341 }