Commit ff317384 authored by Russ Cox's avatar Russ Cox

assorted changes:

	- use a lock instead of a thread in once
		avoids deadlock in recursive once calls
	- implement os.Setenv
	- remove "export" from some scripts
	- remove _ from names in time package
	- fix time test for non-MTV machines

R=r
DELTA=265  (87 added, 58 deleted, 120 changed)
OCL=25057
CL=25057
parent 55d13cf1
...@@ -94,11 +94,12 @@ test: test.files ...@@ -94,11 +94,12 @@ test: test.files
bignum.6: fmt.dirinstall bignum.6: fmt.dirinstall
bufio.6: io.dirinstall os.dirinstall bufio.6: io.dirinstall os.dirinstall
exec.6: os.dirinstall
flag.6: fmt.dirinstall flag.6: fmt.dirinstall
log.6: fmt.dirinstall io.dirinstall os.dirinstall time.dirinstall log.6: fmt.dirinstall io.dirinstall os.dirinstall time.dirinstall
testing.6: flag.install fmt.dirinstall once.6: sync.dirinstall
strings.6: utf8.install strings.6: utf8.install
exec.6: os.dirinstall testing.6: flag.install fmt.dirinstall
fmt.dirinstall: io.dirinstall reflect.dirinstall strconv.dirinstall fmt.dirinstall: io.dirinstall reflect.dirinstall strconv.dirinstall
hash.dirinstall: os.dirinstall hash.dirinstall: os.dirinstall
...@@ -108,7 +109,7 @@ json.dirinstall: container/array.dirinstall fmt.dirinstall io.dirinstall math.di ...@@ -108,7 +109,7 @@ json.dirinstall: container/array.dirinstall fmt.dirinstall io.dirinstall math.di
strconv.dirinstall strings.install utf8.install strconv.dirinstall strings.install utf8.install
# TODO(rsc): net is not supposed to depend on fmt or strings or strconv # TODO(rsc): net is not supposed to depend on fmt or strings or strconv
net.dirinstall: fmt.dirinstall once.install os.dirinstall strconv.dirinstall strings.install net.dirinstall: fmt.dirinstall once.install os.dirinstall strconv.dirinstall strings.install
os.dirinstall: syscall.dirinstall os.dirinstall: syscall.dirinstall once.install
regexp.dirinstall: os.dirinstall regexp.dirinstall: os.dirinstall
reflect.dirinstall: strconv.dirinstall sync.dirinstall reflect.dirinstall: strconv.dirinstall sync.dirinstall
strconv.dirinstall: math.dirinstall os.dirinstall utf8.install strconv.dirinstall: math.dirinstall os.dirinstall utf8.install
......
...@@ -4,74 +4,41 @@ ...@@ -4,74 +4,41 @@
// For one-time initialization that is not done during init. // For one-time initialization that is not done during init.
// Wrap the initialization in a niladic function f() and call // Wrap the initialization in a niladic function f() and call
// once.Do(&f) // once.Do(f)
// If multiple processes call once.Do(&f) simultaneously // If multiple processes call once.Do(f) simultaneously
// with the same f argument, only one will call f, and the // with the same f argument, only one will call f, and the
// others will block until f finishes running. // others will block until f finishes running.
package once package once
type _Job struct { import "sync"
done bool;
doit chan bool; // buffer of 1
}
type _Request struct { type job struct {
f func(); done bool;
reply chan *_Job sync.Mutex; // should probably be sync.Notification or some such
} }
var service = make(chan _Request) var jobs = make(map[func()]*job)
var jobmap = make(map[func()]*_Job) var joblock sync.Mutex;
// Moderate access to the jobmap.
// Even if accesses were thread-safe (they should be but are not)
// something needs to serialize creation of new jobs.
// That's what the Server does.
func server() {
for {
req := <-service;
job, present := jobmap[req.f];
if !present {
job = new(_Job);
job.doit = make(chan bool, 1);
job.doit <- true;
jobmap[req.f] = job
}
req.reply <- job
}
}
func Do(f func()) { func Do(f func()) {
// Look for job in map (avoids channel communication). joblock.Lock();
// If not there, ask map server to make one. j, present := jobs[f];
// TODO: Uncomment use of jobmap[f] once
// maps are thread-safe.
var job *_Job;
var present bool;
// job, present = jobmap[f]
if !present { if !present {
c := make(chan *_Job); // run it
service <- _Request(f, c); j = new(job);
job = <-c j.Lock();
} jobs[f] = j;
joblock.Unlock();
// Optimization
if job.done {
return
}
// If we're the first one, job.doit has a true waiting.
if <-job.doit {
f(); f();
job.done = true j.done = true;
j.Unlock();
} else {
// wait for it
joblock.Unlock();
if j.done != true {
j.Lock();
j.Unlock();
}
} }
// Leave a false waiting for the next guy.
job.doit <- false
}
func init() {
go server()
} }
...@@ -3,25 +3,71 @@ ...@@ -3,25 +3,71 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Environment variables. // Environment variables.
// Setenv doesn't exist yet: don't have the run-time hooks yet
package os package os
import os "os" import (
"once";
"os";
)
var ( var (
ENOENV = NewError("no such environment variable"); ENOENV = NewError("no such environment variable");
env map[string] string;
) )
func Getenv(s string) (v string, err *Error) { func copyenv() {
n := len(s); env = make(map[string] string);
if n == 0 { for i, s := range sys.Envs {
return "", EINVAL for j := 0; j < len(s); j++ {
if s[j] == '=' {
env[s[0:j]] = s[j+1:len(s)];
break;
}
}
}
}
func Getenv(key string) (value string, err *Error) {
once.Do(copyenv);
if len(key) == 0 {
return "", EINVAL;
}
v, ok := env[key];
if !ok {
return "", ENOENV;
} }
for i, e := range sys.Envs { return v, nil;
if len(e) > n && e[n] == '=' && e[0:n] == s { }
return e[n+1:len(e)], nil
func Setenv(key, value string) *Error {
once.Do(copyenv);
if len(key) == 0 {
return EINVAL;
}
env[key] = value;
return nil;
}
func Clearenv() {
once.Do(copyenv); // prevent copyenv in Getenv/Setenv
env = make(map[string] string);
}
func Environ() []string {
once.Do(copyenv);
a := make([]string, len(env));
i := 0;
for k, v := range(env) {
// check i < len(a) for safety,
// in case env is changing underfoot.
if i < len(a) {
a[i] = k + "=" + v;
i++;
} }
} }
return "", ENOENV return a[0:i];
} }
...@@ -27,6 +27,9 @@ func ForkExec(argv0 string, argv []string, envv []string, fd []*FD) ...@@ -27,6 +27,9 @@ func ForkExec(argv0 string, argv []string, envv []string, fd []*FD)
} }
func Exec(argv0 string, argv []string, envv []string) *Error { func Exec(argv0 string, argv []string, envv []string) *Error {
if envv == nil {
envv = Environ();
}
e := syscall.Exec(argv0, argv, envv); e := syscall.Exec(argv0, argv, envv);
return ErrnoToError(e); return ErrnoToError(e);
} }
......
...@@ -10,7 +10,7 @@ print <<EOF; ...@@ -10,7 +10,7 @@ print <<EOF;
package syscall package syscall
export const ( const (
EOF EOF
while(<>){ while(<>){
......
...@@ -11,7 +11,7 @@ print <<EOF; ...@@ -11,7 +11,7 @@ print <<EOF;
package syscall package syscall
export const( const(
EOF EOF
while(<>){ while(<>){
...@@ -25,4 +25,7 @@ while(<>){ ...@@ -25,4 +25,7 @@ while(<>){
print <<EOF; print <<EOF;
) )
func _darwin_system_call_conflict() {
}
EOF EOF
...@@ -10,7 +10,7 @@ print <<EOF; ...@@ -10,7 +10,7 @@ print <<EOF;
package syscall package syscall
export const( const(
EOF EOF
while(<>){ while(<>){
......
...@@ -138,7 +138,7 @@ func SecondsToUTC(sec int64) *Time { ...@@ -138,7 +138,7 @@ func SecondsToUTC(sec int64) *Time {
} }
t.Month = m+1; t.Month = m+1;
t.Day = yday+1; t.Day = yday+1;
t.Zone = "GMT"; t.Zone = "UTC";
return t; return t;
} }
...@@ -149,12 +149,12 @@ func UTC() *Time { ...@@ -149,12 +149,12 @@ func UTC() *Time {
// TODO: Should this return an error? // TODO: Should this return an error?
func SecondsToLocalTime(sec int64) *Time { func SecondsToLocalTime(sec int64) *Time {
zone, offset, err := time.LookupTimezone(sec); z, offset, err := time.LookupTimezone(sec);
if err != nil { if err != nil {
return SecondsToUTC(sec) return SecondsToUTC(sec)
} }
t := SecondsToUTC(sec+int64(offset)); t := SecondsToUTC(sec+int64(offset));
t.Zone = zone; t.Zone = z;
t.ZoneOffset = offset; t.ZoneOffset = offset;
return t return t
} }
......
...@@ -5,31 +5,39 @@ ...@@ -5,31 +5,39 @@
package time package time
import ( import (
"os";
"testing"; "testing";
"time"; "time";
) )
type _TimeTest struct { func init() {
// Force US Pacific time for daylight-savings
// tests below (localtests). Needs to be set
// before the first call into the time library.
os.Setenv("TZ", "US/Pacific");
}
type TimeTest struct {
seconds int64; seconds int64;
golden Time; golden Time;
} }
var utctests = []_TimeTest ( var utctests = []TimeTest (
_TimeTest(0, Time(1970, 1, 1, 0, 0, 0, Thursday, 0, "GMT")), TimeTest(0, Time(1970, 1, 1, 0, 0, 0, Thursday, 0, "UTC")),
_TimeTest(1221681866, Time(2008, 9, 17, 20, 4, 26, Wednesday, 0, "GMT")), TimeTest(1221681866, Time(2008, 9, 17, 20, 4, 26, Wednesday, 0, "UTC")),
_TimeTest(-1221681866, Time(1931, 4, 16, 3, 55, 34, Thursday, 0, "GMT")), TimeTest(-1221681866, Time(1931, 4, 16, 3, 55, 34, Thursday, 0, "UTC")),
_TimeTest(1e18, Time(31688740476, 10, 23, 1, 46, 40, Friday, 0, "GMT")), TimeTest(1e18, Time(31688740476, 10, 23, 1, 46, 40, Friday, 0, "UTC")),
_TimeTest(-1e18, Time(-31688736537, 3, 10, 22, 13, 20, Tuesday, 0, "GMT")), TimeTest(-1e18, Time(-31688736537, 3, 10, 22, 13, 20, Tuesday, 0, "UTC")),
_TimeTest(0x7fffffffffffffff, Time(292277026596, 12, 4, 15, 30, 7, Sunday, 0, "GMT")), TimeTest(0x7fffffffffffffff, Time(292277026596, 12, 4, 15, 30, 7, Sunday, 0, "UTC")),
_TimeTest(-0x8000000000000000, Time(-292277022657, 1, 27, 8, 29, 52, Sunday, 0, "GMT")) TimeTest(-0x8000000000000000, Time(-292277022657, 1, 27, 8, 29, 52, Sunday, 0, "UTC"))
) )
var localtests = []_TimeTest ( var localtests = []TimeTest (
_TimeTest(0, Time(1969, 12, 31, 16, 0, 0, Wednesday, -8*60*60, "PST")), TimeTest(0, Time(1969, 12, 31, 16, 0, 0, Wednesday, -8*60*60, "PST")),
_TimeTest(1221681866, Time(2008, 9, 17, 13, 4, 26, Wednesday, -7*60*60, "PDT")) TimeTest(1221681866, Time(2008, 9, 17, 13, 4, 26, Wednesday, -7*60*60, "PDT"))
) )
func _Same(t, u *Time) bool { func same(t, u *Time) bool {
return t.Year == u.Year return t.Year == u.Year
&& t.Month == u.Month && t.Month == u.Month
&& t.Day == u.Day && t.Day == u.Day
...@@ -50,7 +58,7 @@ func TestSecondsToUTC(t *testing.T) { ...@@ -50,7 +58,7 @@ func TestSecondsToUTC(t *testing.T) {
if newsec != sec { if newsec != sec {
t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec); t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec);
} }
if !_Same(tm, golden) { if !same(tm, golden) {
t.Errorf("SecondsToUTC(%d):", sec); t.Errorf("SecondsToUTC(%d):", sec);
t.Errorf(" want=%v", *golden); t.Errorf(" want=%v", *golden);
t.Errorf(" have=%v", *tm); t.Errorf(" have=%v", *tm);
...@@ -67,7 +75,7 @@ func TestSecondsToLocalTime(t *testing.T) { ...@@ -67,7 +75,7 @@ func TestSecondsToLocalTime(t *testing.T) {
if newsec != sec { if newsec != sec {
t.Errorf("SecondsToLocalTime(%d).Seconds() = %d", sec, newsec); t.Errorf("SecondsToLocalTime(%d).Seconds() = %d", sec, newsec);
} }
if !_Same(tm, golden) { if !same(tm, golden) {
t.Errorf("SecondsToLocalTime(%d):", sec); t.Errorf("SecondsToLocalTime(%d):", sec);
t.Errorf(" want=%v", *golden); t.Errorf(" want=%v", *golden);
t.Errorf(" have=%v", *tm); t.Errorf(" have=%v", *tm);
......
...@@ -16,8 +16,10 @@ import ( ...@@ -16,8 +16,10 @@ import (
) )
const ( const (
_MaxFileSize = 8192; // actual files are closer to 1K maxFileSize = 8192; // actual files are closer to 1K
_HeaderSize = 4+16+4*7 headerSize = 4+16+4*7;
zoneDir = "/usr/share/zoneinfo/";
) )
var ( var (
...@@ -26,13 +28,13 @@ var ( ...@@ -26,13 +28,13 @@ var (
) )
// Simple I/O interface to binary blob of data. // Simple I/O interface to binary blob of data.
type _Data struct { type data struct {
p []byte; p []byte;
error bool; error bool;
} }
func (d *_Data) Read(n int) []byte { func (d *data) read(n int) []byte {
if len(d.p) < n { if len(d.p) < n {
d.p = nil; d.p = nil;
d.error = true; d.error = true;
...@@ -43,8 +45,8 @@ func (d *_Data) Read(n int) []byte { ...@@ -43,8 +45,8 @@ func (d *_Data) Read(n int) []byte {
return p return p
} }
func (d *_Data) Big4() (n uint32, ok bool) { func (d *data) big4() (n uint32, ok bool) {
p := d.Read(4); p := d.read(4);
if len(p) < 4 { if len(p) < 4 {
d.error = true; d.error = true;
return 0, false return 0, false
...@@ -52,8 +54,8 @@ func (d *_Data) Big4() (n uint32, ok bool) { ...@@ -52,8 +54,8 @@ func (d *_Data) Big4() (n uint32, ok bool) {
return uint32(p[0]) << 24 | uint32(p[1]) << 16 | uint32(p[2]) << 8 | uint32(p[3]), true return uint32(p[0]) << 24 | uint32(p[1]) << 16 | uint32(p[2]) << 8 | uint32(p[3]), true
} }
func (d *_Data) Byte() (n byte, ok bool) { func (d *data) byte() (n byte, ok bool) {
p := d.Read(1); p := d.read(1);
if len(p) < 1 { if len(p) < 1 {
d.error = true; d.error = true;
return 0, false return 0, false
...@@ -63,7 +65,7 @@ func (d *_Data) Byte() (n byte, ok bool) { ...@@ -63,7 +65,7 @@ func (d *_Data) Byte() (n byte, ok bool) {
// Make a string by stopping at the first NUL // Make a string by stopping at the first NUL
func _ByteString(p []byte) string { func byteString(p []byte) string {
for i := 0; i < len(p); i++ { for i := 0; i < len(p); i++ {
if p[i] == 0 { if p[i] == 0 {
return string(p[0:i]) return string(p[0:i])
...@@ -73,31 +75,29 @@ func _ByteString(p []byte) string { ...@@ -73,31 +75,29 @@ func _ByteString(p []byte) string {
} }
// Parsed representation // Parsed representation
type _Zone struct { type zone struct {
utcoff int; utcoff int;
isdst bool; isdst bool;
name string; name string;
} }
type _Zonetime struct { type zonetime struct {
time int32; // transition time, in seconds since 1970 GMT time int32; // transition time, in seconds since 1970 GMT
zone *_Zone; // the zone that goes into effect at that time zone *zone; // the zone that goes into effect at that time
isstd, isutc bool; // ignored - no idea what these mean isstd, isutc bool; // ignored - no idea what these mean
} }
func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) { func parseinfo(bytes []byte) (zt []zonetime, err *os.Error) {
d := data(bytes, false);
data1 := _Data(bytes, false);
data := &data1;
// 4-byte magic "TZif" // 4-byte magic "TZif"
if magic := data.Read(4); string(magic) != "TZif" { if magic := d.read(4); string(magic) != "TZif" {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
// 1-byte version, then 15 bytes of padding // 1-byte version, then 15 bytes of padding
var p []byte; var p []byte;
if p = data.Read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' { if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
vers := p[0]; vers := p[0];
...@@ -119,7 +119,7 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) { ...@@ -119,7 +119,7 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) {
) )
var n [6]int; var n [6]int;
for i := 0; i < 6; i++ { for i := 0; i < 6; i++ {
nn, ok := data.Big4(); nn, ok := d.big4();
if !ok { if !ok {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
...@@ -127,32 +127,29 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) { ...@@ -127,32 +127,29 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) {
} }
// Transition times. // Transition times.
txtimes1 := _Data(data.Read(n[NTime]*4), false); txtimes := data(d.read(n[NTime]*4), false);
txtimes := &txtimes1;
// Time zone indices for transition times. // Time zone indices for transition times.
txzones := data.Read(n[NTime]); txzones := d.read(n[NTime]);
// Zone info structures // Zone info structures
zonedata1 := _Data(data.Read(n[NZone]*6), false); zonedata := data(d.read(n[NZone]*6), false);
zonedata := &zonedata1;
// Time zone abbreviations. // Time zone abbreviations.
abbrev := data.Read(n[NChar]); abbrev := d.read(n[NChar]);
// Leap-second time pairs // Leap-second time pairs
leapdata1 := _Data(data.Read(n[NLeap]*8), false); leapdata := data(d.read(n[NLeap]*8), false);
leapdata := &leapdata1;
// Whether tx times associated with local time types // Whether tx times associated with local time types
// are specified as standard time or wall time. // are specified as standard time or wall time.
isstd := data.Read(n[NStdWall]); isstd := d.read(n[NStdWall]);
// Whether tx times associated with local time types // Whether tx times associated with local time types
// are specified as UTC or local time. // are specified as UTC or local time.
isutc := data.Read(n[NUTCLocal]); isutc := d.read(n[NUTCLocal]);
if data.error { // ran out of data if d.error { // ran out of data
return nil, BadZoneinfo return nil, BadZoneinfo
} }
...@@ -163,38 +160,38 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) { ...@@ -163,38 +160,38 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) {
// Now we can build up a useful data structure. // Now we can build up a useful data structure.
// First the zone information. // First the zone information.
// utcoff[4] isdst[1] nameindex[1] // utcoff[4] isdst[1] nameindex[1]
zone := make([]_Zone, n[NZone]); z := make([]zone, n[NZone]);
for i := 0; i < len(zone); i++ { for i := 0; i < len(z); i++ {
var ok bool; var ok bool;
var n uint32; var n uint32;
if n, ok = zonedata.Big4(); !ok { if n, ok = zonedata.big4(); !ok {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
zone[i].utcoff = int(n); z[i].utcoff = int(n);
var b byte; var b byte;
if b, ok = zonedata.Byte(); !ok { if b, ok = zonedata.byte(); !ok {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
zone[i].isdst = b != 0; z[i].isdst = b != 0;
if b, ok = zonedata.Byte(); !ok || int(b) >= len(abbrev) { if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
zone[i].name = _ByteString(abbrev[b:len(abbrev)]) z[i].name = byteString(abbrev[b:len(abbrev)])
} }
// Now the transition time info. // Now the transition time info.
zt = make([]_Zonetime, n[NTime]); zt = make([]zonetime, n[NTime]);
for i := 0; i < len(zt); i++ { for i := 0; i < len(zt); i++ {
var ok bool; var ok bool;
var n uint32; var n uint32;
if n, ok = txtimes.Big4(); !ok { if n, ok = txtimes.big4(); !ok {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
zt[i].time = int32(n); zt[i].time = int32(n);
if int(txzones[i]) >= len(zone) { if int(txzones[i]) >= len(z) {
return nil, BadZoneinfo return nil, BadZoneinfo
} }
zt[i].zone = &zone[txzones[i]]; zt[i].zone = &z[txzones[i]];
if i < len(isstd) { if i < len(isstd) {
zt[i].isstd = isstd[i] != 0 zt[i].isstd = isstd[i] != 0
} }
...@@ -208,52 +205,56 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) { ...@@ -208,52 +205,56 @@ func parseinfo(bytes []byte) (zt []_Zonetime, err *os.Error) {
func readfile(name string, max int) (p []byte, err *os.Error) { func readfile(name string, max int) (p []byte, err *os.Error) {
fd, e := os.Open(name, os.O_RDONLY, 0); fd, e := os.Open(name, os.O_RDONLY, 0);
if e != nil { if e != nil {
return nil, e return nil, e;
} }
p = make([]byte, max+1)[0:0]; p = make([]byte, max);
n := 0; n, err1 := io.Readn(fd, p);
for len(p) < max {
nn, e := fd.Read(p[n:cap(p)]);
if e != nil {
fd.Close(); fd.Close();
return nil, e if err1 == nil { // too long
return nil, BadZoneinfo;
} }
if nn == 0 { if err1 != io.ErrEOF {
fd.Close(); return nil, err1;
return p, nil
} }
p = p[0:n+nn] return p[0:n], nil;
}
fd.Close();
return nil, BadZoneinfo // too long
} }
func readinfofile(name string) (tx []zonetime, err *os.Error) {
func readinfofile(name string) (tx []_Zonetime, err *os.Error) { buf, e := readfile(name, maxFileSize);
data, e := readfile(name, _MaxFileSize);
if e != nil { if e != nil {
return nil, e return nil, e
} }
tx, err = parseinfo(data); tx, err = parseinfo(buf);
return tx, err return tx, err
} }
var zones []_Zonetime var zones []zonetime
var zoneerr *os.Error var zoneerr *os.Error
func _SetupZone() { func setupZone() {
// TODO: /etc/localtime is the default time zone info // consult $TZ to find the time zone to use.
// for the system, but libc allows setting an environment // no $TZ means use the system default /etc/localtime.
// variable in order to direct reading a different file // $TZ="" means use UTC.
// (in /usr/share/zoneinfo). We should check that // $TZ="foo" means use /usr/share/zoneinfo/foo.
// environment variable.
tz, err := os.Getenv("TZ");
var file string;
switch {
case err == os.ENOENV:
zones, zoneerr = readinfofile("/etc/localtime"); zones, zoneerr = readinfofile("/etc/localtime");
case err != nil:
zoneerr = err;
case len(tz) > 0:
zones, zoneerr = readinfofile(zoneDir + tz);
case len(tz) == 0:
// do nothing: use UTC
}
} }
func LookupTimezone(sec int64) (zone string, offset int, err *os.Error) { func LookupTimezone(sec int64) (zone string, offset int, err *os.Error) {
once.Do(_SetupZone); once.Do(setupZone);
if zoneerr != nil || len(zones) == 0 { if zoneerr != nil || len(zones) == 0 {
return "GMT", 0, zoneerr return "UTC", 0, zoneerr
} }
// Binary search for entry with largest time <= sec // Binary search for entry with largest time <= sec
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment