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/
14 // maxFileSize is the max permitted size of files read by readFile.
15 // As reference, the zoneinfo.zip distributed by Go is ~350 KB,
16 // so 10MB is overkill.
17 const maxFileSize
= 10 << 20
19 type fileSizeError
string
21 func (f fileSizeError
) Error() string {
22 return "time: file " + string(f
) + " is too large"
25 // Copies of io.Seek* constants to avoid importing "io":
32 // Simple I/O interface to binary blob of data.
38 func (d
*data
) read(n
int) []byte {
49 func (d
*data
) big4() (n
uint32, ok
bool) {
55 return uint32(p
[0])<<24 |
uint32(p
[1])<<16 |
uint32(p
[2])<<8 |
uint32(p
[3]), true
58 func (d
*data
) byte() (n
byte, ok
bool) {
67 // Make a string by stopping at the first NUL
68 func byteString(p
[]byte) string {
69 for i
:= 0; i
< len(p
); i
++ {
77 var badData
= errors
.New("malformed time zone information")
79 func loadZoneData(bytes
[]byte) (l
*Location
, err error
) {
80 d
:= data
{bytes
, false}
82 // 4-byte magic "TZif"
83 if magic
:= d
.read(4); string(magic
) != "TZif" {
87 // 1-byte version, then 15 bytes of padding
89 if p
= d
.read(16); len(p
) != 16 || p
[0] != 0 && p
[0] != '2' && p
[0] != '3' {
93 // six big-endian 32-bit integers:
94 // number of UTC/local indicators
95 // number of standard/wall indicators
96 // number of leap seconds
97 // number of transition times
98 // number of local time zones
99 // number of characters of time zone abbrev strings
109 for i
:= 0; i
< 6; i
++ {
118 txtimes
:= data
{d
.read(n
[NTime
] * 4), false}
120 // Time zone indices for transition times.
121 txzones
:= d
.read(n
[NTime
])
123 // Zone info structures
124 zonedata
:= data
{d
.read(n
[NZone
] * 6), false}
126 // Time zone abbreviations.
127 abbrev
:= d
.read(n
[NChar
])
129 // Leap-second time pairs
132 // Whether tx times associated with local time types
133 // are specified as standard time or wall time.
134 isstd
:= d
.read(n
[NStdWall
])
136 // Whether tx times associated with local time types
137 // are specified as UTC or local time.
138 isutc
:= d
.read(n
[NUTCLocal
])
140 if d
.error
{ // ran out of data
144 // If version == 2 or 3, the entire file repeats, this time using
145 // 8-byte ints for txtimes and leap seconds.
146 // We won't need those until 2106.
148 // Now we can build up a useful data structure.
149 // First the zone information.
150 // utcoff[4] isdst[1] nameindex[1]
151 zone
:= make([]zone
, n
[NZone
])
152 for i
:= range zone
{
155 if n
, ok
= zonedata
.big4(); !ok
{
158 zone
[i
].offset
= int(int32(n
))
160 if b
, ok
= zonedata
.byte(); !ok
{
163 zone
[i
].isDST
= b
!= 0
164 if b
, ok
= zonedata
.byte(); !ok ||
int(b
) >= len(abbrev
) {
167 zone
[i
].name
= byteString(abbrev
[b
:])
170 // Now the transition time info.
171 tx
:= make([]zoneTrans
, n
[NTime
])
175 if n
, ok
= txtimes
.big4(); !ok
{
178 tx
[i
].when
= int64(int32(n
))
179 if int(txzones
[i
]) >= len(zone
) {
182 tx
[i
].index
= txzones
[i
]
184 tx
[i
].isstd
= isstd
[i
] != 0
187 tx
[i
].isutc
= isutc
[i
] != 0
192 // Build fake transition to cover all time.
193 // This happens in fixed locations like "Etc/GMT0".
194 tx
= append(tx
, zoneTrans
{when
: alpha
, index
: 0})
197 // Committed to succeed.
198 l
= &Location
{zone
: zone
, tx
: tx
}
200 // Fill in the cache with information about right now,
201 // since that will be the most common lookup.
204 if tx
[i
].when
<= sec
&& (i
+1 == len(tx
) || sec
< tx
[i
+1].when
) {
205 l
.cacheStart
= tx
[i
].when
208 l
.cacheEnd
= tx
[i
+1].when
210 l
.cacheZone
= &l
.zone
[tx
[i
].index
]
217 func loadZoneFile(dir
, name
string) (l
*Location
, err error
) {
218 if len(dir
) > 4 && dir
[len(dir
)-4:] == ".zip" {
219 return loadZoneZip(dir
, name
)
222 name
= dir
+ "/" + name
224 buf
, err
:= readFile(name
)
228 return loadZoneData(buf
)
231 // There are 500+ zoneinfo files. Rather than distribute them all
232 // individually, we ship them in an uncompressed zip file.
233 // Used this way, the zip file format serves as a commonly readable
234 // container for the individual small files. We choose zip over tar
235 // because zip files have a contiguous table of contents, making
236 // individual file lookups faster, and because the per-file overhead
237 // in a zip file is considerably less than tar's 512 bytes.
239 // get4 returns the little-endian 32-bit value in b.
240 func get4(b
[]byte) int {
244 return int(b
[0]) |
int(b
[1])<<8 |
int(b
[2])<<16 |
int(b
[3])<<24
247 // get2 returns the little-endian 16-bit value in b.
248 func get2(b
[]byte) int {
252 return int(b
[0]) |
int(b
[1])<<8
255 func loadZoneZip(zipfile
, name
string) (l
*Location
, err error
) {
256 fd
, err
:= open(zipfile
)
258 return nil, errors
.New("open " + zipfile
+ ": " + err
.Error())
263 zecheader
= 0x06054b50
264 zcheader
= 0x02014b50
271 buf
:= make([]byte, ztailsize
)
272 if err
:= preadn(fd
, buf
, -ztailsize
); err
!= nil ||
get4(buf
) != zecheader
{
273 return nil, errors
.New("corrupt zip file " + zipfile
)
276 size
:= get4(buf
[12:])
277 off
:= get4(buf
[16:])
279 buf
= make([]byte, size
)
280 if err
:= preadn(fd
, buf
, off
); err
!= nil {
281 return nil, errors
.New("corrupt zip file " + zipfile
)
284 for i
:= 0; i
< n
; i
++ {
306 // 46+namelen+xlen+fclen - next header
308 if get4(buf
) != zcheader
{
311 meth
:= get2(buf
[10:])
312 size
:= get4(buf
[24:])
313 namelen
:= get2(buf
[28:])
314 xlen
:= get2(buf
[30:])
315 fclen
:= get2(buf
[32:])
316 off
:= get4(buf
[42:])
317 zname
:= buf
[46 : 46+namelen
]
318 buf
= buf
[46+namelen
+xlen
+fclen
:]
319 if string(zname
) != name
{
323 return nil, errors
.New("unsupported compression for " + name
+ " in " + zipfile
)
326 // zip per-file header layout:
340 // 30+namelen+xlen - file data
342 buf
= make([]byte, zheadersize
+namelen
)
343 if err
:= preadn(fd
, buf
, off
); err
!= nil ||
344 get4(buf
) != zheader ||
345 get2(buf
[8:]) != meth ||
346 get2(buf
[26:]) != namelen ||
347 string(buf
[30:30+namelen
]) != name
{
348 return nil, errors
.New("corrupt zip file " + zipfile
)
350 xlen
= get2(buf
[28:])
352 buf
= make([]byte, size
)
353 if err
:= preadn(fd
, buf
, off
+30+namelen
+xlen
); err
!= nil {
354 return nil, errors
.New("corrupt zip file " + zipfile
)
357 return loadZoneData(buf
)
360 return nil, errors
.New("cannot find " + name
+ " in zip file " + zipfile
)