1// Copyright 2011 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.
4
5// Parse Plan 9 timezone(2) files.
6
7package time
8
9import (
10	"runtime"
11	"syscall"
12)
13
14func isSpace(r rune) bool {
15	return r == ' ' || r == '\t' || r == '\n'
16}
17
18// Copied from strings to avoid a dependency.
19func fields(s string) []string {
20	// First count the fields.
21	n := 0
22	inField := false
23	for _, rune := range s {
24		wasInField := inField
25		inField = !isSpace(rune)
26		if inField && !wasInField {
27			n++
28		}
29	}
30
31	// Now create them.
32	a := make([]string, n)
33	na := 0
34	fieldStart := -1 // Set to -1 when looking for start of field.
35	for i, rune := range s {
36		if isSpace(rune) {
37			if fieldStart >= 0 {
38				a[na] = s[fieldStart:i]
39				na++
40				fieldStart = -1
41			}
42		} else if fieldStart == -1 {
43			fieldStart = i
44		}
45	}
46	if fieldStart >= 0 { // Last field might end at EOF.
47		a[na] = s[fieldStart:]
48	}
49	return a
50}
51
52func loadZoneDataPlan9(s string) (l *Location, err error) {
53	f := fields(s)
54	if len(f) < 4 {
55		if len(f) == 2 && f[0] == "GMT" {
56			return UTC, nil
57		}
58		return nil, badData
59	}
60
61	var zones [2]zone
62
63	// standard timezone offset
64	o, err := atoi(f[1])
65	if err != nil {
66		return nil, badData
67	}
68	zones[0] = zone{name: f[0], offset: o, isDST: false}
69
70	// alternate timezone offset
71	o, err = atoi(f[3])
72	if err != nil {
73		return nil, badData
74	}
75	zones[1] = zone{name: f[2], offset: o, isDST: true}
76
77	// transition time pairs
78	var tx []zoneTrans
79	f = f[4:]
80	for i := 0; i < len(f); i++ {
81		zi := 0
82		if i%2 == 0 {
83			zi = 1
84		}
85		t, err := atoi(f[i])
86		if err != nil {
87			return nil, badData
88		}
89		t -= zones[0].offset
90		tx = append(tx, zoneTrans{when: int64(t), index: uint8(zi)})
91	}
92
93	// Committed to succeed.
94	l = &Location{zone: zones[:], tx: tx}
95
96	// Fill in the cache with information about right now,
97	// since that will be the most common lookup.
98	sec, _ := now()
99	for i := range tx {
100		if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
101			l.cacheStart = tx[i].when
102			l.cacheEnd = omega
103			if i+1 < len(tx) {
104				l.cacheEnd = tx[i+1].when
105			}
106			l.cacheZone = &l.zone[tx[i].index]
107		}
108	}
109
110	return l, nil
111}
112
113func loadZoneFilePlan9(name string) (*Location, error) {
114	b, err := readFile(name)
115	if err != nil {
116		return nil, err
117	}
118	return loadZoneDataPlan9(string(b))
119}
120
121func initTestingZone() {
122	z, err := loadLocation("America/Los_Angeles")
123	if err != nil {
124		panic("cannot load America/Los_Angeles for testing: " + err.Error())
125	}
126	z.name = "Local"
127	localLoc = *z
128}
129
130func initLocal() {
131	t, ok := syscall.Getenv("timezone")
132	if ok {
133		if z, err := loadZoneDataPlan9(t); err == nil {
134			localLoc = *z
135			return
136		}
137	} else {
138		if z, err := loadZoneFilePlan9("/adm/timezone/local"); err == nil {
139			localLoc = *z
140			localLoc.name = "Local"
141			return
142		}
143	}
144
145	// Fall back to UTC.
146	localLoc.name = "UTC"
147}
148
149func loadLocation(name string) (*Location, error) {
150	z, err := loadZoneFile(runtime.GOROOT()+"/lib/time/zoneinfo.zip", name)
151	if err != nil {
152		return nil, err
153	}
154	z.name = name
155	return z, nil
156}
157
158func forceZipFileForTesting(zipOnly bool) {
159	// We only use the zip file anyway.
160}
161