src/pkg/time/zoneinfo_read.go - The Go Programming Language

Golang

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	}