2010-12-03 04:34:57 +00:00
|
|
|
// Copyright 2009 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 (
|
|
|
|
"syscall"
|
|
|
|
"sync"
|
|
|
|
"os"
|
|
|
|
)
|
|
|
|
|
|
|
|
// BUG(brainman): The Windows implementation assumes that
|
|
|
|
// this year's rules for daylight savings time apply to all previous
|
|
|
|
// and future years as well.
|
|
|
|
|
2011-09-16 15:47:21 +00:00
|
|
|
// TODO(brainman): use GetDynamicTimeZoneInformation, whenever possible (Vista and up),
|
2010-12-03 04:34:57 +00:00
|
|
|
// to improve on situation described in the bug above.
|
|
|
|
|
|
|
|
type zone struct {
|
|
|
|
name string
|
|
|
|
offset int
|
|
|
|
year int64
|
|
|
|
month, day, dayofweek int
|
|
|
|
hour, minute, second int
|
|
|
|
abssec int64
|
|
|
|
prev *zone
|
|
|
|
}
|
|
|
|
|
2011-10-26 23:57:58 +00:00
|
|
|
// BUG(rsc): On Windows, time zone abbreviations are unavailable.
|
|
|
|
// This package constructs them using the capital letters from a longer
|
|
|
|
// time zone description.
|
|
|
|
|
2010-12-03 04:34:57 +00:00
|
|
|
// Populate zone struct with Windows supplied information. Returns true, if data is valid.
|
|
|
|
func (z *zone) populate(bias, biasdelta int32, d *syscall.Systemtime, name []uint16) (dateisgood bool) {
|
2011-10-26 23:57:58 +00:00
|
|
|
// name is 'Pacific Standard Time' but we want 'PST'.
|
|
|
|
// Extract just capital letters. It's not perfect but the
|
|
|
|
// information we need is not available from the kernel.
|
|
|
|
// Because time zone abbreviations are not unique,
|
|
|
|
// Windows refuses to expose them.
|
|
|
|
//
|
|
|
|
// http://social.msdn.microsoft.com/Forums/eu/vclanguage/thread/a87e1d25-fb71-4fe0-ae9c-a9578c9753eb
|
|
|
|
// http://stackoverflow.com/questions/4195948/windows-time-zone-abbreviations-in-asp-net
|
|
|
|
short := make([]uint16, len(name))
|
|
|
|
w := 0
|
|
|
|
for _, c := range name {
|
|
|
|
if 'A' <= c && c <= 'Z' {
|
|
|
|
short[w] = c
|
|
|
|
w++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
z.name = syscall.UTF16ToString(short[:w])
|
|
|
|
|
2010-12-03 04:34:57 +00:00
|
|
|
z.offset = int(bias)
|
|
|
|
z.year = int64(d.Year)
|
|
|
|
z.month = int(d.Month)
|
|
|
|
z.day = int(d.Day)
|
|
|
|
z.dayofweek = int(d.DayOfWeek)
|
|
|
|
z.hour = int(d.Hour)
|
|
|
|
z.minute = int(d.Minute)
|
|
|
|
z.second = int(d.Second)
|
|
|
|
dateisgood = d.Month != 0
|
|
|
|
if dateisgood {
|
|
|
|
z.offset += int(biasdelta)
|
|
|
|
}
|
|
|
|
z.offset = -z.offset * 60
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-09-16 15:47:21 +00:00
|
|
|
// Pre-calculate cutoff time in seconds since the Unix epoch, if data is supplied in "absolute" format.
|
2010-12-03 04:34:57 +00:00
|
|
|
func (z *zone) preCalculateAbsSec() {
|
|
|
|
if z.year != 0 {
|
2011-10-26 23:57:58 +00:00
|
|
|
t := &Time{
|
|
|
|
Year: z.year,
|
|
|
|
Month: int(z.month),
|
|
|
|
Day: int(z.day),
|
|
|
|
Hour: int(z.hour),
|
|
|
|
Minute: int(z.minute),
|
|
|
|
Second: int(z.second),
|
|
|
|
}
|
|
|
|
z.abssec = t.Seconds()
|
2010-12-03 04:34:57 +00:00
|
|
|
// Time given is in "local" time. Adjust it for "utc".
|
|
|
|
z.abssec -= int64(z.prev.offset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-16 15:47:21 +00:00
|
|
|
// Convert zone cutoff time to sec in number of seconds since the Unix epoch, given particular year.
|
2010-12-03 04:34:57 +00:00
|
|
|
func (z *zone) cutoffSeconds(year int64) int64 {
|
|
|
|
// Windows specifies daylight savings information in "day in month" format:
|
|
|
|
// z.month is month number (1-12)
|
|
|
|
// z.dayofweek is appropriate weekday (Sunday=0 to Saturday=6)
|
|
|
|
// z.day is week within the month (1 to 5, where 5 is last week of the month)
|
|
|
|
// z.hour, z.minute and z.second are absolute time
|
2011-10-26 23:57:58 +00:00
|
|
|
t := &Time{
|
|
|
|
Year: year,
|
|
|
|
Month: int(z.month),
|
|
|
|
Day: 1,
|
|
|
|
Hour: int(z.hour),
|
|
|
|
Minute: int(z.minute),
|
|
|
|
Second: int(z.second),
|
|
|
|
}
|
2010-12-03 04:34:57 +00:00
|
|
|
t = SecondsToUTC(t.Seconds())
|
2011-10-26 23:57:58 +00:00
|
|
|
i := int(z.dayofweek) - t.Weekday()
|
2010-12-03 04:34:57 +00:00
|
|
|
if i < 0 {
|
|
|
|
i += 7
|
|
|
|
}
|
|
|
|
t.Day += i
|
|
|
|
if week := int(z.day) - 1; week < 4 {
|
|
|
|
t.Day += week * 7
|
|
|
|
} else {
|
|
|
|
// "Last" instance of the day.
|
|
|
|
t.Day += 4 * 7
|
|
|
|
if t.Day > months(year)[t.Month] {
|
|
|
|
t.Day -= 7
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Result is in "local" time. Adjust it for "utc".
|
|
|
|
return t.Seconds() - int64(z.prev.offset)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is t before the cutoff for switching to z?
|
|
|
|
func (z *zone) isBeforeCutoff(t *Time) bool {
|
|
|
|
var coff int64
|
|
|
|
if z.year == 0 {
|
|
|
|
// "day in month" format used
|
|
|
|
coff = z.cutoffSeconds(t.Year)
|
|
|
|
} else {
|
|
|
|
// "absolute" format used
|
|
|
|
coff = z.abssec
|
|
|
|
}
|
|
|
|
return t.Seconds() < coff
|
|
|
|
}
|
|
|
|
|
|
|
|
type zoneinfo struct {
|
2011-09-16 15:47:21 +00:00
|
|
|
disabled bool // daylight saving time is not used locally
|
2010-12-03 04:34:57 +00:00
|
|
|
offsetIfDisabled int
|
|
|
|
januaryIsStd bool // is january 1 standard time?
|
|
|
|
std, dst zone
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pick zone (std or dst) t time belongs to.
|
|
|
|
func (zi *zoneinfo) pickZone(t *Time) *zone {
|
|
|
|
z := &zi.std
|
|
|
|
if tz.januaryIsStd {
|
|
|
|
if !zi.dst.isBeforeCutoff(t) && zi.std.isBeforeCutoff(t) {
|
|
|
|
// after switch to daylight time and before the switch back to standard
|
|
|
|
z = &zi.dst
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if zi.std.isBeforeCutoff(t) || !zi.dst.isBeforeCutoff(t) {
|
|
|
|
// before switch to standard time or after the switch back to daylight
|
|
|
|
z = &zi.dst
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return z
|
|
|
|
}
|
|
|
|
|
|
|
|
var tz zoneinfo
|
|
|
|
var initError os.Error
|
|
|
|
var onceSetupZone sync.Once
|
|
|
|
|
|
|
|
func setupZone() {
|
|
|
|
var i syscall.Timezoneinformation
|
|
|
|
if _, e := syscall.GetTimeZoneInformation(&i); e != 0 {
|
|
|
|
initError = os.NewSyscallError("GetTimeZoneInformation", e)
|
|
|
|
return
|
|
|
|
}
|
2011-10-26 23:57:58 +00:00
|
|
|
setupZoneFromTZI(&i)
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupZoneFromTZI(i *syscall.Timezoneinformation) {
|
2010-12-03 04:34:57 +00:00
|
|
|
if !tz.std.populate(i.Bias, i.StandardBias, &i.StandardDate, i.StandardName[0:]) {
|
|
|
|
tz.disabled = true
|
|
|
|
tz.offsetIfDisabled = tz.std.offset
|
|
|
|
return
|
|
|
|
}
|
|
|
|
tz.std.prev = &tz.dst
|
|
|
|
tz.dst.populate(i.Bias, i.DaylightBias, &i.DaylightDate, i.DaylightName[0:])
|
|
|
|
tz.dst.prev = &tz.std
|
|
|
|
tz.std.preCalculateAbsSec()
|
|
|
|
tz.dst.preCalculateAbsSec()
|
|
|
|
// Is january 1 standard time this year?
|
|
|
|
t := UTC()
|
|
|
|
tz.januaryIsStd = tz.dst.cutoffSeconds(t.Year) < tz.std.cutoffSeconds(t.Year)
|
|
|
|
}
|
|
|
|
|
2011-10-26 23:57:58 +00:00
|
|
|
var usPacific = syscall.Timezoneinformation{
|
|
|
|
Bias: 8 * 60,
|
|
|
|
StandardName: [32]uint16{
|
|
|
|
'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e',
|
|
|
|
},
|
|
|
|
StandardDate: syscall.Systemtime{Month: 11, Day: 1, Hour: 2},
|
|
|
|
DaylightName: [32]uint16{
|
|
|
|
'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e',
|
|
|
|
},
|
|
|
|
DaylightDate: syscall.Systemtime{Month: 3, Day: 2, Hour: 2},
|
|
|
|
DaylightBias: -60,
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupTestingZone() {
|
|
|
|
setupZoneFromTZI(&usPacific)
|
|
|
|
}
|
|
|
|
|
2010-12-03 04:34:57 +00:00
|
|
|
// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location.
|
|
|
|
func lookupTimezone(sec int64) (zone string, offset int) {
|
|
|
|
onceSetupZone.Do(setupZone)
|
|
|
|
if initError != nil {
|
|
|
|
return "", 0
|
|
|
|
}
|
|
|
|
if tz.disabled {
|
|
|
|
return "", tz.offsetIfDisabled
|
|
|
|
}
|
|
|
|
t := SecondsToUTC(sec)
|
|
|
|
z := &tz.std
|
|
|
|
if tz.std.year == 0 {
|
|
|
|
// "day in month" format used
|
|
|
|
z = tz.pickZone(t)
|
|
|
|
} else {
|
|
|
|
// "absolute" format used
|
|
|
|
if tz.std.year == t.Year {
|
|
|
|
// we have rule for the year in question
|
|
|
|
z = tz.pickZone(t)
|
|
|
|
} else {
|
|
|
|
// we do not have any information for that year,
|
|
|
|
// will assume standard offset all year around
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return z.name, z.offset
|
|
|
|
}
|
|
|
|
|
|
|
|
// lookupByName returns the time offset for the
|
|
|
|
// time zone with the given abbreviation. It only considers
|
|
|
|
// time zones that apply to the current system.
|
|
|
|
func lookupByName(name string) (off int, found bool) {
|
|
|
|
onceSetupZone.Do(setupZone)
|
|
|
|
if initError != nil {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
if tz.disabled {
|
|
|
|
return tz.offsetIfDisabled, false
|
|
|
|
}
|
|
|
|
switch name {
|
|
|
|
case tz.std.name:
|
|
|
|
return tz.std.offset, true
|
|
|
|
case tz.dst.name:
|
|
|
|
return tz.dst.offset, true
|
|
|
|
}
|
|
|
|
return 0, false
|
|
|
|
}
|