blob: c44477f4743341115b341f59f7cfdcbf583c52a6 [file] [log] [blame]
Russ Coxefe3d352011-11-30 11:59:44 -05001// 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
Russ Coxad17a9c2012-02-18 21:02:41 -05007import (
8 "sync"
9 "syscall"
10)
Russ Coxefe3d352011-11-30 11:59:44 -050011
12// A Location maps time instants to the zone in use at that time.
13// Typically, the Location represents the collection of time offsets
14// in use in a geographical area, such as CEST and CET for central Europe.
15type Location struct {
16 name string
17 zone []zone
18 tx []zoneTrans
19
20 // Most lookups will be for the current time.
21 // To avoid the binary search through tx, keep a
22 // static one-element cache that gives the correct
23 // zone for the time when the Location was created.
24 // if cacheStart <= t <= cacheEnd,
25 // lookup can return cacheZone.
26 // The units for cacheStart and cacheEnd are seconds
27 // since January 1, 1970 UTC, to match the argument
28 // to lookup.
29 cacheStart int64
30 cacheEnd int64
31 cacheZone *zone
32}
33
34// A zone represents a single time zone such as CEST or CET.
35type zone struct {
36 name string // abbreviated name, "CET"
37 offset int // seconds east of UTC
38 isDST bool // is this zone Daylight Savings Time?
39}
40
41// A zoneTrans represents a single time zone transition.
42type zoneTrans struct {
43 when int64 // transition time, in seconds since 1970 GMT
44 index uint8 // the index of the zone that goes into effect at that time
45 isstd, isutc bool // ignored - no idea what these mean
46}
47
48// UTC represents Universal Coordinated Time (UTC).
49var UTC *Location = &utcLoc
50
51// utcLoc is separate so that get can refer to &utcLoc
52// and ensure that it never returns a nil *Location,
53// even if a badly behaved client has changed UTC.
54var utcLoc = Location{name: "UTC"}
55
56// Local represents the system's local time zone.
57var Local *Location = &localLoc
58
59// localLoc is separate so that initLocal can initialize
60// it even if a client has changed Local.
61var localLoc Location
62var localOnce sync.Once
63
64func (l *Location) get() *Location {
65 if l == nil {
66 return &utcLoc
67 }
68 if l == &localLoc {
69 localOnce.Do(initLocal)
70 }
71 return l
72}
73
74// String returns a descriptive name for the time zone information,
75// corresponding to the argument to LoadLocation.
76func (l *Location) String() string {
77 return l.get().name
78}
79
80// FixedZone returns a Location that always uses
81// the given zone name and offset (seconds east of UTC).
82func FixedZone(name string, offset int) *Location {
83 l := &Location{
84 name: name,
85 zone: []zone{{name, offset, false}},
86 tx: []zoneTrans{{-1 << 63, 0, false, false}},
87 cacheStart: -1 << 63,
88 cacheEnd: 1<<63 - 1,
89 }
90 l.cacheZone = &l.zone[0]
91 return l
92}
93
94// lookup returns information about the time zone in use at an
95// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
96//
97// The returned information gives the name of the zone (such as "CET"),
98// the start and end times bracketing sec when that zone is in effect,
99// the offset in seconds east of UTC (such as -5*60*60), and whether
100// the daylight savings is being observed at that time.
101func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
102 l = l.get()
103
104 if len(l.tx) == 0 {
105 name = "UTC"
106 offset = 0
107 isDST = false
108 start = -1 << 63
109 end = 1<<63 - 1
110 return
111 }
112
113 if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
114 name = zone.name
115 offset = zone.offset
116 isDST = zone.isDST
117 start = l.cacheStart
118 end = l.cacheEnd
119 return
120 }
121
122 // Binary search for entry with largest time <= sec.
123 // Not using sort.Search to avoid dependencies.
124 tx := l.tx
125 end = 1<<63 - 1
Russ Coxa76c8b22012-06-03 11:08:17 -0400126 lo := 0
127 hi := len(tx)
128 for hi-lo > 1 {
129 m := lo + (hi-lo)/2
Russ Coxefe3d352011-11-30 11:59:44 -0500130 lim := tx[m].when
131 if sec < lim {
132 end = lim
Russ Coxa76c8b22012-06-03 11:08:17 -0400133 hi = m
Russ Coxefe3d352011-11-30 11:59:44 -0500134 } else {
Russ Coxa76c8b22012-06-03 11:08:17 -0400135 lo = m
Russ Coxefe3d352011-11-30 11:59:44 -0500136 }
137 }
Russ Coxa76c8b22012-06-03 11:08:17 -0400138 zone := &l.zone[tx[lo].index]
Russ Coxefe3d352011-11-30 11:59:44 -0500139 name = zone.name
140 offset = zone.offset
141 isDST = zone.isDST
Russ Coxa76c8b22012-06-03 11:08:17 -0400142 start = tx[lo].when
Russ Coxefe3d352011-11-30 11:59:44 -0500143 // end = maintained during the search
144 return
145}
146
147// lookupName returns information about the time zone with
Russ Cox1d9f67d2013-02-03 23:02:12 -0500148// the given name (such as "EST") at the given pseudo-Unix time
149// (what the given time of day would be in UTC).
150func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) {
Russ Coxefe3d352011-11-30 11:59:44 -0500151 l = l.get()
Russ Cox1d9f67d2013-02-03 23:02:12 -0500152
153 // First try for a zone with the right name that was actually
154 // in effect at the given time. (In Sydney, Australia, both standard
155 // and daylight-savings time are abbreviated "EST". Using the
156 // offset helps us pick the right one for the given time.
157 // It's not perfect: during the backward transition we might pick
158 // either one.)
159 for i := range l.zone {
160 zone := &l.zone[i]
161 if zone.name == name {
162 nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset))
163 if nam == zone.name {
164 return offset, isDST, true
165 }
166 }
167 }
168
169 // Otherwise fall back to an ordinary name match.
Russ Coxefe3d352011-11-30 11:59:44 -0500170 for i := range l.zone {
171 zone := &l.zone[i]
172 if zone.name == name {
173 return zone.offset, zone.isDST, true
174 }
175 }
Russ Cox1d9f67d2013-02-03 23:02:12 -0500176
177 // Otherwise, give up.
Russ Coxefe3d352011-11-30 11:59:44 -0500178 return
179}
180
181// lookupOffset returns information about the time zone with
182// the given offset (such as -5*60*60).
183func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) {
184 l = l.get()
185 for i := range l.zone {
186 zone := &l.zone[i]
187 if zone.offset == offset {
188 return zone.name, zone.isDST, true
189 }
190 }
191 return
192}
193
194// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
195// syntax too, but I don't feel like implementing it today.
196
Russ Coxad17a9c2012-02-18 21:02:41 -0500197var zoneinfo, _ = syscall.Getenv("ZONEINFO")
Russ Coxefe3d352011-11-30 11:59:44 -0500198
199// LoadLocation returns the Location with the given name.
200//
201// If the name is "" or "UTC", LoadLocation returns UTC.
202// If the name is "Local", LoadLocation returns Local.
203//
204// Otherwise, the name is taken to be a location name corresponding to a file
205// in the IANA Time Zone database, such as "America/New_York".
Russ Coxad17a9c2012-02-18 21:02:41 -0500206//
207// The time zone database needed by LoadLocation may not be
208// present on all systems, especially non-Unix systems.
Russ Coxcb5e1812012-02-19 03:16:20 -0500209// LoadLocation looks in the directory or uncompressed zip file
210// named by the ZONEINFO environment variable, if any, then looks in
211// known installation locations on Unix systems,
212// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
Russ Coxefe3d352011-11-30 11:59:44 -0500213func LoadLocation(name string) (*Location, error) {
214 if name == "" || name == "UTC" {
215 return UTC, nil
216 }
217 if name == "Local" {
218 return Local, nil
219 }
Russ Coxad17a9c2012-02-18 21:02:41 -0500220 if zoneinfo != "" {
Russ Coxcb5e1812012-02-19 03:16:20 -0500221 if z, err := loadZoneFile(zoneinfo, name); err == nil {
Russ Coxad17a9c2012-02-18 21:02:41 -0500222 z.name = name
223 return z, nil
224 }
225 }
Russ Coxefe3d352011-11-30 11:59:44 -0500226 return loadLocation(name)
227}