...
Run Format

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

View as plain text