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
5package time
6
7import (
8	"errors"
9	"sync"
10	"syscall"
11)
12
13//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go
14
15// A Location maps time instants to the zone in use at that time.
16// Typically, the Location represents the collection of time offsets
17// in use in a geographical area, such as CEST and CET for central Europe.
18type Location struct {
19	name string
20	zone []zone
21	tx   []zoneTrans
22
23	// Most lookups will be for the current time.
24	// To avoid the binary search through tx, keep a
25	// static one-element cache that gives the correct
26	// zone for the time when the Location was created.
27	// if cacheStart <= t < cacheEnd,
28	// lookup can return cacheZone.
29	// The units for cacheStart and cacheEnd are seconds
30	// since January 1, 1970 UTC, to match the argument
31	// to lookup.
32	cacheStart int64
33	cacheEnd   int64
34	cacheZone  *zone
35}
36
37// A zone represents a single time zone such as CEST or CET.
38type zone struct {
39	name   string // abbreviated name, "CET"
40	offset int    // seconds east of UTC
41	isDST  bool   // is this zone Daylight Savings Time?
42}
43
44// A zoneTrans represents a single time zone transition.
45type zoneTrans struct {
46	when         int64 // transition time, in seconds since 1970 GMT
47	index        uint8 // the index of the zone that goes into effect at that time
48	isstd, isutc bool  // ignored - no idea what these mean
49}
50
51// alpha and omega are the beginning and end of time for zone
52// transitions.
53const (
54	alpha = -1 << 63  // math.MinInt64
55	omega = 1<<63 - 1 // math.MaxInt64
56)
57
58// UTC represents Universal Coordinated Time (UTC).
59var UTC *Location = &utcLoc
60
61// utcLoc is separate so that get can refer to &utcLoc
62// and ensure that it never returns a nil *Location,
63// even if a badly behaved client has changed UTC.
64var utcLoc = Location{name: "UTC"}
65
66// Local represents the system's local time zone.
67var Local *Location = &localLoc
68
69// localLoc is separate so that initLocal can initialize
70// it even if a client has changed Local.
71var localLoc Location
72var localOnce sync.Once
73
74func (l *Location) get() *Location {
75	if l == nil {
76		return &utcLoc
77	}
78	if l == &localLoc {
79		localOnce.Do(initLocal)
80	}
81	return l
82}
83
84// String returns a descriptive name for the time zone information,
85// corresponding to the name argument to LoadLocation or FixedZone.
86func (l *Location) String() string {
87	return l.get().name
88}
89
90// FixedZone returns a Location that always uses
91// the given zone name and offset (seconds east of UTC).
92func FixedZone(name string, offset int) *Location {
93	l := &Location{
94		name:       name,
95		zone:       []zone{{name, offset, false}},
96		tx:         []zoneTrans{{alpha, 0, false, false}},
97		cacheStart: alpha,
98		cacheEnd:   omega,
99	}
100	l.cacheZone = &l.zone[0]
101	return l
102}
103
104// lookup returns information about the time zone in use at an
105// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
106//
107// The returned information gives the name of the zone (such as "CET"),
108// the start and end times bracketing sec when that zone is in effect,
109// the offset in seconds east of UTC (such as -5*60*60), and whether
110// the daylight savings is being observed at that time.
111func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
112	l = l.get()
113
114	if len(l.zone) == 0 {
115		name = "UTC"
116		offset = 0
117		isDST = false
118		start = alpha
119		end = omega
120		return
121	}
122
123	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
124		name = zone.name
125		offset = zone.offset
126		isDST = zone.isDST
127		start = l.cacheStart
128		end = l.cacheEnd
129		return
130	}
131
132	if len(l.tx) == 0 || sec < l.tx[0].when {
133		zone := &l.zone[l.lookupFirstZone()]
134		name = zone.name
135		offset = zone.offset
136		isDST = zone.isDST
137		start = alpha
138		if len(l.tx) > 0 {
139			end = l.tx[0].when
140		} else {
141			end = omega
142		}
143		return
144	}
145
146	// Binary search for entry with largest time <= sec.
147	// Not using sort.Search to avoid dependencies.
148	tx := l.tx
149	end = omega
150	lo := 0
151	hi := len(tx)
152	for hi-lo > 1 {
153		m := lo + (hi-lo)/2
154		lim := tx[m].when
155		if sec < lim {
156			end = lim
157			hi = m
158		} else {
159			lo = m
160		}
161	}
162	zone := &l.zone[tx[lo].index]
163	name = zone.name
164	offset = zone.offset
165	isDST = zone.isDST
166	start = tx[lo].when
167	// end = maintained during the search
168	return
169}
170
171// lookupFirstZone returns the index of the time zone to use for times
172// before the first transition time, or when there are no transition
173// times.
174//
175// The reference implementation in localtime.c from
176// http://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
177// implements the following algorithm for these cases:
178// 1) If the first zone is unused by the transitions, use it.
179// 2) Otherwise, if there are transition times, and the first
180//    transition is to a zone in daylight time, find the first
181//    non-daylight-time zone before and closest to the first transition
182//    zone.
183// 3) Otherwise, use the first zone that is not daylight time, if
184//    there is one.
185// 4) Otherwise, use the first zone.
186func (l *Location) lookupFirstZone() int {
187	// Case 1.
188	if !l.firstZoneUsed() {
189		return 0
190	}
191
192	// Case 2.
193	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
194		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
195			if !l.zone[zi].isDST {
196				return zi
197			}
198		}
199	}
200
201	// Case 3.
202	for zi := range l.zone {
203		if !l.zone[zi].isDST {
204			return zi
205		}
206	}
207
208	// Case 4.
209	return 0
210}
211
212// firstZoneUsed returns whether the first zone is used by some
213// transition.
214func (l *Location) firstZoneUsed() bool {
215	for _, tx := range l.tx {
216		if tx.index == 0 {
217			return true
218		}
219	}
220	return false
221}
222
223// lookupName returns information about the time zone with
224// the given name (such as "EST") at the given pseudo-Unix time
225// (what the given time of day would be in UTC).
226func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
227	l = l.get()
228
229	// First try for a zone with the right name that was actually
230	// in effect at the given time. (In Sydney, Australia, both standard
231	// and daylight-savings time are abbreviated "EST". Using the
232	// offset helps us pick the right one for the given time.
233	// It's not perfect: during the backward transition we might pick
234	// either one.)
235	for i := range l.zone {
236		zone := &l.zone[i]
237		if zone.name == name {
238			nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
239			if nam == zone.name {
240				return offset, true
241			}
242		}
243	}
244
245	// Otherwise fall back to an ordinary name match.
246	for i := range l.zone {
247		zone := &l.zone[i]
248		if zone.name == name {
249			return zone.offset, true
250		}
251	}
252
253	// Otherwise, give up.
254	return
255}
256
257// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
258// syntax too, but I don't feel like implementing it today.
259
260var errLocation = errors.New("time: invalid location name")
261
262var zoneinfo *string
263var zoneinfoOnce sync.Once
264
265// LoadLocation returns the Location with the given name.
266//
267// If the name is "" or "UTC", LoadLocation returns UTC.
268// If the name is "Local", LoadLocation returns Local.
269//
270// Otherwise, the name is taken to be a location name corresponding to a file
271// in the IANA Time Zone database, such as "America/New_York".
272//
273// The time zone database needed by LoadLocation may not be
274// present on all systems, especially non-Unix systems.
275// LoadLocation looks in the directory or uncompressed zip file
276// named by the ZONEINFO environment variable, if any, then looks in
277// known installation locations on Unix systems,
278// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
279func LoadLocation(name string) (*Location, error) {
280	if name == "" || name == "UTC" {
281		return UTC, nil
282	}
283	if name == "Local" {
284		return Local, nil
285	}
286	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
287		// No valid IANA Time Zone name contains a single dot,
288		// much less dot dot. Likewise, none begin with a slash.
289		return nil, errLocation
290	}
291	zoneinfoOnce.Do(func() {
292		env, _ := syscall.Getenv("ZONEINFO")
293		zoneinfo = &env
294	})
295	if *zoneinfo != "" {
296		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
297			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
298				return z, nil
299			}
300		}
301	}
302	return loadLocation(name, zoneSources)
303}
304
305// containsDotDot reports whether s contains "..".
306func containsDotDot(s string) bool {
307	if len(s) < 2 {
308		return false
309	}
310	for i := 0; i < len(s)-1; i++ {
311		if s[i] == '.' && s[i+1] == '.' {
312			return true
313		}
314	}
315	return false
316}
317