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