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 // Simple I/O interface to binary blob of data.
20 func (d
*data
) read(n
int) []byte {
31 func (d
*data
) big4() (n
uint32, ok
bool) {
37 return uint32(p
[0])<<24 |
uint32(p
[1])<<16 |
uint32(p
[2])<<8 |
uint32(p
[3]), true
40 func (d
*data
) byte() (n
byte, ok
bool) {
49 // Make a string by stopping at the first NUL
50 func byteString(p
[]byte) string {
51 for i
:= 0; i
< len(p
); i
++ {
59 var badData
= errors
.New("malformed time zone information")
61 func loadZoneData(bytes
[]byte) (l
*Location
, err error
) {
62 d
:= data
{bytes
, false}
64 // 4-byte magic "TZif"
65 if magic
:= d
.read(4); string(magic
) != "TZif" {
69 // 1-byte version, then 15 bytes of padding
71 if p
= d
.read(16); len(p
) != 16 || p
[0] != 0 && p
[0] != '2' {
75 // six big-endian 32-bit integers:
76 // number of UTC/local indicators
77 // number of standard/wall indicators
78 // number of leap seconds
79 // number of transition times
80 // number of local time zones
81 // number of characters of time zone abbrev strings
91 for i
:= 0; i
< 6; i
++ {
100 txtimes
:= data
{d
.read(n
[NTime
] * 4), false}
102 // Time zone indices for transition times.
103 txzones
:= d
.read(n
[NTime
])
105 // Zone info structures
106 zonedata
:= data
{d
.read(n
[NZone
] * 6), false}
108 // Time zone abbreviations.
109 abbrev
:= d
.read(n
[NChar
])
111 // Leap-second time pairs
114 // Whether tx times associated with local time types
115 // are specified as standard time or wall time.
116 isstd
:= d
.read(n
[NStdWall
])
118 // Whether tx times associated with local time types
119 // are specified as UTC or local time.
120 isutc
:= d
.read(n
[NUTCLocal
])
122 if d
.error
{ // ran out of data
126 // If version == 2, the entire file repeats, this time using
127 // 8-byte ints for txtimes and leap seconds.
128 // We won't need those until 2106.
130 // Now we can build up a useful data structure.
131 // First the zone information.
132 // utcoff[4] isdst[1] nameindex[1]
133 zone
:= make([]zone
, n
[NZone
])
134 for i
:= range zone
{
137 if n
, ok
= zonedata
.big4(); !ok
{
140 zone
[i
].offset
= int(int32(n
))
142 if b
, ok
= zonedata
.byte(); !ok
{
145 zone
[i
].isDST
= b
!= 0
146 if b
, ok
= zonedata
.byte(); !ok ||
int(b
) >= len(abbrev
) {
149 zone
[i
].name
= byteString(abbrev
[b
:])
152 // Now the transition time info.
153 tx
:= make([]zoneTrans
, n
[NTime
])
157 if n
, ok
= txtimes
.big4(); !ok
{
160 tx
[i
].when
= int64(int32(n
))
161 if int(txzones
[i
]) >= len(zone
) {
164 tx
[i
].index
= txzones
[i
]
166 tx
[i
].isstd
= isstd
[i
] != 0
169 tx
[i
].isutc
= isutc
[i
] != 0
174 // Build fake transition to cover all time.
175 // This happens in fixed locations like "Etc/GMT0".
176 tx
= append(tx
, zoneTrans
{when
: -1 << 63, index
: 0})
179 // Committed to succeed.
180 l
= &Location
{zone
: zone
, tx
: tx
}
182 // Fill in the cache with information about right now,
183 // since that will be the most common lookup.
186 if tx
[i
].when
<= sec
&& (i
+1 == len(tx
) || sec
< tx
[i
+1].when
) {
187 l
.cacheStart
= tx
[i
].when
188 l
.cacheEnd
= 1<<63 - 1
190 l
.cacheEnd
= tx
[i
+1].when
192 l
.cacheZone
= &l
.zone
[tx
[i
].index
]
199 func loadZoneFile(dir
, name
string) (l
*Location
, err error
) {
200 if len(dir
) > 4 && dir
[len(dir
)-4:] == ".zip" {
201 return loadZoneZip(dir
, name
)
204 name
= dir
+ "/" + name
206 buf
, err
:= readFile(name
)
210 return loadZoneData(buf
)
213 // There are 500+ zoneinfo files. Rather than distribute them all
214 // individually, we ship them in an uncompressed zip file.
215 // Used this way, the zip file format serves as a commonly readable
216 // container for the individual small files. We choose zip over tar
217 // because zip files have a contiguous table of contents, making
218 // individual file lookups faster, and because the per-file overhead
219 // in a zip file is considerably less than tar's 512 bytes.
221 // get4 returns the little-endian 32-bit value in b.
222 func get4(b
[]byte) int {
226 return int(b
[0]) |
int(b
[1])<<8 |
int(b
[2])<<16 |
int(b
[3])<<24
229 // get2 returns the little-endian 16-bit value in b.
230 func get2(b
[]byte) int {
234 return int(b
[0]) |
int(b
[1])<<8
237 func loadZoneZip(zipfile
, name
string) (l
*Location
, err error
) {
238 fd
, err
:= open(zipfile
)
240 return nil, errors
.New("open " + zipfile
+ ": " + err
.Error())
245 zecheader
= 0x06054b50
246 zcheader
= 0x02014b50
253 buf
:= make([]byte, ztailsize
)
254 if err
:= preadn(fd
, buf
, -ztailsize
); err
!= nil ||
get4(buf
) != zecheader
{
255 return nil, errors
.New("corrupt zip file " + zipfile
)
258 size
:= get4(buf
[12:])
259 off
:= get4(buf
[16:])
261 buf
= make([]byte, size
)
262 if err
:= preadn(fd
, buf
, off
); err
!= nil {
263 return nil, errors
.New("corrupt zip file " + zipfile
)
266 for i
:= 0; i
< n
; i
++ {
288 // 46+namelen+xlen+fclen - next header
290 if get4(buf
) != zcheader
{
293 meth
:= get2(buf
[10:])
294 size
:= get4(buf
[24:])
295 namelen
:= get2(buf
[28:])
296 xlen
:= get2(buf
[30:])
297 fclen
:= get2(buf
[32:])
298 off
:= get4(buf
[42:])
299 zname
:= buf
[46 : 46+namelen
]
300 buf
= buf
[46+namelen
+xlen
+fclen
:]
301 if string(zname
) != name
{
305 return nil, errors
.New("unsupported compression for " + name
+ " in " + zipfile
)
308 // zip per-file header layout:
322 // 30+namelen+xlen - file data
324 buf
= make([]byte, zheadersize
+namelen
)
325 if err
:= preadn(fd
, buf
, off
); err
!= nil ||
326 get4(buf
) != zheader ||
327 get2(buf
[8:]) != meth ||
328 get2(buf
[26:]) != namelen ||
329 string(buf
[30:30+namelen
]) != name
{
330 return nil, errors
.New("corrupt zip file " + zipfile
)
332 xlen
= get2(buf
[28:])
334 buf
= make([]byte, size
)
335 if err
:= preadn(fd
, buf
, off
+30+namelen
+xlen
); err
!= nil {
336 return nil, errors
.New("corrupt zip file " + zipfile
)
339 return loadZoneData(buf
)
342 return nil, errors
.New("cannot find " + name
+ " in zip file " + zipfile
)