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, start, end int64) {
112	l = l.get()
113
114	if len(l.zone) == 0 {
115		name = "UTC"
116		offset = 0
117		start = alpha
118		end = omega
119		return
120	}
121
122	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
123		name = zone.name
124		offset = zone.offset
125		start = l.cacheStart
126		end = l.cacheEnd
127		return
128	}
129
130	if len(l.tx) == 0 || sec < l.tx[0].when {
131		zone := &l.zone[l.lookupFirstZone()]
132		name = zone.name
133		offset = zone.offset
134		start = alpha
135		if len(l.tx) > 0 {
136			end = l.tx[0].when
137		} else {
138			end = omega
139		}
140		return
141	}
142
143	// Binary search for entry with largest time <= sec.
144	// Not using sort.Search to avoid dependencies.
145	tx := l.tx
146	end = omega
147	lo := 0
148	hi := len(tx)
149	for hi-lo > 1 {
150		m := lo + (hi-lo)/2
151		lim := tx[m].when
152		if sec < lim {
153			end = lim
154			hi = m
155		} else {
156			lo = m
157		}
158	}
159	zone := &l.zone[tx[lo].index]
160	name = zone.name
161	offset = zone.offset
162	start = tx[lo].when
163	// end = maintained during the search
164	return
165}
166
167// lookupFirstZone returns the index of the time zone to use for times
168// before the first transition time, or when there are no transition
169// times.
170//
171// The reference implementation in localtime.c from
172// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
173// implements the following algorithm for these cases:
174// 1) If the first zone is unused by the transitions, use it.
175// 2) Otherwise, if there are transition times, and the first
176//    transition is to a zone in daylight time, find the first
177//    non-daylight-time zone before and closest to the first transition
178//    zone.
179// 3) Otherwise, use the first zone that is not daylight time, if
180//    there is one.
181// 4) Otherwise, use the first zone.
182func (l *Location) lookupFirstZone() int {
183	// Case 1.
184	if !l.firstZoneUsed() {
185		return 0
186	}
187
188	// Case 2.
189	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
190		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
191			if !l.zone[zi].isDST {
192				return zi
193			}
194		}
195	}
196
197	// Case 3.
198	for zi := range l.zone {
199		if !l.zone[zi].isDST {
200			return zi
201		}
202	}
203
204	// Case 4.
205	return 0
206}
207
208// firstZoneUsed reports whether the first zone is used by some
209// transition.
210func (l *Location) firstZoneUsed() bool {
211	for _, tx := range l.tx {
212		if tx.index == 0 {
213			return true
214		}
215	}
216	return false
217}
218
219// lookupName returns information about the time zone with
220// the given name (such as "EST") at the given pseudo-Unix time
221// (what the given time of day would be in UTC).
222func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
223	l = l.get()
224
225	// First try for a zone with the right name that was actually
226	// in effect at the given time. (In Sydney, Australia, both standard
227	// and daylight-savings time are abbreviated "EST". Using the
228	// offset helps us pick the right one for the given time.
229	// It's not perfect: during the backward transition we might pick
230	// either one.)
231	for i := range l.zone {
232		zone := &l.zone[i]
233		if zone.name == name {
234			nam, offset, _, _ := l.lookup(unix - int64(zone.offset))
235			if nam == zone.name {
236				return offset, true
237			}
238		}
239	}
240
241	// Otherwise fall back to an ordinary name match.
242	for i := range l.zone {
243		zone := &l.zone[i]
244		if zone.name == name {
245			return zone.offset, true
246		}
247	}
248
249	// Otherwise, give up.
250	return
251}
252
253// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
254// syntax too, but I don't feel like implementing it today.
255
256var errLocation = errors.New("time: invalid location name")
257
258var zoneinfo *string
259var zoneinfoOnce sync.Once
260
261// LoadLocation returns the Location with the given name.
262//
263// If the name is "" or "UTC", LoadLocation returns UTC.
264// If the name is "Local", LoadLocation returns Local.
265//
266// Otherwise, the name is taken to be a location name corresponding to a file
267// in the IANA Time Zone database, such as "America/New_York".
268//
269// The time zone database needed by LoadLocation may not be
270// present on all systems, especially non-Unix systems.
271// LoadLocation looks in the directory or uncompressed zip file
272// named by the ZONEINFO environment variable, if any, then looks in
273// known installation locations on Unix systems,
274// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
275func LoadLocation(name string) (*Location, error) {
276	if name == "" || name == "UTC" {
277		return UTC, nil
278	}
279	if name == "Local" {
280		return Local, nil
281	}
282	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
283		// No valid IANA Time Zone name contains a single dot,
284		// much less dot dot. Likewise, none begin with a slash.
285		return nil, errLocation
286	}
287	zoneinfoOnce.Do(func() {
288		env, _ := syscall.Getenv("ZONEINFO")
289		zoneinfo = &env
290	})
291	var firstErr error
292	if *zoneinfo != "" {
293		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
294			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
295				return z, nil
296			}
297			firstErr = err
298		} else if err != syscall.ENOENT {
299			firstErr = err
300		}
301	}
302	if z, err := loadLocation(name, zoneSources); err == nil {
303		return z, nil
304	} else if firstErr == nil {
305		firstErr = err
306	}
307	return nil, firstErr
308}
309
310// containsDotDot reports whether s contains "..".
311func containsDotDot(s string) bool {
312	if len(s) < 2 {
313		return false
314	}
315	for i := 0; i < len(s)-1; i++ {
316		if s[i] == '.' && s[i+1] == '.' {
317			return true
318		}
319	}
320	return false
321}
322