...
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	//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go
    13	
    14	// A Location maps time instants to the zone in use at that time.
    15	// Typically, the Location represents the collection of time offsets
    16	// in use in a geographical area, such as CEST and CET for central Europe.
    17	type Location struct {
    18		name string
    19		zone []zone
    20		tx   []zoneTrans
    21	
    22		// Most lookups will be for the current time.
    23		// To avoid the binary search through tx, keep a
    24		// static one-element cache that gives the correct
    25		// zone for the time when the Location was created.
    26		// if cacheStart <= t < cacheEnd,
    27		// lookup can return cacheZone.
    28		// The units for cacheStart and cacheEnd are seconds
    29		// since January 1, 1970 UTC, to match the argument
    30		// to lookup.
    31		cacheStart int64
    32		cacheEnd   int64
    33		cacheZone  *zone
    34	}
    35	
    36	// A zone represents a single time zone such as CEST or CET.
    37	type zone struct {
    38		name   string // abbreviated name, "CET"
    39		offset int    // seconds east of UTC
    40		isDST  bool   // is this zone Daylight Savings Time?
    41	}
    42	
    43	// A zoneTrans represents a single time zone transition.
    44	type zoneTrans struct {
    45		when         int64 // transition time, in seconds since 1970 GMT
    46		index        uint8 // the index of the zone that goes into effect at that time
    47		isstd, isutc bool  // ignored - no idea what these mean
    48	}
    49	
    50	// alpha and omega are the beginning and end of time for zone
    51	// transitions.
    52	const (
    53		alpha = -1 << 63  // math.MinInt64
    54		omega = 1<<63 - 1 // math.MaxInt64
    55	)
    56	
    57	// UTC represents Universal Coordinated Time (UTC).
    58	var UTC *Location = &utcLoc
    59	
    60	// utcLoc is separate so that get can refer to &utcLoc
    61	// and ensure that it never returns a nil *Location,
    62	// even if a badly behaved client has changed UTC.
    63	var utcLoc = Location{name: "UTC"}
    64	
    65	// Local represents the system's local time zone.
    66	var Local *Location = &localLoc
    67	
    68	// localLoc is separate so that initLocal can initialize
    69	// it even if a client has changed Local.
    70	var localLoc Location
    71	var localOnce sync.Once
    72	
    73	func (l *Location) get() *Location {
    74		if l == nil {
    75			return &utcLoc
    76		}
    77		if l == &localLoc {
    78			localOnce.Do(initLocal)
    79		}
    80		return l
    81	}
    82	
    83	// String returns a descriptive name for the time zone information,
    84	// corresponding to the argument to LoadLocation.
    85	func (l *Location) String() string {
    86		return l.get().name
    87	}
    88	
    89	// FixedZone returns a Location that always uses
    90	// the given zone name and offset (seconds east of UTC).
    91	func FixedZone(name string, offset int) *Location {
    92		l := &Location{
    93			name:       name,
    94			zone:       []zone{{name, offset, false}},
    95			tx:         []zoneTrans{{alpha, 0, false, false}},
    96			cacheStart: alpha,
    97			cacheEnd:   omega,
    98		}
    99		l.cacheZone = &l.zone[0]
   100		return l
   101	}
   102	
   103	// lookup returns information about the time zone in use at an
   104	// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
   105	//
   106	// The returned information gives the name of the zone (such as "CET"),
   107	// the start and end times bracketing sec when that zone is in effect,
   108	// the offset in seconds east of UTC (such as -5*60*60), and whether
   109	// the daylight savings is being observed at that time.
   110	func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
   111		l = l.get()
   112	
   113		if len(l.zone) == 0 {
   114			name = "UTC"
   115			offset = 0
   116			isDST = false
   117			start = alpha
   118			end = omega
   119			return
   120		}
   121	
   122		if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
   123			name = zone.name
   124			offset = zone.offset
   125			isDST = zone.isDST
   126			start = l.cacheStart
   127			end = l.cacheEnd
   128			return
   129		}
   130	
   131		if len(l.tx) == 0 || sec < l.tx[0].when {
   132			zone := &l.zone[l.lookupFirstZone()]
   133			name = zone.name
   134			offset = zone.offset
   135			isDST = zone.isDST
   136			start = alpha
   137			if len(l.tx) > 0 {
   138				end = l.tx[0].when
   139			} else {
   140				end = omega
   141			}
   142			return
   143		}
   144	
   145		// Binary search for entry with largest time <= sec.
   146		// Not using sort.Search to avoid dependencies.
   147		tx := l.tx
   148		end = omega
   149		lo := 0
   150		hi := len(tx)
   151		for hi-lo > 1 {
   152			m := lo + (hi-lo)/2
   153			lim := tx[m].when
   154			if sec < lim {
   155				end = lim
   156				hi = m
   157			} else {
   158				lo = m
   159			}
   160		}
   161		zone := &l.zone[tx[lo].index]
   162		name = zone.name
   163		offset = zone.offset
   164		isDST = zone.isDST
   165		start = tx[lo].when
   166		// end = maintained during the search
   167		return
   168	}
   169	
   170	// lookupFirstZone returns the index of the time zone to use for times
   171	// before the first transition time, or when there are no transition
   172	// times.
   173	//
   174	// The reference implementation in localtime.c from
   175	// http://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
   176	// implements the following algorithm for these cases:
   177	// 1) If the first zone is unused by the transitions, use it.
   178	// 2) Otherwise, if there are transition times, and the first
   179	//    transition is to a zone in daylight time, find the first
   180	//    non-daylight-time zone before and closest to the first transition
   181	//    zone.
   182	// 3) Otherwise, use the first zone that is not daylight time, if
   183	//    there is one.
   184	// 4) Otherwise, use the first zone.
   185	func (l *Location) lookupFirstZone() int {
   186		// Case 1.
   187		if !l.firstZoneUsed() {
   188			return 0
   189		}
   190	
   191		// Case 2.
   192		if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
   193			for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
   194				if !l.zone[zi].isDST {
   195					return zi
   196				}
   197			}
   198		}
   199	
   200		// Case 3.
   201		for zi := range l.zone {
   202			if !l.zone[zi].isDST {
   203				return zi
   204			}
   205		}
   206	
   207		// Case 4.
   208		return 0
   209	}
   210	
   211	// firstZoneUsed returns whether the first zone is used by some
   212	// transition.
   213	func (l *Location) firstZoneUsed() bool {
   214		for _, tx := range l.tx {
   215			if tx.index == 0 {
   216				return true
   217			}
   218		}
   219		return false
   220	}
   221	
   222	// lookupName returns information about the time zone with
   223	// the given name (such as "EST") at the given pseudo-Unix time
   224	// (what the given time of day would be in UTC).
   225	func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) {
   226		l = l.get()
   227	
   228		// First try for a zone with the right name that was actually
   229		// in effect at the given time. (In Sydney, Australia, both standard
   230		// and daylight-savings time are abbreviated "EST". Using the
   231		// offset helps us pick the right one for the given time.
   232		// It's not perfect: during the backward transition we might pick
   233		// either one.)
   234		for i := range l.zone {
   235			zone := &l.zone[i]
   236			if zone.name == name {
   237				nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset))
   238				if nam == zone.name {
   239					return offset, isDST, true
   240				}
   241			}
   242		}
   243	
   244		// Otherwise fall back to an ordinary name match.
   245		for i := range l.zone {
   246			zone := &l.zone[i]
   247			if zone.name == name {
   248				return zone.offset, zone.isDST, true
   249			}
   250		}
   251	
   252		// Otherwise, give up.
   253		return
   254	}
   255	
   256	// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
   257	// syntax too, but I don't feel like implementing it today.
   258	
   259	var zoneinfo, _ = syscall.Getenv("ZONEINFO")
   260	
   261	// LoadLocation returns the Location with the given name.
   262	//
   263	// If the name is "" or "UTC", LoadLocation returns UTC.
   264	// If the name is "Local", LoadLocation returns Local.
   265	//
   266	// Otherwise, the name is taken to be a location name corresponding to a file
   267	// in the IANA Time Zone database, such as "America/New_York".
   268	//
   269	// The time zone database needed by LoadLocation may not be
   270	// present on all systems, especially non-Unix systems.
   271	// LoadLocation looks in the directory or uncompressed zip file
   272	// named by the ZONEINFO environment variable, if any, then looks in
   273	// known installation locations on Unix systems,
   274	// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
   275	func LoadLocation(name string) (*Location, error) {
   276		if name == "" || name == "UTC" {
   277			return UTC, nil
   278		}
   279		if name == "Local" {
   280			return Local, nil
   281		}
   282		if zoneinfo != "" {
   283			if z, err := loadZoneFile(zoneinfo, name); err == nil {
   284				z.name = name
   285				return z, nil
   286			}
   287		}
   288		return loadLocation(name)
   289	}
   290	

View as plain text