| // Copyright 2009 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 |  | 
 | // Parse "zoneinfo" time zone file. | 
 | // This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others. | 
 | // See tzfile(5), https://en.wikipedia.org/wiki/Zoneinfo, | 
 | // and ftp://munnari.oz.au/pub/oldtz/ | 
 |  | 
 | package time | 
 |  | 
 | import ( | 
 | 	"errors" | 
 | 	"runtime" | 
 | 	"syscall" | 
 | ) | 
 |  | 
 | // maxFileSize is the max permitted size of files read by readFile. | 
 | // As reference, the zoneinfo.zip distributed by Go is ~350 KB, | 
 | // so 10MB is overkill. | 
 | const maxFileSize = 10 << 20 | 
 |  | 
 | type fileSizeError string | 
 |  | 
 | func (f fileSizeError) Error() string { | 
 | 	return "time: file " + string(f) + " is too large" | 
 | } | 
 |  | 
 | // Copies of io.Seek* constants to avoid importing "io": | 
 | const ( | 
 | 	seekStart   = 0 | 
 | 	seekCurrent = 1 | 
 | 	seekEnd     = 2 | 
 | ) | 
 |  | 
 | // Simple I/O interface to binary blob of data. | 
 | type dataIO struct { | 
 | 	p     []byte | 
 | 	error bool | 
 | } | 
 |  | 
 | func (d *dataIO) read(n int) []byte { | 
 | 	if len(d.p) < n { | 
 | 		d.p = nil | 
 | 		d.error = true | 
 | 		return nil | 
 | 	} | 
 | 	p := d.p[0:n] | 
 | 	d.p = d.p[n:] | 
 | 	return p | 
 | } | 
 |  | 
 | func (d *dataIO) big4() (n uint32, ok bool) { | 
 | 	p := d.read(4) | 
 | 	if len(p) < 4 { | 
 | 		d.error = true | 
 | 		return 0, false | 
 | 	} | 
 | 	return uint32(p[3]) | uint32(p[2])<<8 | uint32(p[1])<<16 | uint32(p[0])<<24, true | 
 | } | 
 |  | 
 | func (d *dataIO) big8() (n uint64, ok bool) { | 
 | 	n1, ok1 := d.big4() | 
 | 	n2, ok2 := d.big4() | 
 | 	if !ok1 || !ok2 { | 
 | 		d.error = true | 
 | 		return 0, false | 
 | 	} | 
 | 	return (uint64(n1) << 32) | uint64(n2), true | 
 | } | 
 |  | 
 | func (d *dataIO) byte() (n byte, ok bool) { | 
 | 	p := d.read(1) | 
 | 	if len(p) < 1 { | 
 | 		d.error = true | 
 | 		return 0, false | 
 | 	} | 
 | 	return p[0], true | 
 | } | 
 |  | 
 | // Make a string by stopping at the first NUL | 
 | func byteString(p []byte) string { | 
 | 	for i := 0; i < len(p); i++ { | 
 | 		if p[i] == 0 { | 
 | 			return string(p[0:i]) | 
 | 		} | 
 | 	} | 
 | 	return string(p) | 
 | } | 
 |  | 
 | var badData = errors.New("malformed time zone information") | 
 |  | 
 | // LoadLocationFromTZData returns a Location with the given name | 
 | // initialized from the IANA Time Zone database-formatted data. | 
 | // The data should be in the format of a standard IANA time zone file | 
 | // (for example, the content of /etc/localtime on Unix systems). | 
 | func LoadLocationFromTZData(name string, data []byte) (*Location, error) { | 
 | 	d := dataIO{data, false} | 
 |  | 
 | 	// 4-byte magic "TZif" | 
 | 	if magic := d.read(4); string(magic) != "TZif" { | 
 | 		return nil, badData | 
 | 	} | 
 |  | 
 | 	// 1-byte version, then 15 bytes of padding | 
 | 	var version int | 
 | 	var p []byte | 
 | 	if p = d.read(16); len(p) != 16 { | 
 | 		return nil, badData | 
 | 	} else { | 
 | 		switch p[0] { | 
 | 		case 0: | 
 | 			version = 1 | 
 | 		case '2': | 
 | 			version = 2 | 
 | 		case '3': | 
 | 			version = 3 | 
 | 		default: | 
 | 			return nil, badData | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// six big-endian 32-bit integers: | 
 | 	//	number of UTC/local indicators | 
 | 	//	number of standard/wall indicators | 
 | 	//	number of leap seconds | 
 | 	//	number of transition times | 
 | 	//	number of local time zones | 
 | 	//	number of characters of time zone abbrev strings | 
 | 	const ( | 
 | 		NUTCLocal = iota | 
 | 		NStdWall | 
 | 		NLeap | 
 | 		NTime | 
 | 		NZone | 
 | 		NChar | 
 | 	) | 
 | 	var n [6]int | 
 | 	for i := 0; i < 6; i++ { | 
 | 		nn, ok := d.big4() | 
 | 		if !ok { | 
 | 			return nil, badData | 
 | 		} | 
 | 		if uint32(int(nn)) != nn { | 
 | 			return nil, badData | 
 | 		} | 
 | 		n[i] = int(nn) | 
 | 	} | 
 |  | 
 | 	// If we have version 2 or 3, then the data is first written out | 
 | 	// in a 32-bit format, then written out again in a 64-bit format. | 
 | 	// Skip the 32-bit format and read the 64-bit one, as it can | 
 | 	// describe a broader range of dates. | 
 |  | 
 | 	is64 := false | 
 | 	if version > 1 { | 
 | 		// Skip the 32-bit data. | 
 | 		skip := n[NTime]*4 + | 
 | 			n[NTime] + | 
 | 			n[NZone]*6 + | 
 | 			n[NChar] + | 
 | 			n[NLeap]*8 + | 
 | 			n[NStdWall] + | 
 | 			n[NUTCLocal] | 
 | 		// Skip the version 2 header that we just read. | 
 | 		skip += 4 + 16 | 
 | 		d.read(skip) | 
 |  | 
 | 		is64 = true | 
 |  | 
 | 		// Read the counts again, they can differ. | 
 | 		for i := 0; i < 6; i++ { | 
 | 			nn, ok := d.big4() | 
 | 			if !ok { | 
 | 				return nil, badData | 
 | 			} | 
 | 			if uint32(int(nn)) != nn { | 
 | 				return nil, badData | 
 | 			} | 
 | 			n[i] = int(nn) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	size := 4 | 
 | 	if is64 { | 
 | 		size = 8 | 
 | 	} | 
 |  | 
 | 	// Transition times. | 
 | 	txtimes := dataIO{d.read(n[NTime] * size), false} | 
 |  | 
 | 	// Time zone indices for transition times. | 
 | 	txzones := d.read(n[NTime]) | 
 |  | 
 | 	// Zone info structures | 
 | 	zonedata := dataIO{d.read(n[NZone] * 6), false} | 
 |  | 
 | 	// Time zone abbreviations. | 
 | 	abbrev := d.read(n[NChar]) | 
 |  | 
 | 	// Leap-second time pairs | 
 | 	d.read(n[NLeap] * (size + 4)) | 
 |  | 
 | 	// Whether tx times associated with local time types | 
 | 	// are specified as standard time or wall time. | 
 | 	isstd := d.read(n[NStdWall]) | 
 |  | 
 | 	// Whether tx times associated with local time types | 
 | 	// are specified as UTC or local time. | 
 | 	isutc := d.read(n[NUTCLocal]) | 
 |  | 
 | 	if d.error { // ran out of data | 
 | 		return nil, badData | 
 | 	} | 
 |  | 
 | 	// Now we can build up a useful data structure. | 
 | 	// First the zone information. | 
 | 	//	utcoff[4] isdst[1] nameindex[1] | 
 | 	nzone := n[NZone] | 
 | 	if nzone == 0 { | 
 | 		// Reject tzdata files with no zones. There's nothing useful in them. | 
 | 		// This also avoids a panic later when we add and then use a fake transition (golang.org/issue/29437). | 
 | 		return nil, badData | 
 | 	} | 
 | 	zone := make([]zone, nzone) | 
 | 	for i := range zone { | 
 | 		var ok bool | 
 | 		var n uint32 | 
 | 		if n, ok = zonedata.big4(); !ok { | 
 | 			return nil, badData | 
 | 		} | 
 | 		if uint32(int(n)) != n { | 
 | 			return nil, badData | 
 | 		} | 
 | 		zone[i].offset = int(int32(n)) | 
 | 		var b byte | 
 | 		if b, ok = zonedata.byte(); !ok { | 
 | 			return nil, badData | 
 | 		} | 
 | 		zone[i].isDST = b != 0 | 
 | 		if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) { | 
 | 			return nil, badData | 
 | 		} | 
 | 		zone[i].name = byteString(abbrev[b:]) | 
 | 		if runtime.GOOS == "aix" && len(name) > 8 && (name[:8] == "Etc/GMT+" || name[:8] == "Etc/GMT-") { | 
 | 			// There is a bug with AIX 7.2 TL 0 with files in Etc, | 
 | 			// GMT+1 will return GMT-1 instead of GMT+1 or -01. | 
 | 			if name != "Etc/GMT+0" { | 
 | 				// GMT+0 is OK | 
 | 				zone[i].name = name[4:] | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// Now the transition time info. | 
 | 	tx := make([]zoneTrans, n[NTime]) | 
 | 	for i := range tx { | 
 | 		var n int64 | 
 | 		if !is64 { | 
 | 			if n4, ok := txtimes.big4(); !ok { | 
 | 				return nil, badData | 
 | 			} else { | 
 | 				n = int64(int32(n4)) | 
 | 			} | 
 | 		} else { | 
 | 			if n8, ok := txtimes.big8(); !ok { | 
 | 				return nil, badData | 
 | 			} else { | 
 | 				n = int64(n8) | 
 | 			} | 
 | 		} | 
 | 		tx[i].when = n | 
 | 		if int(txzones[i]) >= len(zone) { | 
 | 			return nil, badData | 
 | 		} | 
 | 		tx[i].index = txzones[i] | 
 | 		if i < len(isstd) { | 
 | 			tx[i].isstd = isstd[i] != 0 | 
 | 		} | 
 | 		if i < len(isutc) { | 
 | 			tx[i].isutc = isutc[i] != 0 | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if len(tx) == 0 { | 
 | 		// Build fake transition to cover all time. | 
 | 		// This happens in fixed locations like "Etc/GMT0". | 
 | 		tx = append(tx, zoneTrans{when: alpha, index: 0}) | 
 | 	} | 
 |  | 
 | 	// Committed to succeed. | 
 | 	l := &Location{zone: zone, tx: tx, name: name} | 
 |  | 
 | 	// Fill in the cache with information about right now, | 
 | 	// since that will be the most common lookup. | 
 | 	sec, _, _ := now() | 
 | 	for i := range tx { | 
 | 		if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) { | 
 | 			l.cacheStart = tx[i].when | 
 | 			l.cacheEnd = omega | 
 | 			if i+1 < len(tx) { | 
 | 				l.cacheEnd = tx[i+1].when | 
 | 			} | 
 | 			l.cacheZone = &l.zone[tx[i].index] | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return l, nil | 
 | } | 
 |  | 
 | // loadTzinfoFromDirOrZip returns the contents of the file with the given name | 
 | // in dir. dir can either be an uncompressed zip file, or a directory. | 
 | func loadTzinfoFromDirOrZip(dir, name string) ([]byte, error) { | 
 | 	if len(dir) > 4 && dir[len(dir)-4:] == ".zip" { | 
 | 		return loadTzinfoFromZip(dir, name) | 
 | 	} | 
 | 	if dir != "" { | 
 | 		name = dir + "/" + name | 
 | 	} | 
 | 	return readFile(name) | 
 | } | 
 |  | 
 | // There are 500+ zoneinfo files. Rather than distribute them all | 
 | // individually, we ship them in an uncompressed zip file. | 
 | // Used this way, the zip file format serves as a commonly readable | 
 | // container for the individual small files. We choose zip over tar | 
 | // because zip files have a contiguous table of contents, making | 
 | // individual file lookups faster, and because the per-file overhead | 
 | // in a zip file is considerably less than tar's 512 bytes. | 
 |  | 
 | // get4 returns the little-endian 32-bit value in b. | 
 | func get4(b []byte) int { | 
 | 	if len(b) < 4 { | 
 | 		return 0 | 
 | 	} | 
 | 	return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24 | 
 | } | 
 |  | 
 | // get2 returns the little-endian 16-bit value in b. | 
 | func get2(b []byte) int { | 
 | 	if len(b) < 2 { | 
 | 		return 0 | 
 | 	} | 
 | 	return int(b[0]) | int(b[1])<<8 | 
 | } | 
 |  | 
 | // loadTzinfoFromZip returns the contents of the file with the given name | 
 | // in the given uncompressed zip file. | 
 | func loadTzinfoFromZip(zipfile, name string) ([]byte, error) { | 
 | 	fd, err := open(zipfile) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	defer closefd(fd) | 
 |  | 
 | 	const ( | 
 | 		zecheader = 0x06054b50 | 
 | 		zcheader  = 0x02014b50 | 
 | 		ztailsize = 22 | 
 |  | 
 | 		zheadersize = 30 | 
 | 		zheader     = 0x04034b50 | 
 | 	) | 
 |  | 
 | 	buf := make([]byte, ztailsize) | 
 | 	if err := preadn(fd, buf, -ztailsize); err != nil || get4(buf) != zecheader { | 
 | 		return nil, errors.New("corrupt zip file " + zipfile) | 
 | 	} | 
 | 	n := get2(buf[10:]) | 
 | 	size := get4(buf[12:]) | 
 | 	off := get4(buf[16:]) | 
 |  | 
 | 	buf = make([]byte, size) | 
 | 	if err := preadn(fd, buf, off); err != nil { | 
 | 		return nil, errors.New("corrupt zip file " + zipfile) | 
 | 	} | 
 |  | 
 | 	for i := 0; i < n; i++ { | 
 | 		// zip entry layout: | 
 | 		//	0	magic[4] | 
 | 		//	4	madevers[1] | 
 | 		//	5	madeos[1] | 
 | 		//	6	extvers[1] | 
 | 		//	7	extos[1] | 
 | 		//	8	flags[2] | 
 | 		//	10	meth[2] | 
 | 		//	12	modtime[2] | 
 | 		//	14	moddate[2] | 
 | 		//	16	crc[4] | 
 | 		//	20	csize[4] | 
 | 		//	24	uncsize[4] | 
 | 		//	28	namelen[2] | 
 | 		//	30	xlen[2] | 
 | 		//	32	fclen[2] | 
 | 		//	34	disknum[2] | 
 | 		//	36	iattr[2] | 
 | 		//	38	eattr[4] | 
 | 		//	42	off[4] | 
 | 		//	46	name[namelen] | 
 | 		//	46+namelen+xlen+fclen - next header | 
 | 		// | 
 | 		if get4(buf) != zcheader { | 
 | 			break | 
 | 		} | 
 | 		meth := get2(buf[10:]) | 
 | 		size := get4(buf[24:]) | 
 | 		namelen := get2(buf[28:]) | 
 | 		xlen := get2(buf[30:]) | 
 | 		fclen := get2(buf[32:]) | 
 | 		off := get4(buf[42:]) | 
 | 		zname := buf[46 : 46+namelen] | 
 | 		buf = buf[46+namelen+xlen+fclen:] | 
 | 		if string(zname) != name { | 
 | 			continue | 
 | 		} | 
 | 		if meth != 0 { | 
 | 			return nil, errors.New("unsupported compression for " + name + " in " + zipfile) | 
 | 		} | 
 |  | 
 | 		// zip per-file header layout: | 
 | 		//	0	magic[4] | 
 | 		//	4	extvers[1] | 
 | 		//	5	extos[1] | 
 | 		//	6	flags[2] | 
 | 		//	8	meth[2] | 
 | 		//	10	modtime[2] | 
 | 		//	12	moddate[2] | 
 | 		//	14	crc[4] | 
 | 		//	18	csize[4] | 
 | 		//	22	uncsize[4] | 
 | 		//	26	namelen[2] | 
 | 		//	28	xlen[2] | 
 | 		//	30	name[namelen] | 
 | 		//	30+namelen+xlen - file data | 
 | 		// | 
 | 		buf = make([]byte, zheadersize+namelen) | 
 | 		if err := preadn(fd, buf, off); err != nil || | 
 | 			get4(buf) != zheader || | 
 | 			get2(buf[8:]) != meth || | 
 | 			get2(buf[26:]) != namelen || | 
 | 			string(buf[30:30+namelen]) != name { | 
 | 			return nil, errors.New("corrupt zip file " + zipfile) | 
 | 		} | 
 | 		xlen = get2(buf[28:]) | 
 |  | 
 | 		buf = make([]byte, size) | 
 | 		if err := preadn(fd, buf, off+30+namelen+xlen); err != nil { | 
 | 			return nil, errors.New("corrupt zip file " + zipfile) | 
 | 		} | 
 |  | 
 | 		return buf, nil | 
 | 	} | 
 |  | 
 | 	return nil, syscall.ENOENT | 
 | } | 
 |  | 
 | // loadTzinfoFromTzdata returns the time zone information of the time zone | 
 | // with the given name, from a tzdata database file as they are typically | 
 | // found on android. | 
 | var loadTzinfoFromTzdata func(file, name string) ([]byte, error) | 
 |  | 
 | // loadTzinfo returns the time zone information of the time zone | 
 | // with the given name, from a given source. A source may be a | 
 | // timezone database directory, tzdata database file or an uncompressed | 
 | // zip file, containing the contents of such a directory. | 
 | func loadTzinfo(name string, source string) ([]byte, error) { | 
 | 	if len(source) >= 6 && source[len(source)-6:] == "tzdata" { | 
 | 		return loadTzinfoFromTzdata(source, name) | 
 | 	} | 
 | 	return loadTzinfoFromDirOrZip(source, name) | 
 | } | 
 |  | 
 | // loadLocation returns the Location with the given name from one of | 
 | // the specified sources. See loadTzinfo for a list of supported sources. | 
 | // The first timezone data matching the given name that is successfully loaded | 
 | // and parsed is returned as a Location. | 
 | func loadLocation(name string, sources []string) (z *Location, firstErr error) { | 
 | 	for _, source := range sources { | 
 | 		var zoneData, err = loadTzinfo(name, source) | 
 | 		if err == nil { | 
 | 			if z, err = LoadLocationFromTZData(name, zoneData); err == nil { | 
 | 				return z, nil | 
 | 			} | 
 | 		} | 
 | 		if firstErr == nil && err != syscall.ENOENT { | 
 | 			firstErr = err | 
 | 		} | 
 | 	} | 
 | 	if firstErr != nil { | 
 | 		return nil, firstErr | 
 | 	} | 
 | 	return nil, errors.New("unknown time zone " + name) | 
 | } | 
 |  | 
 | // readFile reads and returns the content of the named file. | 
 | // It is a trivial implementation of ioutil.ReadFile, reimplemented | 
 | // here to avoid depending on io/ioutil or os. | 
 | // It returns an error if name exceeds maxFileSize bytes. | 
 | func readFile(name string) ([]byte, error) { | 
 | 	f, err := open(name) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	defer closefd(f) | 
 | 	var ( | 
 | 		buf [4096]byte | 
 | 		ret []byte | 
 | 		n   int | 
 | 	) | 
 | 	for { | 
 | 		n, err = read(f, buf[:]) | 
 | 		if n > 0 { | 
 | 			ret = append(ret, buf[:n]...) | 
 | 		} | 
 | 		if n == 0 || err != nil { | 
 | 			break | 
 | 		} | 
 | 		if len(ret) > maxFileSize { | 
 | 			return nil, fileSizeError(name) | 
 | 		} | 
 | 	} | 
 | 	return ret, err | 
 | } |