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