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.
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/
17 // maxFileSize is the max permitted size of files read by readFile.
18 // As reference, the zoneinfo.zip distributed by Go is ~350 KB,
19 // so 10MB is overkill.
20 const maxFileSize
= 10 << 20
22 type fileSizeError
string
24 func (f fileSizeError
) Error() string {
25 return "time: file " + string(f
) + " is too large"
28 // Copies of io.Seek* constants to avoid importing "io":
35 // Simple I/O interface to binary blob of data.
41 func (d
*dataIO
) read(n
int) []byte {
52 func (d
*dataIO
) big4() (n
uint32, ok
bool) {
58 return uint32(p
[0])<<24 |
uint32(p
[1])<<16 |
uint32(p
[2])<<8 |
uint32(p
[3]), true
61 func (d
*dataIO
) byte() (n
byte, ok
bool) {
70 // Make a string by stopping at the first NUL
71 func byteString(p
[]byte) string {
72 for i
:= 0; i
< len(p
); i
++ {
80 var badData
= errors
.New("malformed time zone information")
82 // LoadLocationFromTZData returns a Location with the given name
83 // initialized from the IANA Time Zone database-formatted data.
84 // The data should be in the format of a standard IANA time zone file
85 // (for example, the content of /etc/localtime on Unix systems).
86 func LoadLocationFromTZData(name
string, data
[]byte) (*Location
, error
) {
87 d
:= dataIO
{data
, false}
89 // 4-byte magic "TZif"
90 if magic
:= d
.read(4); string(magic
) != "TZif" {
94 // 1-byte version, then 15 bytes of padding
96 if p
= d
.read(16); len(p
) != 16 || p
[0] != 0 && p
[0] != '2' && p
[0] != '3' {
100 // six big-endian 32-bit integers:
101 // number of UTC/local indicators
102 // number of standard/wall indicators
103 // number of leap seconds
104 // number of transition times
105 // number of local time zones
106 // number of characters of time zone abbrev strings
116 for i
:= 0; i
< 6; i
++ {
125 txtimes
:= dataIO
{d
.read(n
[NTime
] * 4), false}
127 // Time zone indices for transition times.
128 txzones
:= d
.read(n
[NTime
])
130 // Zone info structures
131 zonedata
:= dataIO
{d
.read(n
[NZone
] * 6), false}
133 // Time zone abbreviations.
134 abbrev
:= d
.read(n
[NChar
])
136 // Leap-second time pairs
139 // Whether tx times associated with local time types
140 // are specified as standard time or wall time.
141 isstd
:= d
.read(n
[NStdWall
])
143 // Whether tx times associated with local time types
144 // are specified as UTC or local time.
145 isutc
:= d
.read(n
[NUTCLocal
])
147 if d
.error
{ // ran out of data
151 // If version == 2 or 3, the entire file repeats, this time using
152 // 8-byte ints for txtimes and leap seconds.
153 // We won't need those until 2106.
155 // Now we can build up a useful data structure.
156 // First the zone information.
157 // utcoff[4] isdst[1] nameindex[1]
158 zone
:= make([]zone
, n
[NZone
])
159 for i
:= range zone
{
162 if n
, ok
= zonedata
.big4(); !ok
{
165 zone
[i
].offset
= int(int32(n
))
167 if b
, ok
= zonedata
.byte(); !ok
{
170 zone
[i
].isDST
= b
!= 0
171 if b
, ok
= zonedata
.byte(); !ok ||
int(b
) >= len(abbrev
) {
174 zone
[i
].name
= byteString(abbrev
[b
:])
177 // Now the transition time info.
178 tx
:= make([]zoneTrans
, n
[NTime
])
182 if n
, ok
= txtimes
.big4(); !ok
{
185 tx
[i
].when
= int64(int32(n
))
186 if int(txzones
[i
]) >= len(zone
) {
189 tx
[i
].index
= txzones
[i
]
191 tx
[i
].isstd
= isstd
[i
] != 0
194 tx
[i
].isutc
= isutc
[i
] != 0
199 // Build fake transition to cover all time.
200 // This happens in fixed locations like "Etc/GMT0".
201 tx
= append(tx
, zoneTrans
{when
: alpha
, index
: 0})
204 // Committed to succeed.
205 l
:= &Location
{zone
: zone
, tx
: tx
, name
: name
}
207 // Fill in the cache with information about right now,
208 // since that will be the most common lookup.
211 if tx
[i
].when
<= sec
&& (i
+1 == len(tx
) || sec
< tx
[i
+1].when
) {
212 l
.cacheStart
= tx
[i
].when
215 l
.cacheEnd
= tx
[i
+1].when
217 l
.cacheZone
= &l
.zone
[tx
[i
].index
]
224 // loadTzinfoFromDirOrZip returns the contents of the file with the given name
225 // in dir. dir can either be an uncompressed zip file, or a directory.
226 func loadTzinfoFromDirOrZip(dir
, name
string) ([]byte, error
) {
227 if len(dir
) > 4 && dir
[len(dir
)-4:] == ".zip" {
228 return loadTzinfoFromZip(dir
, name
)
231 name
= dir
+ "/" + name
233 return readFile(name
)
236 // There are 500+ zoneinfo files. Rather than distribute them all
237 // individually, we ship them in an uncompressed zip file.
238 // Used this way, the zip file format serves as a commonly readable
239 // container for the individual small files. We choose zip over tar
240 // because zip files have a contiguous table of contents, making
241 // individual file lookups faster, and because the per-file overhead
242 // in a zip file is considerably less than tar's 512 bytes.
244 // get4 returns the little-endian 32-bit value in b.
245 func get4(b
[]byte) int {
249 return int(b
[0]) |
int(b
[1])<<8 |
int(b
[2])<<16 |
int(b
[3])<<24
252 // get2 returns the little-endian 16-bit value in b.
253 func get2(b
[]byte) int {
257 return int(b
[0]) |
int(b
[1])<<8
260 // loadTzinfoFromZip returns the contents of the file with the given name
261 // in the given uncompressed zip file.
262 func loadTzinfoFromZip(zipfile
, name
string) ([]byte, error
) {
263 fd
, err
:= open(zipfile
)
265 return nil, errors
.New("open " + zipfile
+ ": " + err
.Error())
270 zecheader
= 0x06054b50
271 zcheader
= 0x02014b50
278 buf
:= make([]byte, ztailsize
)
279 if err
:= preadn(fd
, buf
, -ztailsize
); err
!= nil ||
get4(buf
) != zecheader
{
280 return nil, errors
.New("corrupt zip file " + zipfile
)
283 size
:= get4(buf
[12:])
284 off
:= get4(buf
[16:])
286 buf
= make([]byte, size
)
287 if err
:= preadn(fd
, buf
, off
); err
!= nil {
288 return nil, errors
.New("corrupt zip file " + zipfile
)
291 for i
:= 0; i
< n
; i
++ {
313 // 46+namelen+xlen+fclen - next header
315 if get4(buf
) != zcheader
{
318 meth
:= get2(buf
[10:])
319 size
:= get4(buf
[24:])
320 namelen
:= get2(buf
[28:])
321 xlen
:= get2(buf
[30:])
322 fclen
:= get2(buf
[32:])
323 off
:= get4(buf
[42:])
324 zname
:= buf
[46 : 46+namelen
]
325 buf
= buf
[46+namelen
+xlen
+fclen
:]
326 if string(zname
) != name
{
330 return nil, errors
.New("unsupported compression for " + name
+ " in " + zipfile
)
333 // zip per-file header layout:
347 // 30+namelen+xlen - file data
349 buf
= make([]byte, zheadersize
+namelen
)
350 if err
:= preadn(fd
, buf
, off
); err
!= nil ||
351 get4(buf
) != zheader ||
352 get2(buf
[8:]) != meth ||
353 get2(buf
[26:]) != namelen ||
354 string(buf
[30:30+namelen
]) != name
{
355 return nil, errors
.New("corrupt zip file " + zipfile
)
357 xlen
= get2(buf
[28:])
359 buf
= make([]byte, size
)
360 if err
:= preadn(fd
, buf
, off
+30+namelen
+xlen
); err
!= nil {
361 return nil, errors
.New("corrupt zip file " + zipfile
)
367 return nil, errors
.New("cannot find " + name
+ " in zip file " + zipfile
)
370 // loadTzinfoFromTzdata returns the time zone information of the time zone
371 // with the given name, from a tzdata database file as they are typically
373 var loadTzinfoFromTzdata
func(file
, name
string) ([]byte, error
)
375 // loadTzinfo returns the time zone information of the time zone
376 // with the given name, from a given source. A source may be a
377 // timezone database directory, tzdata database file or an uncompressed
378 // zip file, containing the contents of such a directory.
379 func loadTzinfo(name
string, source
string) ([]byte, error
) {
380 if len(source
) >= 6 && source
[len(source
)-6:] == "tzdata" {
381 return loadTzinfoFromTzdata(source
, name
)
383 return loadTzinfoFromDirOrZip(source
, name
)
386 // loadLocation returns the Location with the given name from one of
387 // the specified sources. See loadTzinfo for a list of supported sources.
388 // The first timezone data matching the given name that is successfully loaded
389 // and parsed is returned as a Location.
390 func loadLocation(name
string, sources
[]string) (z
*Location
, firstErr error
) {
391 for _
, source
:= range sources
{
392 var zoneData
, err
= loadTzinfo(name
, source
)
394 if z
, err
= LoadLocationFromTZData(name
, zoneData
); err
== nil {
398 if firstErr
== nil && err
!= syscall
.ENOENT
{
405 return nil, errors
.New("unknown time zone " + name
)
408 // readFile reads and returns the content of the named file.
409 // It is a trivial implementation of ioutil.ReadFile, reimplemented
410 // here to avoid depending on io/ioutil or os.
411 // It returns an error if name exceeds maxFileSize bytes.
412 func readFile(name
string) ([]byte, error
) {
424 n
, err
= read(f
, buf
[:])
426 ret
= append(ret
, buf
[:n
]...)
428 if n
== 0 || err
!= nil {
431 if len(ret
) > maxFileSize
{
432 return nil, fileSizeError(name
)