blob: c8e53a27cf02328ffc8a16909e95dcbea219765d [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
Ian Lance Taylorfabd2612014-01-31 17:22:10 -080048// alpha and omega are the beginning and end of time for zone
49// transitions.
50const (
51 alpha = -1 << 63 // math.MinInt64
52 omega = 1<<63 - 1 // math.MaxInt64
53)
54
Russ Coxefe3d352011-11-30 11:59:44 -050055// UTC represents Universal Coordinated Time (UTC).
56var UTC *Location = &utcLoc
57
58// utcLoc is separate so that get can refer to &utcLoc
59// and ensure that it never returns a nil *Location,
60// even if a badly behaved client has changed UTC.
61var utcLoc = Location{name: "UTC"}
62
63// Local represents the system's local time zone.
64var Local *Location = &localLoc
65
66// localLoc is separate so that initLocal can initialize
67// it even if a client has changed Local.
68var localLoc Location
69var localOnce sync.Once
70
71func (l *Location) get() *Location {
72 if l == nil {
73 return &utcLoc
74 }
75 if l == &localLoc {
76 localOnce.Do(initLocal)
77 }
78 return l
79}
80
81// String returns a descriptive name for the time zone information,
82// corresponding to the argument to LoadLocation.
83func (l *Location) String() string {
84 return l.get().name
85}
86
87// FixedZone returns a Location that always uses
88// the given zone name and offset (seconds east of UTC).
89func FixedZone(name string, offset int) *Location {
90 l := &Location{
91 name: name,
92 zone: []zone{{name, offset, false}},
Ian Lance Taylorfabd2612014-01-31 17:22:10 -080093 tx: []zoneTrans{{alpha, 0, false, false}},
94 cacheStart: alpha,
95 cacheEnd: omega,
Russ Coxefe3d352011-11-30 11:59:44 -050096 }
97 l.cacheZone = &l.zone[0]
98 return l
99}
100
101// lookup returns information about the time zone in use at an
102// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
103//
104// The returned information gives the name of the zone (such as "CET"),
105// the start and end times bracketing sec when that zone is in effect,
106// the offset in seconds east of UTC (such as -5*60*60), and whether
107// the daylight savings is being observed at that time.
108func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
109 l = l.get()
110
Ian Lance Taylor52a73232014-01-31 14:40:13 -0800111 if len(l.zone) == 0 {
Russ Coxefe3d352011-11-30 11:59:44 -0500112 name = "UTC"
113 offset = 0
114 isDST = false
Ian Lance Taylorfabd2612014-01-31 17:22:10 -0800115 start = alpha
116 end = omega
Russ Coxefe3d352011-11-30 11:59:44 -0500117 return
118 }
119
120 if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
121 name = zone.name
122 offset = zone.offset
123 isDST = zone.isDST
124 start = l.cacheStart
125 end = l.cacheEnd
126 return
127 }
128
Ian Lance Taylor52a73232014-01-31 14:40:13 -0800129 if len(l.tx) == 0 || sec < l.tx[0].when {
130 zone := &l.zone[l.lookupFirstZone()]
131 name = zone.name
132 offset = zone.offset
133 isDST = zone.isDST
Ian Lance Taylorfabd2612014-01-31 17:22:10 -0800134 start = alpha
Ian Lance Taylor52a73232014-01-31 14:40:13 -0800135 if len(l.tx) > 0 {
136 end = l.tx[0].when
137 } else {
Ian Lance Taylorfabd2612014-01-31 17:22:10 -0800138 end = omega
Ian Lance Taylor52a73232014-01-31 14:40:13 -0800139 }
140 return
141 }
142
Russ Coxefe3d352011-11-30 11:59:44 -0500143 // Binary search for entry with largest time <= sec.
144 // Not using sort.Search to avoid dependencies.
145 tx := l.tx
Ian Lance Taylorfabd2612014-01-31 17:22:10 -0800146 end = omega
Russ Coxa76c8b22012-06-03 11:08:17 -0400147 lo := 0
148 hi := len(tx)
149 for hi-lo > 1 {
150 m := lo + (hi-lo)/2
Russ Coxefe3d352011-11-30 11:59:44 -0500151 lim := tx[m].when
152 if sec < lim {
153 end = lim
Russ Coxa76c8b22012-06-03 11:08:17 -0400154 hi = m
Russ Coxefe3d352011-11-30 11:59:44 -0500155 } else {
Russ Coxa76c8b22012-06-03 11:08:17 -0400156 lo = m
Russ Coxefe3d352011-11-30 11:59:44 -0500157 }
158 }
Russ Coxa76c8b22012-06-03 11:08:17 -0400159 zone := &l.zone[tx[lo].index]
Russ Coxefe3d352011-11-30 11:59:44 -0500160 name = zone.name
161 offset = zone.offset
162 isDST = zone.isDST
Russ Coxa76c8b22012-06-03 11:08:17 -0400163 start = tx[lo].when
Russ Coxefe3d352011-11-30 11:59:44 -0500164 // end = maintained during the search
165 return
166}
167
Ian Lance Taylor52a73232014-01-31 14:40:13 -0800168// lookupFirstZone returns the index of the time zone to use for times
169// before the first transition time, or when there are no transition
170// times.
171//
172// The reference implementation in localtime.c from
173// http://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
174// implements the following algorithm for these cases:
175// 1) If the first zone is unused by the transitions, use it.
176// 2) Otherwise, if there are transition times, and the first
177// transition is to a zone in daylight time, find the first
178// non-daylight-time zone before and closest to the first transition
179// zone.
180// 3) Otherwise, use the first zone that is not daylight time, if
181// there is one.
182// 4) Otherwise, use the first zone.
183func (l *Location) lookupFirstZone() int {
184 // Case 1.
185 if !l.firstZoneUsed() {
186 return 0
187 }
188
189 // Case 2.
190 if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
191 for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
192 if !l.zone[zi].isDST {
193 return zi
194 }
195 }
196 }
197
198 // Case 3.
199 for zi := range l.zone {
200 if !l.zone[zi].isDST {
201 return zi
202 }
203 }
204
205 // Case 4.
206 return 0
207}
208
209// firstZoneUsed returns whether the first zone is used by some
210// transition.
211func (l *Location) firstZoneUsed() bool {
212 for _, tx := range l.tx {
213 if tx.index == 0 {
214 return true
215 }
216 }
217 return false
218}
219
Russ Coxefe3d352011-11-30 11:59:44 -0500220// lookupName returns information about the time zone with
Russ Cox1d9f67d2013-02-03 23:02:12 -0500221// the given name (such as "EST") at the given pseudo-Unix time
222// (what the given time of day would be in UTC).
223func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) {
Russ Coxefe3d352011-11-30 11:59:44 -0500224 l = l.get()
Russ Cox1d9f67d2013-02-03 23:02:12 -0500225
226 // First try for a zone with the right name that was actually
227 // in effect at the given time. (In Sydney, Australia, both standard
228 // and daylight-savings time are abbreviated "EST". Using the
229 // offset helps us pick the right one for the given time.
230 // It's not perfect: during the backward transition we might pick
231 // either one.)
232 for i := range l.zone {
233 zone := &l.zone[i]
234 if zone.name == name {
235 nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset))
236 if nam == zone.name {
237 return offset, isDST, true
238 }
239 }
240 }
241
242 // Otherwise fall back to an ordinary name match.
Russ Coxefe3d352011-11-30 11:59:44 -0500243 for i := range l.zone {
244 zone := &l.zone[i]
245 if zone.name == name {
246 return zone.offset, zone.isDST, true
247 }
248 }
Russ Cox1d9f67d2013-02-03 23:02:12 -0500249
250 // Otherwise, give up.
Russ Coxefe3d352011-11-30 11:59:44 -0500251 return
252}
253
Russ Coxefe3d352011-11-30 11:59:44 -0500254// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
255// syntax too, but I don't feel like implementing it today.
256
Russ Coxad17a9c2012-02-18 21:02:41 -0500257var zoneinfo, _ = syscall.Getenv("ZONEINFO")
Russ Coxefe3d352011-11-30 11:59:44 -0500258
259// LoadLocation returns the Location with the given name.
260//
261// If the name is "" or "UTC", LoadLocation returns UTC.
262// If the name is "Local", LoadLocation returns Local.
263//
264// Otherwise, the name is taken to be a location name corresponding to a file
265// in the IANA Time Zone database, such as "America/New_York".
Russ Coxad17a9c2012-02-18 21:02:41 -0500266//
267// The time zone database needed by LoadLocation may not be
268// present on all systems, especially non-Unix systems.
Russ Coxcb5e1812012-02-19 03:16:20 -0500269// LoadLocation looks in the directory or uncompressed zip file
270// named by the ZONEINFO environment variable, if any, then looks in
271// known installation locations on Unix systems,
272// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
Russ Coxefe3d352011-11-30 11:59:44 -0500273func LoadLocation(name string) (*Location, error) {
274 if name == "" || name == "UTC" {
275 return UTC, nil
276 }
277 if name == "Local" {
278 return Local, nil
279 }
Russ Coxad17a9c2012-02-18 21:02:41 -0500280 if zoneinfo != "" {
Russ Coxcb5e1812012-02-19 03:16:20 -0500281 if z, err := loadZoneFile(zoneinfo, name); err == nil {
Russ Coxad17a9c2012-02-18 21:02:41 -0500282 z.name = name
283 return z, nil
284 }
285 }
Russ Coxefe3d352011-11-30 11:59:44 -0500286 return loadLocation(name)
287}