github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/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  	"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  }