github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/src/time/zoneinfo.go (about)

     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, isDST bool, start, end int64) {
   112  	l = l.get()
   113  
   114  	if len(l.zone) == 0 {
   115  		name = "UTC"
   116  		offset = 0
   117  		isDST = false
   118  		start = alpha
   119  		end = omega
   120  		return
   121  	}
   122  
   123  	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
   124  		name = zone.name
   125  		offset = zone.offset
   126  		isDST = zone.isDST
   127  		start = l.cacheStart
   128  		end = l.cacheEnd
   129  		return
   130  	}
   131  
   132  	if len(l.tx) == 0 || sec < l.tx[0].when {
   133  		zone := &l.zone[l.lookupFirstZone()]
   134  		name = zone.name
   135  		offset = zone.offset
   136  		isDST = zone.isDST
   137  		start = alpha
   138  		if len(l.tx) > 0 {
   139  			end = l.tx[0].when
   140  		} else {
   141  			end = omega
   142  		}
   143  		return
   144  	}
   145  
   146  	// Binary search for entry with largest time <= sec.
   147  	// Not using sort.Search to avoid dependencies.
   148  	tx := l.tx
   149  	end = omega
   150  	lo := 0
   151  	hi := len(tx)
   152  	for hi-lo > 1 {
   153  		m := lo + (hi-lo)/2
   154  		lim := tx[m].when
   155  		if sec < lim {
   156  			end = lim
   157  			hi = m
   158  		} else {
   159  			lo = m
   160  		}
   161  	}
   162  	zone := &l.zone[tx[lo].index]
   163  	name = zone.name
   164  	offset = zone.offset
   165  	isDST = zone.isDST
   166  	start = tx[lo].when
   167  	// end = maintained during the search
   168  	return
   169  }
   170  
   171  // lookupFirstZone returns the index of the time zone to use for times
   172  // before the first transition time, or when there are no transition
   173  // times.
   174  //
   175  // The reference implementation in localtime.c from
   176  // http://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
   177  // implements the following algorithm for these cases:
   178  // 1) If the first zone is unused by the transitions, use it.
   179  // 2) Otherwise, if there are transition times, and the first
   180  //    transition is to a zone in daylight time, find the first
   181  //    non-daylight-time zone before and closest to the first transition
   182  //    zone.
   183  // 3) Otherwise, use the first zone that is not daylight time, if
   184  //    there is one.
   185  // 4) Otherwise, use the first zone.
   186  func (l *Location) lookupFirstZone() int {
   187  	// Case 1.
   188  	if !l.firstZoneUsed() {
   189  		return 0
   190  	}
   191  
   192  	// Case 2.
   193  	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
   194  		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
   195  			if !l.zone[zi].isDST {
   196  				return zi
   197  			}
   198  		}
   199  	}
   200  
   201  	// Case 3.
   202  	for zi := range l.zone {
   203  		if !l.zone[zi].isDST {
   204  			return zi
   205  		}
   206  	}
   207  
   208  	// Case 4.
   209  	return 0
   210  }
   211  
   212  // firstZoneUsed returns whether the first zone is used by some
   213  // transition.
   214  func (l *Location) firstZoneUsed() bool {
   215  	for _, tx := range l.tx {
   216  		if tx.index == 0 {
   217  			return true
   218  		}
   219  	}
   220  	return false
   221  }
   222  
   223  // lookupName returns information about the time zone with
   224  // the given name (such as "EST") at the given pseudo-Unix time
   225  // (what the given time of day would be in UTC).
   226  func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) {
   227  	l = l.get()
   228  
   229  	// First try for a zone with the right name that was actually
   230  	// in effect at the given time. (In Sydney, Australia, both standard
   231  	// and daylight-savings time are abbreviated "EST". Using the
   232  	// offset helps us pick the right one for the given time.
   233  	// It's not perfect: during the backward transition we might pick
   234  	// either one.)
   235  	for i := range l.zone {
   236  		zone := &l.zone[i]
   237  		if zone.name == name {
   238  			nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset))
   239  			if nam == zone.name {
   240  				return offset, isDST, true
   241  			}
   242  		}
   243  	}
   244  
   245  	// Otherwise fall back to an ordinary name match.
   246  	for i := range l.zone {
   247  		zone := &l.zone[i]
   248  		if zone.name == name {
   249  			return zone.offset, zone.isDST, true
   250  		}
   251  	}
   252  
   253  	// Otherwise, give up.
   254  	return
   255  }
   256  
   257  // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
   258  // syntax too, but I don't feel like implementing it today.
   259  
   260  var errLocation = errors.New("time: invalid location name")
   261  
   262  var zoneinfo *string
   263  var zoneinfoOnce sync.Once
   264  
   265  // LoadLocation returns the Location with the given name.
   266  //
   267  // If the name is "" or "UTC", LoadLocation returns UTC.
   268  // If the name is "Local", LoadLocation returns Local.
   269  //
   270  // Otherwise, the name is taken to be a location name corresponding to a file
   271  // in the IANA Time Zone database, such as "America/New_York".
   272  //
   273  // The time zone database needed by LoadLocation may not be
   274  // present on all systems, especially non-Unix systems.
   275  // LoadLocation looks in the directory or uncompressed zip file
   276  // named by the ZONEINFO environment variable, if any, then looks in
   277  // known installation locations on Unix systems,
   278  // and finally looks in $GOROOT/lib/time/zoneinfo.zip.
   279  func LoadLocation(name string) (*Location, error) {
   280  	if name == "" || name == "UTC" {
   281  		return UTC, nil
   282  	}
   283  	if name == "Local" {
   284  		return Local, nil
   285  	}
   286  	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
   287  		// No valid IANA Time Zone name contains a single dot,
   288  		// much less dot dot. Likewise, none begin with a slash.
   289  		return nil, errLocation
   290  	}
   291  	zoneinfoOnce.Do(func() {
   292  		env, _ := syscall.Getenv("ZONEINFO")
   293  		zoneinfo = &env
   294  	})
   295  	if zoneinfo != nil && *zoneinfo != "" {
   296  		if z, err := loadZoneFile(*zoneinfo, name); err == nil {
   297  			z.name = name
   298  			return z, nil
   299  		}
   300  	}
   301  	return loadLocation(name)
   302  }
   303  
   304  // containsDotDot reports whether s contains "..".
   305  func containsDotDot(s string) bool {
   306  	if len(s) < 2 {
   307  		return false
   308  	}
   309  	for i := 0; i < len(s)-1; i++ {
   310  		if s[i] == '.' && s[i+1] == '.' {
   311  			return true
   312  		}
   313  	}
   314  	return false
   315  }