github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/src/pkg/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  // UTC represents Universal Coordinated Time (UTC).
    49  var UTC *Location = &utcLoc
    50  
    51  // utcLoc is separate so that get can refer to &utcLoc
    52  // and ensure that it never returns a nil *Location,
    53  // even if a badly behaved client has changed UTC.
    54  var utcLoc = Location{name: "UTC"}
    55  
    56  // Local represents the system's local time zone.
    57  var Local *Location = &localLoc
    58  
    59  // localLoc is separate so that initLocal can initialize
    60  // it even if a client has changed Local.
    61  var localLoc Location
    62  var localOnce sync.Once
    63  
    64  func (l *Location) get() *Location {
    65  	if l == nil {
    66  		return &utcLoc
    67  	}
    68  	if l == &localLoc {
    69  		localOnce.Do(initLocal)
    70  	}
    71  	return l
    72  }
    73  
    74  // String returns a descriptive name for the time zone information,
    75  // corresponding to the argument to LoadLocation.
    76  func (l *Location) String() string {
    77  	return l.get().name
    78  }
    79  
    80  // FixedZone returns a Location that always uses
    81  // the given zone name and offset (seconds east of UTC).
    82  func FixedZone(name string, offset int) *Location {
    83  	l := &Location{
    84  		name:       name,
    85  		zone:       []zone{{name, offset, false}},
    86  		tx:         []zoneTrans{{-1 << 63, 0, false, false}},
    87  		cacheStart: -1 << 63,
    88  		cacheEnd:   1<<63 - 1,
    89  	}
    90  	l.cacheZone = &l.zone[0]
    91  	return l
    92  }
    93  
    94  // lookup returns information about the time zone in use at an
    95  // instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
    96  //
    97  // The returned information gives the name of the zone (such as "CET"),
    98  // the start and end times bracketing sec when that zone is in effect,
    99  // the offset in seconds east of UTC (such as -5*60*60), and whether
   100  // the daylight savings is being observed at that time.
   101  func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
   102  	l = l.get()
   103  
   104  	if len(l.tx) == 0 {
   105  		name = "UTC"
   106  		offset = 0
   107  		isDST = false
   108  		start = -1 << 63
   109  		end = 1<<63 - 1
   110  		return
   111  	}
   112  
   113  	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
   114  		name = zone.name
   115  		offset = zone.offset
   116  		isDST = zone.isDST
   117  		start = l.cacheStart
   118  		end = l.cacheEnd
   119  		return
   120  	}
   121  
   122  	// Binary search for entry with largest time <= sec.
   123  	// Not using sort.Search to avoid dependencies.
   124  	tx := l.tx
   125  	end = 1<<63 - 1
   126  	lo := 0
   127  	hi := len(tx)
   128  	for hi-lo > 1 {
   129  		m := lo + (hi-lo)/2
   130  		lim := tx[m].when
   131  		if sec < lim {
   132  			end = lim
   133  			hi = m
   134  		} else {
   135  			lo = m
   136  		}
   137  	}
   138  	zone := &l.zone[tx[lo].index]
   139  	name = zone.name
   140  	offset = zone.offset
   141  	isDST = zone.isDST
   142  	start = tx[lo].when
   143  	// end = maintained during the search
   144  	return
   145  }
   146  
   147  // lookupName returns information about the time zone with
   148  // the given name (such as "EST") at the given pseudo-Unix time
   149  // (what the given time of day would be in UTC).
   150  func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) {
   151  	l = l.get()
   152  
   153  	// First try for a zone with the right name that was actually
   154  	// in effect at the given time. (In Sydney, Australia, both standard
   155  	// and daylight-savings time are abbreviated "EST". Using the
   156  	// offset helps us pick the right one for the given time.
   157  	// It's not perfect: during the backward transition we might pick
   158  	// either one.)
   159  	for i := range l.zone {
   160  		zone := &l.zone[i]
   161  		if zone.name == name {
   162  			nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset))
   163  			if nam == zone.name {
   164  				return offset, isDST, true
   165  			}
   166  		}
   167  	}
   168  
   169  	// Otherwise fall back to an ordinary name match.
   170  	for i := range l.zone {
   171  		zone := &l.zone[i]
   172  		if zone.name == name {
   173  			return zone.offset, zone.isDST, true
   174  		}
   175  	}
   176  
   177  	// Otherwise, give up.
   178  	return
   179  }
   180  
   181  // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
   182  // syntax too, but I don't feel like implementing it today.
   183  
   184  var zoneinfo, _ = syscall.Getenv("ZONEINFO")
   185  
   186  // LoadLocation returns the Location with the given name.
   187  //
   188  // If the name is "" or "UTC", LoadLocation returns UTC.
   189  // If the name is "Local", LoadLocation returns Local.
   190  //
   191  // Otherwise, the name is taken to be a location name corresponding to a file
   192  // in the IANA Time Zone database, such as "America/New_York".
   193  //
   194  // The time zone database needed by LoadLocation may not be
   195  // present on all systems, especially non-Unix systems.
   196  // LoadLocation looks in the directory or uncompressed zip file
   197  // named by the ZONEINFO environment variable, if any, then looks in
   198  // known installation locations on Unix systems,
   199  // and finally looks in $GOROOT/lib/time/zoneinfo.zip.
   200  func LoadLocation(name string) (*Location, error) {
   201  	if name == "" || name == "UTC" {
   202  		return UTC, nil
   203  	}
   204  	if name == "Local" {
   205  		return Local, nil
   206  	}
   207  	if zoneinfo != "" {
   208  		if z, err := loadZoneFile(zoneinfo, name); err == nil {
   209  			z.name = name
   210  			return z, nil
   211  		}
   212  	}
   213  	return loadLocation(name)
   214  }