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.
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.
15 type Location
struct {
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
34 // A zone represents a single time zone such as CEST or CET.
36 name
string // abbreviated name, "CET"
37 offset
int // seconds east of UTC
38 isDST
bool // is this zone Daylight Savings Time?
41 // A zoneTrans represents a single time zone transition.
42 type 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
48 // alpha and omega are the beginning and end of time for zone
51 alpha
= -1 << 63 // math.MinInt64
52 omega
= 1<<63 - 1 // math.MaxInt64
55 // UTC represents Universal Coordinated Time (UTC).
56 var UTC
*Location
= &utcLoc
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.
61 var utcLoc
= Location
{name
: "UTC"}
63 // Local represents the system's local time zone.
64 var Local
*Location
= &localLoc
66 // localLoc is separate so that initLocal can initialize
67 // it even if a client has changed Local.
69 var localOnce sync
.Once
71 func (l
*Location
) get() *Location
{
76 localOnce
.Do(initLocal
)
81 // String returns a descriptive name for the time zone information,
82 // corresponding to the argument to LoadLocation.
83 func (l
*Location
) String() string {
87 // FixedZone returns a Location that always uses
88 // the given zone name and offset (seconds east of UTC).
89 func FixedZone(name
string, offset
int) *Location
{
92 zone
: []zone
{{name
, offset
, false}},
93 tx
: []zoneTrans
{{alpha
, 0, false, false}},
97 l
.cacheZone
= &l
.zone
[0]
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.
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.
108 func (l
*Location
) lookup(sec
int64) (name
string, offset
int, isDST
bool, start
, end
int64) {
111 if len(l
.zone
) == 0 {
120 if zone
:= l
.cacheZone
; zone
!= nil && l
.cacheStart
<= sec
&& sec
< l
.cacheEnd
{
129 if len(l
.tx
) == 0 || sec
< l
.tx
[0].when
{
130 zone
:= &l
.zone
[l
.lookupFirstZone()]
143 // Binary search for entry with largest time <= sec.
144 // Not using sort.Search to avoid dependencies.
159 zone
:= &l
.zone
[tx
[lo
].index
]
164 // end = maintained during the search
168 // lookupFirstZone returns the index of the time zone to use for times
169 // before the first transition time, or when there are no transition
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
180 // 3) Otherwise, use the first zone that is not daylight time, if
182 // 4) Otherwise, use the first zone.
183 func (l
*Location
) lookupFirstZone() int {
185 if !l
.firstZoneUsed() {
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
{
199 for zi
:= range l
.zone
{
200 if !l
.zone
[zi
].isDST
{
209 // firstZoneUsed returns whether the first zone is used by some
211 func (l
*Location
) firstZoneUsed() bool {
212 for _
, tx
:= range l
.tx
{
220 // lookupName returns information about the time zone with
221 // the given name (such as "EST") at the given pseudo-Unix time
222 // (what the given time of day would be in UTC).
223 func (l
*Location
) lookupName(name
string, unix
int64) (offset
int, isDST
bool, ok
bool) {
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
232 for i
:= range l
.zone
{
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
242 // Otherwise fall back to an ordinary name match.
243 for i
:= range l
.zone
{
245 if zone
.name
== name
{
246 return zone
.offset
, zone
.isDST
, true
250 // Otherwise, give up.
254 // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
255 // syntax too, but I don't feel like implementing it today.
257 var zoneinfo
, _
= syscall
.Getenv("ZONEINFO")
259 // LoadLocation returns the Location with the given name.
261 // If the name is "" or "UTC", LoadLocation returns UTC.
262 // If the name is "Local", LoadLocation returns Local.
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".
267 // The time zone database needed by LoadLocation may not be
268 // present on all systems, especially non-Unix systems.
269 // 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.
273 func LoadLocation(name
string) (*Location
, error
) {
274 if name
== "" || name
== "UTC" {
281 if z
, err
:= loadZoneFile(zoneinfo
, name
); err
== nil {
286 return loadLocation(name
)