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.
13 //go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go
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 type Location
struct {
24 // The tzdata information can be followed by a string that describes
25 // how to handle DST transitions not recorded in zoneTrans.
26 // The format is the TZ environment variable without a colon; see
27 // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html.
28 // Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0
31 // Most lookups will be for the current time.
32 // To avoid the binary search through tx, keep a
33 // static one-element cache that gives the correct
34 // zone for the time when the Location was created.
35 // if cacheStart <= t < cacheEnd,
36 // lookup can return cacheZone.
37 // The units for cacheStart and cacheEnd are seconds
38 // since January 1, 1970 UTC, to match the argument
45 // A zone represents a single time zone such as CET.
47 name
string // abbreviated name, "CET"
48 offset
int // seconds east of UTC
49 isDST
bool // is this zone Daylight Savings Time?
52 // A zoneTrans represents a single time zone transition.
53 type zoneTrans
struct {
54 when
int64 // transition time, in seconds since 1970 GMT
55 index
uint8 // the index of the zone that goes into effect at that time
56 isstd
, isutc
bool // ignored - no idea what these mean
59 // alpha and omega are the beginning and end of time for zone
62 alpha
= -1 << 63 // math.MinInt64
63 omega
= 1<<63 - 1 // math.MaxInt64
66 // UTC represents Universal Coordinated Time (UTC).
67 var UTC
*Location
= &utcLoc
69 // utcLoc is separate so that get can refer to &utcLoc
70 // and ensure that it never returns a nil *Location,
71 // even if a badly behaved client has changed UTC.
72 var utcLoc
= Location
{name
: "UTC"}
74 // Local represents the system's local time zone.
75 // On Unix systems, Local consults the TZ environment
76 // variable to find the time zone to use. No TZ means
77 // use the system default /etc/localtime.
78 // TZ="" means use UTC.
79 // TZ="foo" means use file foo in the system timezone directory.
80 var Local
*Location
= &localLoc
82 // localLoc is separate so that initLocal can initialize
83 // it even if a client has changed Local.
85 var localOnce sync
.Once
87 func (l
*Location
) get() *Location
{
92 localOnce
.Do(initLocal
)
97 // String returns a descriptive name for the time zone information,
98 // corresponding to the name argument to LoadLocation or FixedZone.
99 func (l
*Location
) String() string {
103 // FixedZone returns a Location that always uses
104 // the given zone name and offset (seconds east of UTC).
105 func FixedZone(name
string, offset
int) *Location
{
108 zone
: []zone
{{name
, offset
, false}},
109 tx
: []zoneTrans
{{alpha
, 0, false, false}},
113 l
.cacheZone
= &l
.zone
[0]
117 // lookup returns information about the time zone in use at an
118 // instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
120 // The returned information gives the name of the zone (such as "CET"),
121 // the start and end times bracketing sec when that zone is in effect,
122 // the offset in seconds east of UTC (such as -5*60*60), and whether
123 // the daylight savings is being observed at that time.
124 func (l
*Location
) lookup(sec
int64) (name
string, offset
int, start
, end
int64, isDST
bool) {
127 if len(l
.zone
) == 0 {
136 if zone
:= l
.cacheZone
; zone
!= nil && l
.cacheStart
<= sec
&& sec
< l
.cacheEnd
{
145 if len(l
.tx
) == 0 || sec
< l
.tx
[0].when
{
146 zone
:= &l
.zone
[l
.lookupFirstZone()]
159 // Binary search for entry with largest time <= sec.
160 // Not using sort.Search to avoid dependencies.
175 zone
:= &l
.zone
[tx
[lo
].index
]
179 // end = maintained during the search
182 // If we're at the end of the known zone transitions,
183 // try the extend string.
184 if lo
== len(tx
)-1 && l
.extend
!= "" {
185 if ename
, eoffset
, estart
, eend
, eisDST
, ok
:= tzset(l
.extend
, end
, sec
); ok
{
186 return ename
, eoffset
, estart
, eend
, eisDST
193 // lookupFirstZone returns the index of the time zone to use for times
194 // before the first transition time, or when there are no transition
197 // The reference implementation in localtime.c from
198 // https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
199 // implements the following algorithm for these cases:
200 // 1) If the first zone is unused by the transitions, use it.
201 // 2) Otherwise, if there are transition times, and the first
202 // transition is to a zone in daylight time, find the first
203 // non-daylight-time zone before and closest to the first transition
205 // 3) Otherwise, use the first zone that is not daylight time, if
207 // 4) Otherwise, use the first zone.
208 func (l
*Location
) lookupFirstZone() int {
210 if !l
.firstZoneUsed() {
215 if len(l
.tx
) > 0 && l
.zone
[l
.tx
[0].index
].isDST
{
216 for zi
:= int(l
.tx
[0].index
) - 1; zi
>= 0; zi
-- {
217 if !l
.zone
[zi
].isDST
{
224 for zi
:= range l
.zone
{
225 if !l
.zone
[zi
].isDST
{
234 // firstZoneUsed reports whether the first zone is used by some
236 func (l
*Location
) firstZoneUsed() bool {
237 for _
, tx
:= range l
.tx
{
245 // tzset takes a timezone string like the one found in the TZ environment
246 // variable, the end of the last time zone transition expressed as seconds
247 // since January 1, 1970 00:00:00 UTC, and a time expressed the same way.
248 // We call this a tzset string since in C the function tzset reads TZ.
249 // The return values are as for lookup, plus ok which reports whether the
251 func tzset(s
string, initEnd
, sec
int64) (name
string, offset
int, start
, end
int64, isDST
, ok
bool) {
253 stdName
, dstName
string
254 stdOffset
, dstOffset
int
257 stdName
, s
, ok
= tzsetName(s
)
259 stdOffset
, s
, ok
= tzsetOffset(s
)
262 return "", 0, 0, 0, false, false
265 // The numbers in the tzset string are added to local time to get UTC,
266 // but our offsets are added to UTC to get local time,
267 // so we negate the number we see here.
268 stdOffset
= -stdOffset
270 if len(s
) == 0 || s
[0] == ',' {
271 // No daylight savings time.
272 return stdName
, stdOffset
, initEnd
, omega
, false, true
275 dstName
, s
, ok
= tzsetName(s
)
277 if len(s
) == 0 || s
[0] == ',' {
278 dstOffset
= stdOffset
+ secondsPerHour
280 dstOffset
, s
, ok
= tzsetOffset(s
)
281 dstOffset
= -dstOffset
// as with stdOffset, above
285 return "", 0, 0, 0, false, false
289 // Default DST rules per tzcode.
290 s
= ",M3.2.0,M11.1.0"
292 // The TZ definition does not mention ';' here but tzcode accepts it.
293 if s
[0] != ',' && s
[0] != ';' {
294 return "", 0, 0, 0, false, false
298 var startRule
, endRule rule
299 startRule
, s
, ok
= tzsetRule(s
)
300 if !ok ||
len(s
) == 0 || s
[0] != ',' {
301 return "", 0, 0, 0, false, false
304 endRule
, s
, ok
= tzsetRule(s
)
305 if !ok ||
len(s
) > 0 {
306 return "", 0, 0, 0, false, false
309 year
, _
, _
, yday
:= absDate(uint64(sec
+unixToInternal
+internalToAbsolute
), false)
311 ysec
:= int64(yday
*secondsPerDay
) + sec%secondsPerDay
313 // Compute start of year in seconds since Unix epoch.
314 d
:= daysSinceEpoch(year
)
315 abs
:= int64(d
* secondsPerDay
)
316 abs
+= absoluteToInternal
+ internalToUnix
318 startSec
:= int64(tzruleTime(year
, startRule
, stdOffset
))
319 endSec
:= int64(tzruleTime(year
, endRule
, dstOffset
))
320 dstIsDST
, stdIsDST
:= true, false
321 // Note: this is a flipping of "DST" and "STD" while retaining the labels
322 // This happens in southern hemispheres. The labelling here thus is a little
323 // inconsistent with the goal.
324 if endSec
< startSec
{
325 startSec
, endSec
= endSec
, startSec
326 stdName
, dstName
= dstName
, stdName
327 stdOffset
, dstOffset
= dstOffset
, stdOffset
328 stdIsDST
, dstIsDST
= dstIsDST
, stdIsDST
331 // The start and end values that we return are accurate
332 // close to a daylight savings transition, but are otherwise
333 // just the start and end of the year. That suffices for
334 // the only caller that cares, which is Date.
336 return stdName
, stdOffset
, abs
, startSec
+ abs
, stdIsDST
, true
337 } else if ysec
>= endSec
{
338 return stdName
, stdOffset
, endSec
+ abs
, abs
+ 365*secondsPerDay
, stdIsDST
, true
340 return dstName
, dstOffset
, startSec
+ abs
, endSec
+ abs
, dstIsDST
, true
344 // tzsetName returns the timezone name at the start of the tzset string s,
345 // and the remainder of s, and reports whether the parsing is OK.
346 func tzsetName(s
string) (string, string, bool) {
351 for i
, r
:= range s
{
353 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
357 return s
[:i
], s
[i
:], true
365 for i
, r
:= range s
{
367 return s
[1:i
], s
[i
+1:], true
374 // tzsetOffset returns the timezone offset at the start of the tzset string s,
375 // and the remainder of s, and reports whether the parsing is OK.
376 // The timezone offset is returned as a number of seconds.
377 func tzsetOffset(s
string) (offset
int, rest
string, ok
bool) {
384 } else if s
[0] == '-' {
389 // The tzdata code permits values up to 24 * 7 here,
390 // although POSIX does not.
392 hours
, s
, ok
= tzsetNum(s
, 0, 24*7)
396 off
:= hours
* secondsPerHour
397 if len(s
) == 0 || s
[0] != ':' {
405 mins
, s
, ok
= tzsetNum(s
[1:], 0, 59)
409 off
+= mins
* secondsPerMinute
410 if len(s
) == 0 || s
[0] != ':' {
418 secs
, s
, ok
= tzsetNum(s
[1:], 0, 59)
430 // ruleKind is the kinds of rules that can be seen in a tzset string.
434 ruleJulian ruleKind
= iota
439 // rule is a rule read from a tzset string.
445 time
int // transition time
448 // tzsetRule parses a rule from a tzset string.
449 // It returns the rule, and the remainder of the string, and reports success.
450 func tzsetRule(s
string) (rule
, string, bool) {
453 return rule
{}, "", false
458 jday
, s
, ok
= tzsetNum(s
[1:], 1, 365)
460 return rule
{}, "", false
464 } else if s
[0] == 'M' {
466 mon
, s
, ok
= tzsetNum(s
[1:], 1, 12)
467 if !ok ||
len(s
) == 0 || s
[0] != '.' {
468 return rule
{}, "", false
472 week
, s
, ok
= tzsetNum(s
[1:], 1, 5)
473 if !ok ||
len(s
) == 0 || s
[0] != '.' {
474 return rule
{}, "", false
477 day
, s
, ok
= tzsetNum(s
[1:], 0, 6)
479 return rule
{}, "", false
481 r
.kind
= ruleMonthWeekDay
487 day
, s
, ok
= tzsetNum(s
, 0, 365)
489 return rule
{}, "", false
495 if len(s
) == 0 || s
[0] != '/' {
496 r
.time
= 2 * secondsPerHour
// 2am is the default
500 offset
, s
, ok
:= tzsetOffset(s
[1:])
502 return rule
{}, "", false
509 // tzsetNum parses a number from a tzset string.
510 // It returns the number, and the remainder of the string, and reports success.
511 // The number must be between min and max.
512 func tzsetNum(s
string, min
, max
int) (num
int, rest
string, ok
bool) {
517 for i
, r
:= range s
{
518 if r
< '0' || r
> '9' {
519 if i
== 0 || num
< min
{
522 return num
, s
[i
:], true
536 // tzruleTime takes a year, a rule, and a timezone offset,
537 // and returns the number of seconds since the start of the year
538 // that the rule takes effect.
539 func tzruleTime(year
int, r rule
, off
int) int {
543 s
= (r
.day
- 1) * secondsPerDay
544 if isLeap(year
) && r
.day
>= 60 {
548 s
= r
.day
* secondsPerDay
549 case ruleMonthWeekDay
:
550 // Zeller's Congruence.
551 m1
:= (r
.mon
+9)%12
+ 1
558 dow
:= ((26*m1
-2)/10 + 1 + yy2
+ yy2
/4 + yy1
/4 - 2*yy1
) % 7
562 // Now dow is the day-of-week of the first day of r.mon.
563 // Get the day-of-month of the first "dow" day.
568 for i
:= 1; i
< r
.week
; i
++ {
569 if d
+7 >= daysIn(Month(r
.mon
), year
) {
574 d
+= int(daysBefore
[r
.mon
-1])
575 if isLeap(year
) && r
.mon
> 2 {
578 s
= d
* secondsPerDay
581 return s
+ r
.time
- off
584 // lookupName returns information about the time zone with
585 // the given name (such as "EST") at the given pseudo-Unix time
586 // (what the given time of day would be in UTC).
587 func (l
*Location
) lookupName(name
string, unix
int64) (offset
int, ok
bool) {
590 // First try for a zone with the right name that was actually
591 // in effect at the given time. (In Sydney, Australia, both standard
592 // and daylight-savings time are abbreviated "EST". Using the
593 // offset helps us pick the right one for the given time.
594 // It's not perfect: during the backward transition we might pick
596 for i
:= range l
.zone
{
598 if zone
.name
== name
{
599 nam
, offset
, _
, _
, _
:= l
.lookup(unix
- int64(zone
.offset
))
600 if nam
== zone
.name
{
606 // Otherwise fall back to an ordinary name match.
607 for i
:= range l
.zone
{
609 if zone
.name
== name
{
610 return zone
.offset
, true
614 // Otherwise, give up.
618 // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
619 // syntax too, but I don't feel like implementing it today.
621 var errLocation
= errors
.New("time: invalid location name")
624 var zoneinfoOnce sync
.Once
626 // LoadLocation returns the Location with the given name.
628 // If the name is "" or "UTC", LoadLocation returns UTC.
629 // If the name is "Local", LoadLocation returns Local.
631 // Otherwise, the name is taken to be a location name corresponding to a file
632 // in the IANA Time Zone database, such as "America/New_York".
634 // LoadLocation looks for the IANA Time Zone database in the following
635 // locations in order:
637 // - the directory or uncompressed zip file named by the ZONEINFO environment variable
638 // - on a Unix system, the system standard installation location
639 // - $GOROOT/lib/time/zoneinfo.zip
640 // - the time/tzdata package, if it was imported
641 func LoadLocation(name
string) (*Location
, error
) {
642 if name
== "" || name
== "UTC" {
648 if containsDotDot(name
) || name
[0] == '/' || name
[0] == '\\' {
649 // No valid IANA Time Zone name contains a single dot,
650 // much less dot dot. Likewise, none begin with a slash.
651 return nil, errLocation
653 zoneinfoOnce
.Do(func() {
654 env
, _
:= syscall
.Getenv("ZONEINFO")
659 if zoneData
, err
:= loadTzinfoFromDirOrZip(*zoneinfo
, name
); err
== nil {
660 if z
, err
:= LoadLocationFromTZData(name
, zoneData
); err
== nil {
664 } else if err
!= syscall
.ENOENT
{
668 if z
, err
:= loadLocation(name
, zoneSources
); err
== nil {
670 } else if firstErr
== nil {
676 // containsDotDot reports whether s contains "..".
677 func containsDotDot(s
string) bool {
681 for i
:= 0; i
< len(s
)-1; i
++ {
682 if s
[i
] == '.' && s
[i
+1] == '.' {