// 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 timeimport (_// for linkname)// registerLoadFromEmbeddedTZData is called by the time/tzdata package,// if it is imported.////go:linkname registerLoadFromEmbeddedTZDatafunc registerLoadFromEmbeddedTZData( func(string) (string, error)) {loadFromEmbeddedTZData = }// loadFromEmbeddedTZData is used to load a specific tzdata file// from tzdata information embedded in the binary itself.// This is set when the time/tzdata package is imported,// via registerLoadFromEmbeddedTzdata.var loadFromEmbeddedTZData func(zipname string) (string, error)// 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 << 20type fileSizeError stringfunc ( fileSizeError) () string {return"time: file " + string() + " 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 ( *dataIO) ( int) []byte {iflen(.p) < { .p = nil .error = truereturnnil } := .p[0:] .p = .p[:]return}func ( *dataIO) () ( uint32, bool) { := .read(4)iflen() < 4 { .error = truereturn0, false }returnuint32([3]) | uint32([2])<<8 | uint32([1])<<16 | uint32([0])<<24, true}func ( *dataIO) () ( uint64, bool) { , := .big4() , := .big4()if ! || ! { .error = truereturn0, false }return (uint64() << 32) | uint64(), true}func ( *dataIO) () ( byte, bool) { := .read(1)iflen() < 1 { .error = truereturn0, false }return [0], true}// rest returns the rest of the data in the buffer.func ( *dataIO) () []byte { := .p .p = nilreturn}// Make a string by stopping at the first NULfunc byteString( []byte) string {if := bytealg.IndexByte(, 0); != -1 { = [:] }returnstring()}var errBadData = 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 ( string, []byte) (*Location, error) { := dataIO{, false}// 4-byte magic "TZif"if := .read(4); string() != "TZif" {returnnil, errBadData }// 1-byte version, then 15 bytes of paddingvarintvar []byteif = .read(16); len() != 16 {returnnil, errBadData } else {switch [0] {case0: = 1case'2': = 2case'3': = 3default:returnnil, errBadData } }// 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 stringsconst ( = iota )var [6]intfor := 0; < 6; ++ { , := .big4()if ! {returnnil, errBadData }ifuint32(int()) != {returnnil, errBadData } [] = int() }// 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. := falseif > 1 {// Skip the 32-bit data. := []*4 + [] + []*6 + [] + []*8 + [] + []// Skip the version 2 header that we just read. += 4 + 16 .read() = true// Read the counts again, they can differ.for := 0; < 6; ++ { , := .big4()if ! {returnnil, errBadData }ifuint32(int()) != {returnnil, errBadData } [] = int() } } := 4if { = 8 }// Transition times. := dataIO{.read([] * ), false}// Time zone indices for transition times. := .read([])// Zone info structures := dataIO{.read([] * 6), false}// Time zone abbreviations. := .read([])// Leap-second time pairs .read([] * ( + 4))// Whether tx times associated with local time types // are specified as standard time or wall time. := .read([])// Whether tx times associated with local time types // are specified as UTC or local time. := .read([])if .error { // ran out of datareturnnil, errBadData }varstring := .rest()iflen() > 2 && [0] == '\n' && [len()-1] == '\n' { = string([1 : len()-1]) }// Now we can build up a useful data structure. // First the zone information. // utcoff[4] isdst[1] nameindex[1] := []if == 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).returnnil, errBadData } := make([]zone, )for := range {varboolvaruint32if , = .big4(); ! {returnnil, errBadData }ifuint32(int()) != {returnnil, errBadData } [].offset = int(int32())varbyteif , = .byte(); ! {returnnil, errBadData } [].isDST = != 0if , = .byte(); ! || int() >= len() {returnnil, errBadData } [].name = byteString([:])ifruntime.GOOS == "aix" && len() > 8 && ([:8] == "Etc/GMT+" || [: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 != "Etc/GMT+0" {// GMT+0 is OK [].name = [4:] } } }// Now the transition time info. := make([]zoneTrans, [])for := range {varint64if ! {if , := .big4(); ! {returnnil, errBadData } else { = int64(int32()) } } else {if , := .big8(); ! {returnnil, errBadData } else { = int64() } } [].when = ifint([]) >= len() {returnnil, errBadData } [].index = []if < len() { [].isstd = [] != 0 }if < len() { [].isutc = [] != 0 } }iflen() == 0 {// Build fake transition to cover all time. // This happens in fixed locations like "Etc/GMT0". = append(, zoneTrans{when: alpha, index: 0}) }// Committed to succeed. := &Location{zone: , tx: , name: , extend: }// Fill in the cache with information about right now, // since that will be the most common lookup. , , := now()for := range {if [].when <= && (+1 == len() || < [+1].when) { .cacheStart = [].when .cacheEnd = omega .cacheZone = &.zone[[].index]if +1 < len() { .cacheEnd = [+1].when } elseif .extend != "" {// If we're at the end of the known zone transitions, // try the extend string.if , , , , , := tzset(.extend, .cacheStart, ); { .cacheStart = .cacheEnd = // Find the zone that is returned by tzset to avoid allocation if possible.if := findZone(.zone, , , ); != -1 { .cacheZone = &.zone[] } else { .cacheZone = &zone{name: ,offset: ,isDST: , } } } }break } }return , nil}func findZone( []zone, string, int, bool) int {for , := range {if .name == && .offset == && .isDST == {return } }return -1}// 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(, string) ([]byte, error) {iflen() > 4 && [len()-4:] == ".zip" {returnloadTzinfoFromZip(, ) }if != "" { = + "/" + }returnreadFile()}// 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( []byte) int {iflen() < 4 {return0 }returnint([0]) | int([1])<<8 | int([2])<<16 | int([3])<<24}// get2 returns the little-endian 16-bit value in b.func get2( []byte) int {iflen() < 2 {return0 }returnint([0]) | int([1])<<8}// loadTzinfoFromZip returns the contents of the file with the given name// in the given uncompressed zip file.func loadTzinfoFromZip(, string) ([]byte, error) { , := open()if != nil {returnnil, }deferclosefd()const ( = 0x06054b50 = 0x02014b50 = 22 = 30 = 0x04034b50 ) := make([]byte, )if := preadn(, , -); != nil || get4() != {returnnil, errors.New("corrupt zip file " + ) } := get2([10:]) := get4([12:]) := get4([16:]) = make([]byte, )if := preadn(, , ); != nil {returnnil, errors.New("corrupt zip file " + ) }for := 0; < ; ++ {// 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 //ifget4() != {break } := get2([10:]) := get4([24:]) := get2([28:]) := get2([30:]) := get2([32:]) := get4([42:]) := [46 : 46+] = [46+++:]ifstring() != {continue }if != 0 {returnnil, errors.New("unsupported compression for " + + " in " + ) }// 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 // = make([]byte, +)if := preadn(, , ); != nil ||get4() != ||get2([8:]) != ||get2([26:]) != ||string([30:30+]) != {returnnil, errors.New("corrupt zip file " + ) } = get2([28:]) = make([]byte, )if := preadn(, , +30++); != nil {returnnil, errors.New("corrupt zip file " + ) }return , nil }returnnil, 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( string, string) ([]byte, error) {iflen() >= 6 && [len()-6:] == "tzdata" {returnloadTzinfoFromTzdata(, ) }returnloadTzinfoFromDirOrZip(, )}// 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( string, []string) ( *Location, error) {for , := range { , := loadTzinfo(, )if == nil {if , = LoadLocationFromTZData(, ); == nil {return , nil } }if == nil && != syscall.ENOENT { = } }ifloadFromEmbeddedTZData != nil { , := loadFromEmbeddedTZData()if == nil {if , = LoadLocationFromTZData(, []byte()); == nil {return , nil } }if == nil && != syscall.ENOENT { = } }if , := gorootZoneSource(runtime.GOROOT()); { , := loadTzinfo(, )if == nil {if , = LoadLocationFromTZData(, ); == nil {return , nil } }if == nil && != syscall.ENOENT { = } }if != nil {returnnil, }returnnil, errors.New("unknown time zone " + )}// readFile reads and returns the content of the named file.// It is a trivial implementation of os.ReadFile, reimplemented// here to avoid depending on io/ioutil or os.// It returns an error if name exceeds maxFileSize bytes.func readFile( string) ([]byte, error) { , := open()if != nil {returnnil, }deferclosefd()var ( [4096]byte []byteint )for { , = read(, [:])if > 0 { = append(, [:]...) }if == 0 || != nil {break }iflen() > maxFileSize {returnnil, fileSizeError() } }return , }
The pages are generated with Goldsv0.6.9-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds.