zoneinfo_windows.mx raw

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