1 // Copyright 2009 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.
14 // TODO(rsc): Fall back to copy of zoneinfo files.
16 // BUG(brainman,rsc): On Windows, the operating system does not provide complete
17 // time zone information.
18 // The implementation assumes that this year's rules for daylight savings
19 // time apply to all previous and future years as well.
21 // getKeyValue retrieves the string value kname associated with the open registry key kh.
22 func getKeyValue(kh syscall
.Handle
, kname
string) (string, error
) {
23 var buf
[50]uint16 // buf needs to be large enough to fit zone descriptions
25 n
:= uint32(len(buf
) * 2) // RegQueryValueEx's signature expects array of bytes, not uint16
26 p
, _
:= syscall
.UTF16PtrFromString(kname
)
27 if err
:= syscall
.RegQueryValueEx(kh
, p
, nil, &typ
, (*byte)(unsafe
.Pointer(&buf
[0])), &n
); err
!= nil {
30 if typ
!= syscall
.REG_SZ
{ // null terminated strings only
31 return "", errors
.New("Key is not string")
33 return syscall
.UTF16ToString(buf
[:]), nil
36 // matchZoneKey checks if stdname and dstname match the corresponding "Std"
37 // and "Dlt" key values in the kname key stored under the open registry key zones.
38 func matchZoneKey(zones syscall
.Handle
, kname
string, stdname
, dstname
string) (matched
bool, err2 error
) {
40 p
, _
:= syscall
.UTF16PtrFromString(kname
)
41 if err
:= syscall
.RegOpenKeyEx(zones
, p
, 0, syscall
.KEY_READ
, &h
); err
!= nil {
44 defer syscall
.RegCloseKey(h
)
46 s
, err
:= getKeyValue(h
, "Std")
53 s
, err
= getKeyValue(h
, "Dlt")
63 // toEnglishName searches the registry for an English name of a time zone
64 // whose zone names are stdname and dstname and returns the English name.
65 func toEnglishName(stdname
, dstname
string) (string, error
) {
66 var zones syscall
.Handle
67 p
, _
:= syscall
.UTF16PtrFromString(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`)
68 if err
:= syscall
.RegOpenKeyEx(syscall
.HKEY_LOCAL_MACHINE
, p
, 0, syscall
.KEY_READ
, &zones
); err
!= nil {
71 defer syscall
.RegCloseKey(zones
)
74 if err
:= syscall
.RegQueryInfoKey(zones
, nil, nil, nil, &count
, nil, nil, nil, nil, nil, nil, nil); err
!= nil {
78 var buf
[50]uint16 // buf needs to be large enough to fit zone descriptions
79 for i
:= uint32(0); i
< count
; i
++ {
81 if syscall
.RegEnumKeyEx(zones
, i
, &buf
[0], &n
, nil, nil, nil, nil) != nil {
84 kname
:= syscall
.UTF16ToString(buf
[:])
85 matched
, err
:= matchZoneKey(zones
, kname
, stdname
, dstname
)
86 if err
== nil && matched
{
90 return "", errors
.New(`English name for time zone "` + stdname
+ `" not found in registry`)
93 // extractCAPS exracts capital letters from description desc.
94 func extractCAPS(desc
string) string {
96 for _
, c
:= range desc
{
97 if 'A' <= c
&& c
<= 'Z' {
98 short
= append(short
, rune(c
))
104 // abbrev returns the abbreviations to use for the given zone z.
105 func abbrev(z
*syscall
.Timezoneinformation
) (std
, dst
string) {
106 stdName
:= syscall
.UTF16ToString(z
.StandardName
[:])
107 a
, ok
:= abbrs
[stdName
]
109 dstName
:= syscall
.UTF16ToString(z
.DaylightName
[:])
110 // Perhaps stdName is not English. Try to convert it.
111 englishName
, err
:= toEnglishName(stdName
, dstName
)
113 a
, ok
= abbrs
[englishName
]
118 // fallback to using capital letters
119 return extractCAPS(stdName
), extractCAPS(dstName
)
124 // pseudoUnix returns the pseudo-Unix time (seconds since Jan 1 1970 *LOCAL TIME*)
125 // denoted by the system date+time d in the given year.
126 // It is up to the caller to convert this local time into a UTC-based time.
127 func pseudoUnix(year
int, d
*syscall
.Systemtime
) int64 {
128 // Windows specifies daylight savings information in "day in month" format:
129 // d.Month is month number (1-12)
130 // d.DayOfWeek is appropriate weekday (Sunday=0 to Saturday=6)
131 // d.Day is week within the month (1 to 5, where 5 is last week of the month)
132 // d.Hour, d.Minute and d.Second are absolute time
134 t
:= Date(year
, Month(d
.Month
), day
, int(d
.Hour
), int(d
.Minute
), int(d
.Second
), 0, UTC
)
135 i
:= int(d
.DayOfWeek
) - int(t
.Weekday())
140 if week
:= int(d
.Day
) - 1; week
< 4 {
143 // "Last" instance of the day.
145 if day
> daysIn(Month(d
.Month
), year
) {
149 return t
.sec
+ int64(day
-1)*secondsPerDay
+ internalToUnix
152 func initLocalFromTZI(i
*syscall
.Timezoneinformation
) {
156 if i
.StandardDate
.Month
> 0 {
159 l
.zone
= make([]zone
, nzone
)
161 stdname
, dstname
:= abbrev(i
)
166 // No daylight savings.
167 std
.offset
= -int(i
.Bias
) * 60
168 l
.cacheStart
= -1 << 63
169 l
.cacheEnd
= 1<<63 - 1
171 l
.tx
= make([]zoneTrans
, 1)
172 l
.tx
[0].when
= l
.cacheStart
177 // StandardBias must be ignored if StandardDate is not set,
178 // so this computation is delayed until after the nzone==1
180 std
.offset
= -int(i
.Bias
+i
.StandardBias
) * 60
184 dst
.offset
= -int(i
.Bias
+i
.DaylightBias
) * 60
187 // Arrange so that d0 is first transition date, d1 second,
188 // i0 is index of zone after first transition, i1 second.
189 d0
:= &i
.StandardDate
190 d1
:= &i
.DaylightDate
193 if d0
.Month
> d1
.Month
{
198 // 2 tx per year, 100 years on each side of this year
199 l
.tx
= make([]zoneTrans
, 400)
204 for y
:= year
- 100; y
< year
+100; y
++ {
206 tx
.when
= pseudoUnix(y
, d0
) - int64(l
.zone
[i1
].offset
)
211 tx
.when
= pseudoUnix(y
, d1
) - int64(l
.zone
[i0
].offset
)
217 var usPacific
= syscall
.Timezoneinformation
{
219 StandardName
: [32]uint16{
220 'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e',
222 StandardDate
: syscall
.Systemtime
{Month
: 11, Day
: 1, Hour
: 2},
223 DaylightName
: [32]uint16{
224 'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e',
226 DaylightDate
: syscall
.Systemtime
{Month
: 3, Day
: 2, Hour
: 2},
230 var aus
= syscall
.Timezoneinformation
{
232 StandardName
: [32]uint16{
233 'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e',
235 StandardDate
: syscall
.Systemtime
{Month
: 4, Day
: 1, Hour
: 3},
236 DaylightName
: [32]uint16{
237 'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e',
239 DaylightDate
: syscall
.Systemtime
{Month
: 10, Day
: 1, Hour
: 2},
243 func initTestingZone() {
244 initLocalFromTZI(&usPacific
)
247 func initAusTestingZone() {
248 initLocalFromTZI(&aus
)
252 var i syscall
.Timezoneinformation
253 if _
, err
:= syscall
.GetTimeZoneInformation(&i
); err
!= nil {
254 localLoc
.name
= "UTC"
260 func loadLocation(name
string) (*Location
, error
) {
261 if z
, err
:= loadZoneFile(runtime
.GOROOT()+`\lib\time\zoneinfo.zip`, name
); err
== nil {
265 return nil, errors
.New("unknown time zone " + name
)
268 func forceZipFileForTesting(zipOnly
bool) {
269 // We only use the zip file anyway.