...
Run Format

Source file src/time/zoneinfo.go

Documentation: time

  // Copyright 2011 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  package time
  
  import (
  	"sync"
  	"syscall"
  )
  
  //go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go
  
  // A Location maps time instants to the zone in use at that time.
  // Typically, the Location represents the collection of time offsets
  // in use in a geographical area, such as CEST and CET for central Europe.
  type Location struct {
  	name string
  	zone []zone
  	tx   []zoneTrans
  
  	// Most lookups will be for the current time.
  	// To avoid the binary search through tx, keep a
  	// static one-element cache that gives the correct
  	// zone for the time when the Location was created.
  	// if cacheStart <= t < cacheEnd,
  	// lookup can return cacheZone.
  	// The units for cacheStart and cacheEnd are seconds
  	// since January 1, 1970 UTC, to match the argument
  	// to lookup.
  	cacheStart int64
  	cacheEnd   int64
  	cacheZone  *zone
  }
  
  // A zone represents a single time zone such as CEST or CET.
  type zone struct {
  	name   string // abbreviated name, "CET"
  	offset int    // seconds east of UTC
  	isDST  bool   // is this zone Daylight Savings Time?
  }
  
  // A zoneTrans represents a single time zone transition.
  type zoneTrans struct {
  	when         int64 // transition time, in seconds since 1970 GMT
  	index        uint8 // the index of the zone that goes into effect at that time
  	isstd, isutc bool  // ignored - no idea what these mean
  }
  
  // alpha and omega are the beginning and end of time for zone
  // transitions.
  const (
  	alpha = -1 << 63  // math.MinInt64
  	omega = 1<<63 - 1 // math.MaxInt64
  )
  
  // UTC represents Universal Coordinated Time (UTC).
  var UTC *Location = &utcLoc
  
  // utcLoc is separate so that get can refer to &utcLoc
  // and ensure that it never returns a nil *Location,
  // even if a badly behaved client has changed UTC.
  var utcLoc = Location{name: "UTC"}
  
  // Local represents the system's local time zone.
  var Local *Location = &localLoc
  
  // localLoc is separate so that initLocal can initialize
  // it even if a client has changed Local.
  var localLoc Location
  var localOnce sync.Once
  
  func (l *Location) get() *Location {
  	if l == nil {
  		return &utcLoc
  	}
  	if l == &localLoc {
  		localOnce.Do(initLocal)
  	}
  	return l
  }
  
  // String returns a descriptive name for the time zone information,
  // corresponding to the argument to LoadLocation.
  func (l *Location) String() string {
  	return l.get().name
  }
  
  // FixedZone returns a Location that always uses
  // the given zone name and offset (seconds east of UTC).
  func FixedZone(name string, offset int) *Location {
  	l := &Location{
  		name:       name,
  		zone:       []zone{{name, offset, false}},
  		tx:         []zoneTrans{{alpha, 0, false, false}},
  		cacheStart: alpha,
  		cacheEnd:   omega,
  	}
  	l.cacheZone = &l.zone[0]
  	return l
  }
  
  // lookup returns information about the time zone in use at an
  // instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
  //
  // The returned information gives the name of the zone (such as "CET"),
  // the start and end times bracketing sec when that zone is in effect,
  // the offset in seconds east of UTC (such as -5*60*60), and whether
  // the daylight savings is being observed at that time.
  func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
  	l = l.get()
  
  	if len(l.zone) == 0 {
  		name = "UTC"
  		offset = 0
  		isDST = false
  		start = alpha
  		end = omega
  		return
  	}
  
  	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
  		name = zone.name
  		offset = zone.offset
  		isDST = zone.isDST
  		start = l.cacheStart
  		end = l.cacheEnd
  		return
  	}
  
  	if len(l.tx) == 0 || sec < l.tx[0].when {
  		zone := &l.zone[l.lookupFirstZone()]
  		name = zone.name
  		offset = zone.offset
  		isDST = zone.isDST
  		start = alpha
  		if len(l.tx) > 0 {
  			end = l.tx[0].when
  		} else {
  			end = omega
  		}
  		return
  	}
  
  	// Binary search for entry with largest time <= sec.
  	// Not using sort.Search to avoid dependencies.
  	tx := l.tx
  	end = omega
  	lo := 0
  	hi := len(tx)
  	for hi-lo > 1 {
  		m := lo + (hi-lo)/2
  		lim := tx[m].when
  		if sec < lim {
  			end = lim
  			hi = m
  		} else {
  			lo = m
  		}
  	}
  	zone := &l.zone[tx[lo].index]
  	name = zone.name
  	offset = zone.offset
  	isDST = zone.isDST
  	start = tx[lo].when
  	// end = maintained during the search
  	return
  }
  
  // lookupFirstZone returns the index of the time zone to use for times
  // before the first transition time, or when there are no transition
  // times.
  //
  // The reference implementation in localtime.c from
  // http://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
  // implements the following algorithm for these cases:
  // 1) If the first zone is unused by the transitions, use it.
  // 2) Otherwise, if there are transition times, and the first
  //    transition is to a zone in daylight time, find the first
  //    non-daylight-time zone before and closest to the first transition
  //    zone.
  // 3) Otherwise, use the first zone that is not daylight time, if
  //    there is one.
  // 4) Otherwise, use the first zone.
  func (l *Location) lookupFirstZone() int {
  	// Case 1.
  	if !l.firstZoneUsed() {
  		return 0
  	}
  
  	// Case 2.
  	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
  		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
  			if !l.zone[zi].isDST {
  				return zi
  			}
  		}
  	}
  
  	// Case 3.
  	for zi := range l.zone {
  		if !l.zone[zi].isDST {
  			return zi
  		}
  	}
  
  	// Case 4.
  	return 0
  }
  
  // firstZoneUsed returns whether the first zone is used by some
  // transition.
  func (l *Location) firstZoneUsed() bool {
  	for _, tx := range l.tx {
  		if tx.index == 0 {
  			return true
  		}
  	}
  	return false
  }
  
  // lookupName returns information about the time zone with
  // the given name (such as "EST") at the given pseudo-Unix time
  // (what the given time of day would be in UTC).
  func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) {
  	l = l.get()
  
  	// First try for a zone with the right name that was actually
  	// in effect at the given time. (In Sydney, Australia, both standard
  	// and daylight-savings time are abbreviated "EST". Using the
  	// offset helps us pick the right one for the given time.
  	// It's not perfect: during the backward transition we might pick
  	// either one.)
  	for i := range l.zone {
  		zone := &l.zone[i]
  		if zone.name == name {
  			nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset))
  			if nam == zone.name {
  				return offset, isDST, true
  			}
  		}
  	}
  
  	// Otherwise fall back to an ordinary name match.
  	for i := range l.zone {
  		zone := &l.zone[i]
  		if zone.name == name {
  			return zone.offset, zone.isDST, true
  		}
  	}
  
  	// Otherwise, give up.
  	return
  }
  
  // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
  // syntax too, but I don't feel like implementing it today.
  
  var zoneinfo, _ = syscall.Getenv("ZONEINFO")
  
  // LoadLocation returns the Location with the given name.
  //
  // If the name is "" or "UTC", LoadLocation returns UTC.
  // If the name is "Local", LoadLocation returns Local.
  //
  // Otherwise, the name is taken to be a location name corresponding to a file
  // in the IANA Time Zone database, such as "America/New_York".
  //
  // The time zone database needed by LoadLocation may not be
  // present on all systems, especially non-Unix systems.
  // LoadLocation looks in the directory or uncompressed zip file
  // named by the ZONEINFO environment variable, if any, then looks in
  // known installation locations on Unix systems,
  // and finally looks in $GOROOT/lib/time/zoneinfo.zip.
  func LoadLocation(name string) (*Location, error) {
  	if name == "" || name == "UTC" {
  		return UTC, nil
  	}
  	if name == "Local" {
  		return Local, nil
  	}
  	if zoneinfo != "" {
  		if z, err := loadZoneFile(zoneinfo, name); err == nil {
  			z.name = name
  			return z, nil
  		}
  	}
  	return loadLocation(name)
  }
  

View as plain text