Home | History | Annotate | Download | only in time
      1 // Copyright 2016 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 the "tzdata" packed timezone file used on Android.
      6 // The format is lifted from ZoneInfoDB.java and ZoneInfo.java in
      7 // java/libcore/util in the AOSP.
      8 
      9 package time
     10 
     11 import (
     12 	"errors"
     13 	"runtime"
     14 )
     15 
     16 var tzdataPaths = []string{
     17 	"/system/usr/share/zoneinfo/tzdata",
     18 	"/data/misc/zoneinfo/current/tzdata",
     19 	runtime.GOROOT() + "/lib/time/zoneinfo.zip",
     20 }
     21 
     22 var origTzdataPaths = tzdataPaths
     23 
     24 func forceZipFileForTesting(zipOnly bool) {
     25 	tzdataPaths = make([]string, len(origTzdataPaths))
     26 	copy(tzdataPaths, origTzdataPaths)
     27 	if zipOnly {
     28 		for i := 0; i < len(tzdataPaths)-1; i++ {
     29 			tzdataPaths[i] = "/XXXNOEXIST"
     30 		}
     31 	}
     32 }
     33 
     34 func initTestingZone() {
     35 	z, err := loadLocation("America/Los_Angeles")
     36 	if err != nil {
     37 		panic("cannot load America/Los_Angeles for testing: " + err.Error())
     38 	}
     39 	z.name = "Local"
     40 	localLoc = *z
     41 }
     42 
     43 func initLocal() {
     44 	// TODO(elias.naur): getprop persist.sys.timezone
     45 	localLoc = *UTC
     46 }
     47 
     48 func loadLocation(name string) (*Location, error) {
     49 	var firstErr error
     50 	for _, path := range tzdataPaths {
     51 		var z *Location
     52 		var err error
     53 		if len(path) > 4 && path[len(path)-4:] == ".zip" {
     54 			z, err = loadZoneZip(path, name)
     55 		} else {
     56 			z, err = loadTzdataFile(path, name)
     57 		}
     58 		if err == nil {
     59 			z.name = name
     60 			return z, nil
     61 		} else if firstErr == nil && !isNotExist(err) {
     62 			firstErr = err
     63 		}
     64 	}
     65 	if firstErr != nil {
     66 		return nil, firstErr
     67 	}
     68 	return nil, errors.New("unknown time zone " + name)
     69 }
     70 
     71 func loadTzdataFile(file, name string) (*Location, error) {
     72 	const (
     73 		headersize = 12 + 3*4
     74 		namesize   = 40
     75 		entrysize  = namesize + 3*4
     76 	)
     77 	if len(name) > namesize {
     78 		return nil, errors.New(name + " is longer than the maximum zone name length (40 bytes)")
     79 	}
     80 	fd, err := open(file)
     81 	if err != nil {
     82 		return nil, err
     83 	}
     84 	defer closefd(fd)
     85 
     86 	buf := make([]byte, headersize)
     87 	if err := preadn(fd, buf, 0); err != nil {
     88 		return nil, errors.New("corrupt tzdata file " + file)
     89 	}
     90 	d := data{buf, false}
     91 	if magic := d.read(6); string(magic) != "tzdata" {
     92 		return nil, errors.New("corrupt tzdata file " + file)
     93 	}
     94 	d = data{buf[12:], false}
     95 	indexOff, _ := d.big4()
     96 	dataOff, _ := d.big4()
     97 	indexSize := dataOff - indexOff
     98 	entrycount := indexSize / entrysize
     99 	buf = make([]byte, indexSize)
    100 	if err := preadn(fd, buf, int(indexOff)); err != nil {
    101 		return nil, errors.New("corrupt tzdata file " + file)
    102 	}
    103 	for i := 0; i < int(entrycount); i++ {
    104 		entry := buf[i*entrysize : (i+1)*entrysize]
    105 		// len(name) <= namesize is checked at function entry
    106 		if string(entry[:len(name)]) != name {
    107 			continue
    108 		}
    109 		d := data{entry[namesize:], false}
    110 		off, _ := d.big4()
    111 		size, _ := d.big4()
    112 		buf := make([]byte, size)
    113 		if err := preadn(fd, buf, int(off+dataOff)); err != nil {
    114 			return nil, errors.New("corrupt tzdata file " + file)
    115 		}
    116 		return loadZoneData(buf)
    117 	}
    118 	return nil, errors.New("cannot find " + name + " in tzdata file " + file)
    119 }
    120