time: new Time, Duration, ZoneInfo types

R=r, bradfitz, gri, dsymonds, iant
CC=golang-dev
https://golang.org/cl/5392041
diff --git a/src/pkg/time/zoneinfo.go b/src/pkg/time/zoneinfo.go
new file mode 100644
index 0000000..aca56e7
--- /dev/null
+++ b/src/pkg/time/zoneinfo.go
@@ -0,0 +1,191 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time
+
+import "sync"
+
+// A Location maps time instants to the zone in use at that time.
+// Typically, the Location represents the collection of time offsets
+// in use in a geographical area, such as CEST and CET for central Europe.
+type Location struct {
+	name string
+	zone []zone
+	tx   []zoneTrans
+
+	// Most lookups will be for the current time.
+	// To avoid the binary search through tx, keep a
+	// static one-element cache that gives the correct
+	// zone for the time when the Location was created.
+	// if cacheStart <= t <= cacheEnd,
+	// lookup can return cacheZone.
+	// The units for cacheStart and cacheEnd are seconds
+	// since January 1, 1970 UTC, to match the argument
+	// to lookup.
+	cacheStart int64
+	cacheEnd   int64
+	cacheZone  *zone
+}
+
+// A zone represents a single time zone such as CEST or CET.
+type zone struct {
+	name   string // abbreviated name, "CET"
+	offset int    // seconds east of UTC
+	isDST  bool   // is this zone Daylight Savings Time?
+}
+
+// A zoneTrans represents a single time zone transition.
+type zoneTrans struct {
+	when         int64 // transition time, in seconds since 1970 GMT
+	index        uint8 // the index of the zone that goes into effect at that time
+	isstd, isutc bool  // ignored - no idea what these mean
+}
+
+// UTC represents Universal Coordinated Time (UTC).
+var UTC *Location = &utcLoc
+
+// utcLoc is separate so that get can refer to &utcLoc
+// and ensure that it never returns a nil *Location,
+// even if a badly behaved client has changed UTC.
+var utcLoc = Location{name: "UTC"}
+
+// Local represents the system's local time zone.
+var Local *Location = &localLoc
+
+// localLoc is separate so that initLocal can initialize
+// it even if a client has changed Local.
+var localLoc Location
+var localOnce sync.Once
+
+func (l *Location) get() *Location {
+	if l == nil {
+		return &utcLoc
+	}
+	if l == &localLoc {
+		localOnce.Do(initLocal)
+	}
+	return l
+}
+
+// String returns a descriptive name for the time zone information,
+// corresponding to the argument to LoadLocation.
+func (l *Location) String() string {
+	return l.get().name
+}
+
+// FixedZone returns a Location that always uses
+// the given zone name and offset (seconds east of UTC).
+func FixedZone(name string, offset int) *Location {
+	l := &Location{
+		name:       name,
+		zone:       []zone{{name, offset, false}},
+		tx:         []zoneTrans{{-1 << 63, 0, false, false}},
+		cacheStart: -1 << 63,
+		cacheEnd:   1<<63 - 1,
+	}
+	l.cacheZone = &l.zone[0]
+	return l
+}
+
+// lookup returns information about the time zone in use at an
+// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
+//
+// The returned information gives the name of the zone (such as "CET"),
+// the start and end times bracketing sec when that zone is in effect,
+// the offset in seconds east of UTC (such as -5*60*60), and whether
+// the daylight savings is being observed at that time.
+func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
+	l = l.get()
+
+	if len(l.tx) == 0 {
+		name = "UTC"
+		offset = 0
+		isDST = false
+		start = -1 << 63
+		end = 1<<63 - 1
+		return
+	}
+
+	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
+		name = zone.name
+		offset = zone.offset
+		isDST = zone.isDST
+		start = l.cacheStart
+		end = l.cacheEnd
+		return
+	}
+
+	// Binary search for entry with largest time <= sec.
+	// Not using sort.Search to avoid dependencies.
+	tx := l.tx
+	end = 1<<63 - 1
+	for len(tx) > 1 {
+		m := len(tx) / 2
+		lim := tx[m].when
+		if sec < lim {
+			end = lim
+			tx = tx[0:m]
+		} else {
+			tx = tx[m:]
+		}
+	}
+	zone := &l.zone[tx[0].index]
+	name = zone.name
+	offset = zone.offset
+	isDST = zone.isDST
+	start = tx[0].when
+	// end = maintained during the search
+	return
+}
+
+// lookupName returns information about the time zone with
+// the given name (such as "EST").
+func (l *Location) lookupName(name string) (offset int, isDST bool, ok bool) {
+	l = l.get()
+	for i := range l.zone {
+		zone := &l.zone[i]
+		if zone.name == name {
+			return zone.offset, zone.isDST, true
+		}
+	}
+	return
+}
+
+// lookupOffset returns information about the time zone with
+// the given offset (such as -5*60*60).
+func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) {
+	l = l.get()
+	for i := range l.zone {
+		zone := &l.zone[i]
+		if zone.offset == offset {
+			return zone.name, zone.isDST, true
+		}
+	}
+	return
+}
+
+// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
+// syntax too, but I don't feel like implementing it today.
+
+// NOTE(rsc): Using the IANA names below means ensuring we have access
+// to the database.  Probably we will ship the files in $GOROOT/lib/zoneinfo/
+// and only look there if there are no system files available (such as on Windows).
+// The files total 200 kB.
+
+// LoadLocation returns the Location with the given name.
+//
+// If the name is "" or "UTC", LoadLocation returns UTC.
+// If the name is "Local", LoadLocation returns Local.
+//
+// Otherwise, the name is taken to be a location name corresponding to a file
+// in the IANA Time Zone database, such as "America/New_York".
+func LoadLocation(name string) (*Location, error) {
+	if name == "" || name == "UTC" {
+		return UTC, nil
+	}
+	if name == "Local" {
+		return Local, nil
+	}
+	return loadLocation(name)
+}