...
Run Format

Source file src/time/zoneinfo.go

     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	

View as plain text