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. For many Locations the time offset varies
18// depending on whether daylight savings time is in use at the time instant.
19//
20// Location is used to provide a time zone in a printed Time value and for
21// calculations involving intervals that may cross daylight savings time
22// boundaries.
23type Location struct {
24	name string
25	zone []zone
26	tx   []zoneTrans
27
28	// The tzdata information can be followed by a string that describes
29	// how to handle DST transitions not recorded in zoneTrans.
30	// The format is the TZ environment variable without a colon; see
31	// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html.
32	// Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0
33	extend string
34
35	// Most lookups will be for the current time.
36	// To avoid the binary search through tx, keep a
37	// static one-element cache that gives the correct
38	// zone for the time when the Location was created.
39	// if cacheStart <= t < cacheEnd,
40	// lookup can return cacheZone.
41	// The units for cacheStart and cacheEnd are seconds
42	// since January 1, 1970 UTC, to match the argument
43	// to lookup.
44	cacheStart int64
45	cacheEnd   int64
46	cacheZone  *zone
47}
48
49// A zone represents a single time zone such as CET.
50type zone struct {
51	name   string // abbreviated name, "CET"
52	offset int    // seconds east of UTC
53	isDST  bool   // is this zone Daylight Savings Time?
54}
55
56// A zoneTrans represents a single time zone transition.
57type zoneTrans struct {
58	when         int64 // transition time, in seconds since 1970 GMT
59	index        uint8 // the index of the zone that goes into effect at that time
60	isstd, isutc bool  // ignored - no idea what these mean
61}
62
63// alpha and omega are the beginning and end of time for zone
64// transitions.
65const (
66	alpha = -1 << 63  // math.MinInt64
67	omega = 1<<63 - 1 // math.MaxInt64
68)
69
70// UTC represents Universal Coordinated Time (UTC).
71var UTC *Location = &utcLoc
72
73// utcLoc is separate so that get can refer to &utcLoc
74// and ensure that it never returns a nil *Location,
75// even if a badly behaved client has changed UTC.
76var utcLoc = Location{name: "UTC"}
77
78// Local represents the system's local time zone.
79// On Unix systems, Local consults the TZ environment
80// variable to find the time zone to use. No TZ means
81// use the system default /etc/localtime.
82// TZ="" means use UTC.
83// TZ="foo" means use file foo in the system timezone directory.
84var Local *Location = &localLoc
85
86// localLoc is separate so that initLocal can initialize
87// it even if a client has changed Local.
88var localLoc Location
89var localOnce sync.Once
90
91func (l *Location) get() *Location {
92	if l == nil {
93		return &utcLoc
94	}
95	if l == &localLoc {
96		localOnce.Do(initLocal)
97	}
98	return l
99}
100
101// String returns a descriptive name for the time zone information,
102// corresponding to the name argument to [LoadLocation] or [FixedZone].
103func (l *Location) String() string {
104	return l.get().name
105}
106
107var unnamedFixedZones []*Location
108var unnamedFixedZonesOnce sync.Once
109
110// FixedZone returns a [Location] that always uses
111// the given zone name and offset (seconds east of UTC).
112func FixedZone(name string, offset int) *Location {
113	// Most calls to FixedZone have an unnamed zone with an offset by the hour.
114	// Optimize for that case by returning the same *Location for a given hour.
115	const hoursBeforeUTC = 12
116	const hoursAfterUTC = 14
117	hour := offset / 60 / 60
118	if name == "" && -hoursBeforeUTC <= hour && hour <= +hoursAfterUTC && hour*60*60 == offset {
119		unnamedFixedZonesOnce.Do(func() {
120			unnamedFixedZones = make([]*Location, hoursBeforeUTC+1+hoursAfterUTC)
121			for hr := -hoursBeforeUTC; hr <= +hoursAfterUTC; hr++ {
122				unnamedFixedZones[hr+hoursBeforeUTC] = fixedZone("", hr*60*60)
123			}
124		})
125		return unnamedFixedZones[hour+hoursBeforeUTC]
126	}
127	return fixedZone(name, offset)
128}
129
130func fixedZone(name string, offset int) *Location {
131	l := &Location{
132		name:       name,
133		zone:       []zone{{name, offset, false}},
134		tx:         []zoneTrans{{alpha, 0, false, false}},
135		cacheStart: alpha,
136		cacheEnd:   omega,
137	}
138	l.cacheZone = &l.zone[0]
139	return l
140}
141
142// lookup returns information about the time zone in use at an
143// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
144//
145// The returned information gives the name of the zone (such as "CET"),
146// the start and end times bracketing sec when that zone is in effect,
147// the offset in seconds east of UTC (such as -5*60*60), and whether
148// the daylight savings is being observed at that time.
149func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) {
150	l = l.get()
151
152	if len(l.zone) == 0 {
153		name = "UTC"
154		offset = 0
155		start = alpha
156		end = omega
157		isDST = false
158		return
159	}
160
161	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
162		name = zone.name
163		offset = zone.offset
164		start = l.cacheStart
165		end = l.cacheEnd
166		isDST = zone.isDST
167		return
168	}
169
170	if len(l.tx) == 0 || sec < l.tx[0].when {
171		zone := &l.zone[l.lookupFirstZone()]
172		name = zone.name
173		offset = zone.offset
174		start = alpha
175		if len(l.tx) > 0 {
176			end = l.tx[0].when
177		} else {
178			end = omega
179		}
180		isDST = zone.isDST
181		return
182	}
183
184	// Binary search for entry with largest time <= sec.
185	// Not using sort.Search to avoid dependencies.
186	tx := l.tx
187	end = omega
188	lo := 0
189	hi := len(tx)
190	for hi-lo > 1 {
191		m := int(uint(lo+hi) >> 1)
192		lim := tx[m].when
193		if sec < lim {
194			end = lim
195			hi = m
196		} else {
197			lo = m
198		}
199	}
200	zone := &l.zone[tx[lo].index]
201	name = zone.name
202	offset = zone.offset
203	start = tx[lo].when
204	// end = maintained during the search
205	isDST = zone.isDST
206
207	// If we're at the end of the known zone transitions,
208	// try the extend string.
209	if lo == len(tx)-1 && l.extend != "" {
210		if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, start, sec); ok {
211			return ename, eoffset, estart, eend, eisDST
212		}
213	}
214
215	return
216}
217
218// lookupFirstZone returns the index of the time zone to use for times
219// before the first transition time, or when there are no transition
220// times.
221//
222// The reference implementation in localtime.c from
223// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
224// implements the following algorithm for these cases:
225//  1. If the first zone is unused by the transitions, use it.
226//  2. Otherwise, if there are transition times, and the first
227//     transition is to a zone in daylight time, find the first
228//     non-daylight-time zone before and closest to the first transition
229//     zone.
230//  3. Otherwise, use the first zone that is not daylight time, if
231//     there is one.
232//  4. Otherwise, use the first zone.
233func (l *Location) lookupFirstZone() int {
234	// Case 1.
235	if !l.firstZoneUsed() {
236		return 0
237	}
238
239	// Case 2.
240	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
241		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
242			if !l.zone[zi].isDST {
243				return zi
244			}
245		}
246	}
247
248	// Case 3.
249	for zi := range l.zone {
250		if !l.zone[zi].isDST {
251			return zi
252		}
253	}
254
255	// Case 4.
256	return 0
257}
258
259// firstZoneUsed reports whether the first zone is used by some
260// transition.
261func (l *Location) firstZoneUsed() bool {
262	for _, tx := range l.tx {
263		if tx.index == 0 {
264			return true
265		}
266	}
267	return false
268}
269
270// tzset takes a timezone string like the one found in the TZ environment
271// variable, the time of the last time zone transition expressed as seconds
272// since January 1, 1970 00:00:00 UTC, and a time expressed the same way.
273// We call this a tzset string since in C the function tzset reads TZ.
274// The return values are as for lookup, plus ok which reports whether the
275// parse succeeded.
276func tzset(s string, lastTxSec, sec int64) (name string, offset int, start, end int64, isDST, ok bool) {
277	var (
278		stdName, dstName     string
279		stdOffset, dstOffset int
280	)
281
282	stdName, s, ok = tzsetName(s)
283	if ok {
284		stdOffset, s, ok = tzsetOffset(s)
285	}
286	if !ok {
287		return "", 0, 0, 0, false, false
288	}
289
290	// The numbers in the tzset string are added to local time to get UTC,
291	// but our offsets are added to UTC to get local time,
292	// so we negate the number we see here.
293	stdOffset = -stdOffset
294
295	if len(s) == 0 || s[0] == ',' {
296		// No daylight savings time.
297		return stdName, stdOffset, lastTxSec, omega, false, true
298	}
299
300	dstName, s, ok = tzsetName(s)
301	if ok {
302		if len(s) == 0 || s[0] == ',' {
303			dstOffset = stdOffset + secondsPerHour
304		} else {
305			dstOffset, s, ok = tzsetOffset(s)
306			dstOffset = -dstOffset // as with stdOffset, above
307		}
308	}
309	if !ok {
310		return "", 0, 0, 0, false, false
311	}
312
313	if len(s) == 0 {
314		// Default DST rules per tzcode.
315		s = ",M3.2.0,M11.1.0"
316	}
317	// The TZ definition does not mention ';' here but tzcode accepts it.
318	if s[0] != ',' && s[0] != ';' {
319		return "", 0, 0, 0, false, false
320	}
321	s = s[1:]
322
323	var startRule, endRule rule
324	startRule, s, ok = tzsetRule(s)
325	if !ok || len(s) == 0 || s[0] != ',' {
326		return "", 0, 0, 0, false, false
327	}
328	s = s[1:]
329	endRule, s, ok = tzsetRule(s)
330	if !ok || len(s) > 0 {
331		return "", 0, 0, 0, false, false
332	}
333
334	year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)
335
336	ysec := int64(yday*secondsPerDay) + sec%secondsPerDay
337
338	// Compute start of year in seconds since Unix epoch.
339	d := daysSinceEpoch(year)
340	abs := int64(d * secondsPerDay)
341	abs += absoluteToInternal + internalToUnix
342
343	startSec := int64(tzruleTime(year, startRule, stdOffset))
344	endSec := int64(tzruleTime(year, endRule, dstOffset))
345	dstIsDST, stdIsDST := true, false
346	// Note: this is a flipping of "DST" and "STD" while retaining the labels
347	// This happens in southern hemispheres. The labelling here thus is a little
348	// inconsistent with the goal.
349	if endSec < startSec {
350		startSec, endSec = endSec, startSec
351		stdName, dstName = dstName, stdName
352		stdOffset, dstOffset = dstOffset, stdOffset
353		stdIsDST, dstIsDST = dstIsDST, stdIsDST
354	}
355
356	// The start and end values that we return are accurate
357	// close to a daylight savings transition, but are otherwise
358	// just the start and end of the year. That suffices for
359	// the only caller that cares, which is Date.
360	if ysec < startSec {
361		return stdName, stdOffset, abs, startSec + abs, stdIsDST, true
362	} else if ysec >= endSec {
363		return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true
364	} else {
365		return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true
366	}
367}
368
369// tzsetName returns the timezone name at the start of the tzset string s,
370// and the remainder of s, and reports whether the parsing is OK.
371func tzsetName(s string) (string, string, bool) {
372	if len(s) == 0 {
373		return "", "", false
374	}
375	if s[0] != '<' {
376		for i, r := range s {
377			switch r {
378			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
379				if i < 3 {
380					return "", "", false
381				}
382				return s[:i], s[i:], true
383			}
384		}
385		if len(s) < 3 {
386			return "", "", false
387		}
388		return s, "", true
389	} else {
390		for i, r := range s {
391			if r == '>' {
392				return s[1:i], s[i+1:], true
393			}
394		}
395		return "", "", false
396	}
397}
398
399// tzsetOffset returns the timezone offset at the start of the tzset string s,
400// and the remainder of s, and reports whether the parsing is OK.
401// The timezone offset is returned as a number of seconds.
402func tzsetOffset(s string) (offset int, rest string, ok bool) {
403	if len(s) == 0 {
404		return 0, "", false
405	}
406	neg := false
407	if s[0] == '+' {
408		s = s[1:]
409	} else if s[0] == '-' {
410		s = s[1:]
411		neg = true
412	}
413
414	// The tzdata code permits values up to 24 * 7 here,
415	// although POSIX does not.
416	var hours int
417	hours, s, ok = tzsetNum(s, 0, 24*7)
418	if !ok {
419		return 0, "", false
420	}
421	off := hours * secondsPerHour
422	if len(s) == 0 || s[0] != ':' {
423		if neg {
424			off = -off
425		}
426		return off, s, true
427	}
428
429	var mins int
430	mins, s, ok = tzsetNum(s[1:], 0, 59)
431	if !ok {
432		return 0, "", false
433	}
434	off += mins * secondsPerMinute
435	if len(s) == 0 || s[0] != ':' {
436		if neg {
437			off = -off
438		}
439		return off, s, true
440	}
441
442	var secs int
443	secs, s, ok = tzsetNum(s[1:], 0, 59)
444	if !ok {
445		return 0, "", false
446	}
447	off += secs
448
449	if neg {
450		off = -off
451	}
452	return off, s, true
453}
454
455// ruleKind is the kinds of rules that can be seen in a tzset string.
456type ruleKind int
457
458const (
459	ruleJulian ruleKind = iota
460	ruleDOY
461	ruleMonthWeekDay
462)
463
464// rule is a rule read from a tzset string.
465type rule struct {
466	kind ruleKind
467	day  int
468	week int
469	mon  int
470	time int // transition time
471}
472
473// tzsetRule parses a rule from a tzset string.
474// It returns the rule, and the remainder of the string, and reports success.
475func tzsetRule(s string) (rule, string, bool) {
476	var r rule
477	if len(s) == 0 {
478		return rule{}, "", false
479	}
480	ok := false
481	if s[0] == 'J' {
482		var jday int
483		jday, s, ok = tzsetNum(s[1:], 1, 365)
484		if !ok {
485			return rule{}, "", false
486		}
487		r.kind = ruleJulian
488		r.day = jday
489	} else if s[0] == 'M' {
490		var mon int
491		mon, s, ok = tzsetNum(s[1:], 1, 12)
492		if !ok || len(s) == 0 || s[0] != '.' {
493			return rule{}, "", false
494
495		}
496		var week int
497		week, s, ok = tzsetNum(s[1:], 1, 5)
498		if !ok || len(s) == 0 || s[0] != '.' {
499			return rule{}, "", false
500		}
501		var day int
502		day, s, ok = tzsetNum(s[1:], 0, 6)
503		if !ok {
504			return rule{}, "", false
505		}
506		r.kind = ruleMonthWeekDay
507		r.day = day
508		r.week = week
509		r.mon = mon
510	} else {
511		var day int
512		day, s, ok = tzsetNum(s, 0, 365)
513		if !ok {
514			return rule{}, "", false
515		}
516		r.kind = ruleDOY
517		r.day = day
518	}
519
520	if len(s) == 0 || s[0] != '/' {
521		r.time = 2 * secondsPerHour // 2am is the default
522		return r, s, true
523	}
524
525	offset, s, ok := tzsetOffset(s[1:])
526	if !ok {
527		return rule{}, "", false
528	}
529	r.time = offset
530
531	return r, s, true
532}
533
534// tzsetNum parses a number from a tzset string.
535// It returns the number, and the remainder of the string, and reports success.
536// The number must be between min and max.
537func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
538	if len(s) == 0 {
539		return 0, "", false
540	}
541	num = 0
542	for i, r := range s {
543		if r < '0' || r > '9' {
544			if i == 0 || num < min {
545				return 0, "", false
546			}
547			return num, s[i:], true
548		}
549		num *= 10
550		num += int(r) - '0'
551		if num > max {
552			return 0, "", false
553		}
554	}
555	if num < min {
556		return 0, "", false
557	}
558	return num, "", true
559}
560
561// tzruleTime takes a year, a rule, and a timezone offset,
562// and returns the number of seconds since the start of the year
563// that the rule takes effect.
564func tzruleTime(year int, r rule, off int) int {
565	var s int
566	switch r.kind {
567	case ruleJulian:
568		s = (r.day - 1) * secondsPerDay
569		if isLeap(year) && r.day >= 60 {
570			s += secondsPerDay
571		}
572	case ruleDOY:
573		s = r.day * secondsPerDay
574	case ruleMonthWeekDay:
575		// Zeller's Congruence.
576		m1 := (r.mon+9)%12 + 1
577		yy0 := year
578		if r.mon <= 2 {
579			yy0--
580		}
581		yy1 := yy0 / 100
582		yy2 := yy0 % 100
583		dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
584		if dow < 0 {
585			dow += 7
586		}
587		// Now dow is the day-of-week of the first day of r.mon.
588		// Get the day-of-month of the first "dow" day.
589		d := r.day - dow
590		if d < 0 {
591			d += 7
592		}
593		for i := 1; i < r.week; i++ {
594			if d+7 >= daysIn(Month(r.mon), year) {
595				break
596			}
597			d += 7
598		}
599		d += int(daysBefore[r.mon-1])
600		if isLeap(year) && r.mon > 2 {
601			d++
602		}
603		s = d * secondsPerDay
604	}
605
606	return s + r.time - off
607}
608
609// lookupName returns information about the time zone with
610// the given name (such as "EST") at the given pseudo-Unix time
611// (what the given time of day would be in UTC).
612func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
613	l = l.get()
614
615	// First try for a zone with the right name that was actually
616	// in effect at the given time. (In Sydney, Australia, both standard
617	// and daylight-savings time are abbreviated "EST". Using the
618	// offset helps us pick the right one for the given time.
619	// It's not perfect: during the backward transition we might pick
620	// either one.)
621	for i := range l.zone {
622		zone := &l.zone[i]
623		if zone.name == name {
624			nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
625			if nam == zone.name {
626				return offset, true
627			}
628		}
629	}
630
631	// Otherwise fall back to an ordinary name match.
632	for i := range l.zone {
633		zone := &l.zone[i]
634		if zone.name == name {
635			return zone.offset, true
636		}
637	}
638
639	// Otherwise, give up.
640	return
641}
642
643// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
644// syntax too, but I don't feel like implementing it today.
645
646var errLocation = errors.New("time: invalid location name")
647
648var zoneinfo *string
649var zoneinfoOnce sync.Once
650
651// LoadLocation returns the Location with the given name.
652//
653// If the name is "" or "UTC", LoadLocation returns UTC.
654// If the name is "Local", LoadLocation returns Local.
655//
656// Otherwise, the name is taken to be a location name corresponding to a file
657// in the IANA Time Zone database, such as "America/New_York".
658//
659// LoadLocation looks for the IANA Time Zone database in the following
660// locations in order:
661//
662//   - the directory or uncompressed zip file named by the ZONEINFO environment variable
663//   - on a Unix system, the system standard installation location
664//   - $GOROOT/lib/time/zoneinfo.zip
665//   - the time/tzdata package, if it was imported
666func LoadLocation(name string) (*Location, error) {
667	if name == "" || name == "UTC" {
668		return UTC, nil
669	}
670	if name == "Local" {
671		return Local, nil
672	}
673	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
674		// No valid IANA Time Zone name contains a single dot,
675		// much less dot dot. Likewise, none begin with a slash.
676		return nil, errLocation
677	}
678	zoneinfoOnce.Do(func() {
679		env, _ := syscall.Getenv("ZONEINFO")
680		zoneinfo = &env
681	})
682	var firstErr error
683	if *zoneinfo != "" {
684		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
685			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
686				return z, nil
687			}
688			firstErr = err
689		} else if err != syscall.ENOENT {
690			firstErr = err
691		}
692	}
693	if z, err := loadLocation(name, platformZoneSources); err == nil {
694		return z, nil
695	} else if firstErr == nil {
696		firstErr = err
697	}
698	return nil, firstErr
699}
700
701// containsDotDot reports whether s contains "..".
702func containsDotDot(s string) bool {
703	if len(s) < 2 {
704		return false
705	}
706	for i := 0; i < len(s)-1; i++ {
707		if s[i] == '.' && s[i+1] == '.' {
708			return true
709		}
710	}
711	return false
712}
713