github.com/roboticscm/goman@v0.0.0-20210203095141-87c07b4a0a55/src/time/zoneinfo_windows.go (about) 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. 4 5 package time 6 7 import ( 8 "errors" 9 "runtime" 10 "syscall" 11 "unsafe" 12 ) 13 14 //go:generate go run genzabbrs.go -output zoneinfo_abbrs_windows.go 15 16 // TODO(rsc): Fall back to copy of zoneinfo files. 17 18 // BUG(brainman,rsc): On Windows, the operating system does not provide complete 19 // time zone information. 20 // The implementation assumes that this year's rules for daylight savings 21 // time apply to all previous and future years as well. 22 23 // getKeyValue retrieves the string value kname associated with the open registry key kh. 24 func getKeyValue(kh syscall.Handle, kname string) (string, error) { 25 var buf [50]uint16 // buf needs to be large enough to fit zone descriptions 26 var typ uint32 27 n := uint32(len(buf) * 2) // RegQueryValueEx's signature expects array of bytes, not uint16 28 p, _ := syscall.UTF16PtrFromString(kname) 29 if err := syscall.RegQueryValueEx(kh, p, nil, &typ, (*byte)(unsafe.Pointer(&buf[0])), &n); err != nil { 30 return "", err 31 } 32 if typ != syscall.REG_SZ { // null terminated strings only 33 return "", errors.New("Key is not string") 34 } 35 return syscall.UTF16ToString(buf[:]), nil 36 } 37 38 // matchZoneKey checks if stdname and dstname match the corresponding "Std" 39 // and "Dlt" key values in the kname key stored under the open registry key zones. 40 func matchZoneKey(zones syscall.Handle, kname string, stdname, dstname string) (matched bool, err2 error) { 41 var h syscall.Handle 42 p, _ := syscall.UTF16PtrFromString(kname) 43 if err := syscall.RegOpenKeyEx(zones, p, 0, syscall.KEY_READ, &h); err != nil { 44 return false, err 45 } 46 defer syscall.RegCloseKey(h) 47 48 s, err := getKeyValue(h, "Std") 49 if err != nil { 50 return false, err 51 } 52 if s != stdname { 53 return false, nil 54 } 55 s, err = getKeyValue(h, "Dlt") 56 if err != nil { 57 return false, err 58 } 59 if s != dstname && dstname != stdname { 60 return false, nil 61 } 62 return true, nil 63 } 64 65 // toEnglishName searches the registry for an English name of a time zone 66 // whose zone names are stdname and dstname and returns the English name. 67 func toEnglishName(stdname, dstname string) (string, error) { 68 var zones syscall.Handle 69 p, _ := syscall.UTF16PtrFromString(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`) 70 if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, p, 0, syscall.KEY_READ, &zones); err != nil { 71 return "", err 72 } 73 defer syscall.RegCloseKey(zones) 74 75 var count uint32 76 if err := syscall.RegQueryInfoKey(zones, nil, nil, nil, &count, nil, nil, nil, nil, nil, nil, nil); err != nil { 77 return "", err 78 } 79 80 var buf [50]uint16 // buf needs to be large enough to fit zone descriptions 81 for i := uint32(0); i < count; i++ { 82 n := uint32(len(buf)) 83 if syscall.RegEnumKeyEx(zones, i, &buf[0], &n, nil, nil, nil, nil) != nil { 84 continue 85 } 86 kname := syscall.UTF16ToString(buf[:]) 87 matched, err := matchZoneKey(zones, kname, stdname, dstname) 88 if err == nil && matched { 89 return kname, nil 90 } 91 } 92 return "", errors.New(`English name for time zone "` + stdname + `" not found in registry`) 93 } 94 95 // extractCAPS extracts capital letters from description desc. 96 func extractCAPS(desc string) string { 97 var short []rune 98 for _, c := range desc { 99 if 'A' <= c && c <= 'Z' { 100 short = append(short, rune(c)) 101 } 102 } 103 return string(short) 104 } 105 106 // abbrev returns the abbreviations to use for the given zone z. 107 func abbrev(z *syscall.Timezoneinformation) (std, dst string) { 108 stdName := syscall.UTF16ToString(z.StandardName[:]) 109 a, ok := abbrs[stdName] 110 if !ok { 111 dstName := syscall.UTF16ToString(z.DaylightName[:]) 112 // Perhaps stdName is not English. Try to convert it. 113 englishName, err := toEnglishName(stdName, dstName) 114 if err == nil { 115 a, ok = abbrs[englishName] 116 if ok { 117 return a.std, a.dst 118 } 119 } 120 // fallback to using capital letters 121 return extractCAPS(stdName), extractCAPS(dstName) 122 } 123 return a.std, a.dst 124 } 125 126 // pseudoUnix returns the pseudo-Unix time (seconds since Jan 1 1970 *LOCAL TIME*) 127 // denoted by the system date+time d in the given year. 128 // It is up to the caller to convert this local time into a UTC-based time. 129 func pseudoUnix(year int, d *syscall.Systemtime) int64 { 130 // Windows specifies daylight savings information in "day in month" format: 131 // d.Month is month number (1-12) 132 // d.DayOfWeek is appropriate weekday (Sunday=0 to Saturday=6) 133 // d.Day is week within the month (1 to 5, where 5 is last week of the month) 134 // d.Hour, d.Minute and d.Second are absolute time 135 day := 1 136 t := Date(year, Month(d.Month), day, int(d.Hour), int(d.Minute), int(d.Second), 0, UTC) 137 i := int(d.DayOfWeek) - int(t.Weekday()) 138 if i < 0 { 139 i += 7 140 } 141 day += i 142 if week := int(d.Day) - 1; week < 4 { 143 day += week * 7 144 } else { 145 // "Last" instance of the day. 146 day += 4 * 7 147 if day > daysIn(Month(d.Month), year) { 148 day -= 7 149 } 150 } 151 return t.sec + int64(day-1)*secondsPerDay + internalToUnix 152 } 153 154 func initLocalFromTZI(i *syscall.Timezoneinformation) { 155 l := &localLoc 156 157 nzone := 1 158 if i.StandardDate.Month > 0 { 159 nzone++ 160 } 161 l.zone = make([]zone, nzone) 162 163 stdname, dstname := abbrev(i) 164 165 std := &l.zone[0] 166 std.name = stdname 167 if nzone == 1 { 168 // No daylight savings. 169 std.offset = -int(i.Bias) * 60 170 l.cacheStart = alpha 171 l.cacheEnd = omega 172 l.cacheZone = std 173 l.tx = make([]zoneTrans, 1) 174 l.tx[0].when = l.cacheStart 175 l.tx[0].index = 0 176 return 177 } 178 179 // StandardBias must be ignored if StandardDate is not set, 180 // so this computation is delayed until after the nzone==1 181 // return above. 182 std.offset = -int(i.Bias+i.StandardBias) * 60 183 184 dst := &l.zone[1] 185 dst.name = dstname 186 dst.offset = -int(i.Bias+i.DaylightBias) * 60 187 dst.isDST = true 188 189 // Arrange so that d0 is first transition date, d1 second, 190 // i0 is index of zone after first transition, i1 second. 191 d0 := &i.StandardDate 192 d1 := &i.DaylightDate 193 i0 := 0 194 i1 := 1 195 if d0.Month > d1.Month { 196 d0, d1 = d1, d0 197 i0, i1 = i1, i0 198 } 199 200 // 2 tx per year, 100 years on each side of this year 201 l.tx = make([]zoneTrans, 400) 202 203 t := Now().UTC() 204 year := t.Year() 205 txi := 0 206 for y := year - 100; y < year+100; y++ { 207 tx := &l.tx[txi] 208 tx.when = pseudoUnix(y, d0) - int64(l.zone[i1].offset) 209 tx.index = uint8(i0) 210 txi++ 211 212 tx = &l.tx[txi] 213 tx.when = pseudoUnix(y, d1) - int64(l.zone[i0].offset) 214 tx.index = uint8(i1) 215 txi++ 216 } 217 } 218 219 var usPacific = syscall.Timezoneinformation{ 220 Bias: 8 * 60, 221 StandardName: [32]uint16{ 222 'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e', 223 }, 224 StandardDate: syscall.Systemtime{Month: 11, Day: 1, Hour: 2}, 225 DaylightName: [32]uint16{ 226 'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e', 227 }, 228 DaylightDate: syscall.Systemtime{Month: 3, Day: 2, Hour: 2}, 229 DaylightBias: -60, 230 } 231 232 var aus = syscall.Timezoneinformation{ 233 Bias: -10 * 60, 234 StandardName: [32]uint16{ 235 'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e', 236 }, 237 StandardDate: syscall.Systemtime{Month: 4, Day: 1, Hour: 3}, 238 DaylightName: [32]uint16{ 239 'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e', 240 }, 241 DaylightDate: syscall.Systemtime{Month: 10, Day: 1, Hour: 2}, 242 DaylightBias: -60, 243 } 244 245 func initTestingZone() { 246 initLocalFromTZI(&usPacific) 247 } 248 249 func initAusTestingZone() { 250 initLocalFromTZI(&aus) 251 } 252 253 func initLocal() { 254 var i syscall.Timezoneinformation 255 if _, err := syscall.GetTimeZoneInformation(&i); err != nil { 256 localLoc.name = "UTC" 257 return 258 } 259 initLocalFromTZI(&i) 260 } 261 262 func loadLocation(name string) (*Location, error) { 263 if z, err := loadZoneFile(runtime.GOROOT()+`\lib\time\zoneinfo.zip`, name); err == nil { 264 z.name = name 265 return z, nil 266 } 267 return nil, errors.New("unknown time zone " + name) 268 } 269 270 func forceZipFileForTesting(zipOnly bool) { 271 // We only use the zip file anyway. 272 }