...

Source file src/pkg/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		"errors"
     9		"sync"
    10		"syscall"
    11	)
    12	
    13	//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go
    14	
    15	// A Location maps time instants to the zone in use at that time.
    16	// Typically, the Location represents the collection of time offsets
    17	// in use in a geographical area, such as CEST and CET for central Europe.
    18	type Location struct {
    19		name string
    20		zone []zone
    21		tx   []zoneTrans
    22	
    23		// Most lookups will be for the current time.
    24		// To avoid the binary search through tx, keep a
    25		// static one-element cache that gives the correct
    26		// zone for the time when the Location was created.
    27		// if cacheStart <= t < cacheEnd,
    28		// lookup can return cacheZone.
    29		// The units for cacheStart and cacheEnd are seconds
    30		// since January 1, 1970 UTC, to match the argument
    31		// to lookup.
    32		cacheStart int64
    33		cacheEnd   int64
    34		cacheZone  *zone
    35	}
    36	
    37	// A zone represents a single time zone such as CEST or CET.
    38	type zone struct {
    39		name   string // abbreviated name, "CET"
    40		offset int    // seconds east of UTC
    41		isDST  bool   // is this zone Daylight Savings Time?
    42	}
    43	
    44	// A zoneTrans represents a single time zone transition.
    45	type zoneTrans struct {
    46		when         int64 // transition time, in seconds since 1970 GMT
    47		index        uint8 // the index of the zone that goes into effect at that time
    48		isstd, isutc bool  // ignored - no idea what these mean
    49	}
    50	
    51	// alpha and omega are the beginning and end of time for zone
    52	// transitions.
    53	const (
    54		alpha = -1 << 63  // math.MinInt64
    55		omega = 1<<63 - 1 // math.MaxInt64
    56	)
    57	
    58	// UTC represents Universal Coordinated Time (UTC).
    59	var UTC *Location = &utcLoc
    60	
    61	// utcLoc is separate so that get can refer to &utcLoc
    62	// and ensure that it never returns a nil *Location,
    63	// even if a badly behaved client has changed UTC.
    64	var utcLoc = Location{name: "UTC"}
    65	
    66	// Local represents the system's local time zone.
    67	var Local *Location = &localLoc
    68	
    69	// localLoc is separate so that initLocal can initialize
    70	// it even if a client has changed Local.
    71	var localLoc Location
    72	var localOnce sync.Once
    73	
    74	func (l *Location) get() *Location {
    75		if l == nil {
    76			return &utcLoc
    77		}
    78		if l == &localLoc {
    79			localOnce.Do(initLocal)
    80		}
    81		return l
    82	}
    83	
    84	// String returns a descriptive name for the time zone information,
    85	// corresponding to the name argument to LoadLocation or FixedZone.
    86	func (l *Location) String() string {
    87		return l.get().name
    88	}
    89	
    90	// FixedZone returns a Location that always uses
    91	// the given zone name and offset (seconds east of UTC).
    92	func FixedZone(name string, offset int) *Location {
    93		l := &Location{
    94			name:       name,
    95			zone:       []zone{{name, offset, false}},
    96			tx:         []zoneTrans{{alpha, 0, false, false}},
    97			cacheStart: alpha,
    98			cacheEnd:   omega,
    99		}
   100		l.cacheZone = &l.zone[0]
   101		return l
   102	}
   103	
   104	// lookup returns information about the time zone in use at an
   105	// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
   106	//
   107	// The returned information gives the name of the zone (such as "CET"),
   108	// the start and end times bracketing sec when that zone is in effect,
   109	// the offset in seconds east of UTC (such as -5*60*60), and whether
   110	// the daylight savings is being observed at that time.
   111	func (l *Location) lookup(sec int64) (name string, offset int, start, end int64) {
   112		l = l.get()
   113	
   114		if len(l.zone) == 0 {
   115			name = "UTC"
   116			offset = 0
   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			start = l.cacheStart
   126			end = l.cacheEnd
   127			return
   128		}
   129	
   130		if len(l.tx) == 0 || sec < l.tx[0].when {
   131			zone := &l.zone[l.lookupFirstZone()]
   132			name = zone.name
   133			offset = zone.offset
   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		start = tx[lo].when
   163		// end = maintained during the search
   164		return
   165	}
   166	
   167	// lookupFirstZone returns the index of the time zone to use for times
   168	// before the first transition time, or when there are no transition
   169	// times.
   170	//
   171	// The reference implementation in localtime.c from
   172	// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
   173	// implements the following algorithm for these cases:
   174	// 1) If the first zone is unused by the transitions, use it.
   175	// 2) Otherwise, if there are transition times, and the first
   176	//    transition is to a zone in daylight time, find the first
   177	//    non-daylight-time zone before and closest to the first transition
   178	//    zone.
   179	// 3) Otherwise, use the first zone that is not daylight time, if
   180	//    there is one.
   181	// 4) Otherwise, use the first zone.
   182	func (l *Location) lookupFirstZone() int {
   183		// Case 1.
   184		if !l.firstZoneUsed() {
   185			return 0
   186		}
   187	
   188		// Case 2.
   189		if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
   190			for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
   191				if !l.zone[zi].isDST {
   192					return zi
   193				}
   194			}
   195		}
   196	
   197		// Case 3.
   198		for zi := range l.zone {
   199			if !l.zone[zi].isDST {
   200				return zi
   201			}
   202		}
   203	
   204		// Case 4.
   205		return 0
   206	}
   207	
   208	// firstZoneUsed reports whether the first zone is used by some
   209	// transition.
   210	func (l *Location) firstZoneUsed() bool {
   211		for _, tx := range l.tx {
   212			if tx.index == 0 {
   213				return true
   214			}
   215		}
   216		return false
   217	}
   218	
   219	// lookupName returns information about the time zone with
   220	// the given name (such as "EST") at the given pseudo-Unix time
   221	// (what the given time of day would be in UTC).
   222	func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
   223		l = l.get()
   224	
   225		// First try for a zone with the right name that was actually
   226		// in effect at the given time. (In Sydney, Australia, both standard
   227		// and daylight-savings time are abbreviated "EST". Using the
   228		// offset helps us pick the right one for the given time.
   229		// It's not perfect: during the backward transition we might pick
   230		// either one.)
   231		for i := range l.zone {
   232			zone := &l.zone[i]
   233			if zone.name == name {
   234				nam, offset, _, _ := l.lookup(unix - int64(zone.offset))
   235				if nam == zone.name {
   236					return offset, true
   237				}
   238			}
   239		}
   240	
   241		// Otherwise fall back to an ordinary name match.
   242		for i := range l.zone {
   243			zone := &l.zone[i]
   244			if zone.name == name {
   245				return zone.offset, true
   246			}
   247		}
   248	
   249		// Otherwise, give up.
   250		return
   251	}
   252	
   253	// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
   254	// syntax too, but I don't feel like implementing it today.
   255	
   256	var errLocation = errors.New("time: invalid location name")
   257	
   258	var zoneinfo *string
   259	var zoneinfoOnce sync.Once
   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 containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
   283			// No valid IANA Time Zone name contains a single dot,
   284			// much less dot dot. Likewise, none begin with a slash.
   285			return nil, errLocation
   286		}
   287		zoneinfoOnce.Do(func() {
   288			env, _ := syscall.Getenv("ZONEINFO")
   289			zoneinfo = &env
   290		})
   291		var firstErr error
   292		if *zoneinfo != "" {
   293			if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
   294				if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
   295					return z, nil
   296				}
   297				firstErr = err
   298			} else if err != syscall.ENOENT {
   299				firstErr = err
   300			}
   301		}
   302		if z, err := loadLocation(name, zoneSources); err == nil {
   303			return z, nil
   304		} else if firstErr == nil {
   305			firstErr = err
   306		}
   307		return nil, firstErr
   308	}
   309	
   310	// containsDotDot reports whether s contains "..".
   311	func containsDotDot(s string) bool {
   312		if len(s) < 2 {
   313			return false
   314		}
   315		for i := 0; i < len(s)-1; i++ {
   316			if s[i] == '.' && s[i+1] == '.' {
   317				return true
   318			}
   319		}
   320		return false
   321	}
   322	

View as plain text