Home | History | Annotate | Download | only in time
      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 
      5 package time
      6 
      7 import (
      8 	"sync"
      9 	"syscall"
     10 )
     11 
     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 {
     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.
     35 type 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.
     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
     46 }
     47 
     48 // alpha and omega are the beginning and end of time for zone
     49 // transitions.
     50 const (
     51 	alpha = -1 << 63  // math.MinInt64
     52 	omega = 1<<63 - 1 // math.MaxInt64
     53 )
     54 
     55 // UTC represents Universal Coordinated Time (UTC).
     56 var 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.
     61 var utcLoc = Location{name: "UTC"}
     62 
     63 // Local represents the system's local time zone.
     64 var Local *Location = &localLoc
     65 
     66 // localLoc is separate so that initLocal can initialize
     67 // it even if a client has changed Local.
     68 var localLoc Location
     69 var localOnce sync.Once
     70 
     71 func (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.
     83 func (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).
     89 func FixedZone(name string, offset int) *Location {
     90 	l := &Location{
     91 		name:       name,
     92 		zone:       []zone{{name, offset, false}},
     93 		tx:         []zoneTrans{{alpha, 0, false, false}},
     94 		cacheStart: alpha,
     95 		cacheEnd:   omega,
     96 	}
     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.
    108 func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
    109 	l = l.get()
    110 
    111 	if len(l.zone) == 0 {
    112 		name = "UTC"
    113 		offset = 0
    114 		isDST = false
    115 		start = alpha
    116 		end = omega
    117 		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 
    129 	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
    134 		start = alpha
    135 		if len(l.tx) > 0 {
    136 			end = l.tx[0].when
    137 		} else {
    138 			end = omega
    139 		}
    140 		return
    141 	}
    142 
    143 	// Binary search for entry with largest time <= sec.
    144 	// Not using sort.Search to avoid dependencies.
    145 	tx := l.tx
    146 	end = omega
    147 	lo := 0
    148 	hi := len(tx)
    149 	for hi-lo > 1 {
    150 		m := lo + (hi-lo)/2
    151 		lim := tx[m].when
    152 		if sec < lim {
    153 			end = lim
    154 			hi = m
    155 		} else {
    156 			lo = m
    157 		}
    158 	}
    159 	zone := &l.zone[tx[lo].index]
    160 	name = zone.name
    161 	offset = zone.offset
    162 	isDST = zone.isDST
    163 	start = tx[lo].when
    164 	// end = maintained during the search
    165 	return
    166 }
    167 
    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
    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.
    183 func (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.
    211 func (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 
    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) {
    224 	l = l.get()
    225 
    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.
    243 	for i := range l.zone {
    244 		zone := &l.zone[i]
    245 		if zone.name == name {
    246 			return zone.offset, zone.isDST, true
    247 		}
    248 	}
    249 
    250 	// Otherwise, give up.
    251 	return
    252 }
    253 
    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.
    256 
    257 var zoneinfo, _ = syscall.Getenv("ZONEINFO")
    258 
    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".
    266 //
    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" {
    275 		return UTC, nil
    276 	}
    277 	if name == "Local" {
    278 		return Local, nil
    279 	}
    280 	if zoneinfo != "" {
    281 		if z, err := loadZoneFile(zoneinfo, name); err == nil {
    282 			z.name = name
    283 			return z, nil
    284 		}
    285 	}
    286 	return loadLocation(name)
    287 }
    288