Black Lives Matter. Support the Equal Justice Initiative.

Source file src/time/zoneinfo.go

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

View as plain text