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 // Copies of io.Seek* constants to avoid importing "io":
21 // Simple I/O interface to binary blob of data.
27 func (d
*data
) read(n
int) []byte {
38 func (d
*data
) big4() (n
uint32, ok
bool) {
44 return uint32(p
[0])<<24 |
uint32(p
[1])<<16 |
uint32(p
[2])<<8 |
uint32(p
[3]), true
47 func (d
*data
) byte() (n
byte, ok
bool) {
56 // Make a string by stopping at the first NUL
57 func byteString(p
[]byte) string {
58 for i
:= 0; i
< len(p
); i
++ {
66 var badData
= errors
.New("malformed time zone information")
68 func loadZoneData(bytes
[]byte) (l
*Location
, err error
) {
69 d
:= data
{bytes
, false}
71 // 4-byte magic "TZif"
72 if magic
:= d
.read(4); string(magic
) != "TZif" {
76 // 1-byte version, then 15 bytes of padding
78 if p
= d
.read(16); len(p
) != 16 || p
[0] != 0 && p
[0] != '2' && p
[0] != '3' {
82 // six big-endian 32-bit integers:
83 // number of UTC/local indicators
84 // number of standard/wall indicators
85 // number of leap seconds
86 // number of transition times
87 // number of local time zones
88 // number of characters of time zone abbrev strings
98 for i
:= 0; i
< 6; i
++ {
107 txtimes
:= data
{d
.read(n
[NTime
] * 4), false}
109 // Time zone indices for transition times.
110 txzones
:= d
.read(n
[NTime
])
112 // Zone info structures
113 zonedata
:= data
{d
.read(n
[NZone
] * 6), false}
115 // Time zone abbreviations.
116 abbrev
:= d
.read(n
[NChar
])
118 // Leap-second time pairs
121 // Whether tx times associated with local time types
122 // are specified as standard time or wall time.
123 isstd
:= d
.read(n
[NStdWall
])
125 // Whether tx times associated with local time types
126 // are specified as UTC or local time.
127 isutc
:= d
.read(n
[NUTCLocal
])
129 if d
.error
{ // ran out of data
133 // If version == 2 or 3, the entire file repeats, this time using
134 // 8-byte ints for txtimes and leap seconds.
135 // We won't need those until 2106.
137 // Now we can build up a useful data structure.
138 // First the zone information.
139 // utcoff[4] isdst[1] nameindex[1]
140 zone
:= make([]zone
, n
[NZone
])
141 for i
:= range zone
{
144 if n
, ok
= zonedata
.big4(); !ok
{
147 zone
[i
].offset
= int(int32(n
))
149 if b
, ok
= zonedata
.byte(); !ok
{
152 zone
[i
].isDST
= b
!= 0
153 if b
, ok
= zonedata
.byte(); !ok ||
int(b
) >= len(abbrev
) {
156 zone
[i
].name
= byteString(abbrev
[b
:])
159 // Now the transition time info.
160 tx
:= make([]zoneTrans
, n
[NTime
])
164 if n
, ok
= txtimes
.big4(); !ok
{
167 tx
[i
].when
= int64(int32(n
))
168 if int(txzones
[i
]) >= len(zone
) {
171 tx
[i
].index
= txzones
[i
]
173 tx
[i
].isstd
= isstd
[i
] != 0
176 tx
[i
].isutc
= isutc
[i
] != 0
181 // Build fake transition to cover all time.
182 // This happens in fixed locations like "Etc/GMT0".
183 tx
= append(tx
, zoneTrans
{when
: alpha
, index
: 0})
186 // Committed to succeed.
187 l
= &Location
{zone
: zone
, tx
: tx
}
189 // Fill in the cache with information about right now,
190 // since that will be the most common lookup.
193 if tx
[i
].when
<= sec
&& (i
+1 == len(tx
) || sec
< tx
[i
+1].when
) {
194 l
.cacheStart
= tx
[i
].when
197 l
.cacheEnd
= tx
[i
+1].when
199 l
.cacheZone
= &l
.zone
[tx
[i
].index
]
206 func loadZoneFile(dir
, name
string) (l
*Location
, err error
) {
207 if len(dir
) > 4 && dir
[len(dir
)-4:] == ".zip" {
208 return loadZoneZip(dir
, name
)
211 name
= dir
+ "/" + name
213 buf
, err
:= readFile(name
)
217 return loadZoneData(buf
)
220 // There are 500+ zoneinfo files. Rather than distribute them all
221 // individually, we ship them in an uncompressed zip file.
222 // Used this way, the zip file format serves as a commonly readable
223 // container for the individual small files. We choose zip over tar
224 // because zip files have a contiguous table of contents, making
225 // individual file lookups faster, and because the per-file overhead
226 // in a zip file is considerably less than tar's 512 bytes.
228 // get4 returns the little-endian 32-bit value in b.
229 func get4(b
[]byte) int {
233 return int(b
[0]) |
int(b
[1])<<8 |
int(b
[2])<<16 |
int(b
[3])<<24
236 // get2 returns the little-endian 16-bit value in b.
237 func get2(b
[]byte) int {
241 return int(b
[0]) |
int(b
[1])<<8
244 func loadZoneZip(zipfile
, name
string) (l
*Location
, err error
) {
245 fd
, err
:= open(zipfile
)
247 return nil, errors
.New("open " + zipfile
+ ": " + err
.Error())
252 zecheader
= 0x06054b50
253 zcheader
= 0x02014b50
260 buf
:= make([]byte, ztailsize
)
261 if err
:= preadn(fd
, buf
, -ztailsize
); err
!= nil ||
get4(buf
) != zecheader
{
262 return nil, errors
.New("corrupt zip file " + zipfile
)
265 size
:= get4(buf
[12:])
266 off
:= get4(buf
[16:])
268 buf
= make([]byte, size
)
269 if err
:= preadn(fd
, buf
, off
); err
!= nil {
270 return nil, errors
.New("corrupt zip file " + zipfile
)
273 for i
:= 0; i
< n
; i
++ {
295 // 46+namelen+xlen+fclen - next header
297 if get4(buf
) != zcheader
{
300 meth
:= get2(buf
[10:])
301 size
:= get4(buf
[24:])
302 namelen
:= get2(buf
[28:])
303 xlen
:= get2(buf
[30:])
304 fclen
:= get2(buf
[32:])
305 off
:= get4(buf
[42:])
306 zname
:= buf
[46 : 46+namelen
]
307 buf
= buf
[46+namelen
+xlen
+fclen
:]
308 if string(zname
) != name
{
312 return nil, errors
.New("unsupported compression for " + name
+ " in " + zipfile
)
315 // zip per-file header layout:
329 // 30+namelen+xlen - file data
331 buf
= make([]byte, zheadersize
+namelen
)
332 if err
:= preadn(fd
, buf
, off
); err
!= nil ||
333 get4(buf
) != zheader ||
334 get2(buf
[8:]) != meth ||
335 get2(buf
[26:]) != namelen ||
336 string(buf
[30:30+namelen
]) != name
{
337 return nil, errors
.New("corrupt zip file " + zipfile
)
339 xlen
= get2(buf
[28:])
341 buf
= make([]byte, size
)
342 if err
:= preadn(fd
, buf
, off
+30+namelen
+xlen
); err
!= nil {
343 return nil, errors
.New("corrupt zip file " + zipfile
)
346 return loadZoneData(buf
)
349 return nil, errors
.New("cannot find " + name
+ " in zip file " + zipfile
)