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