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