github.com/c12o16h1/go/src@v0.0.0-20200114212001-5a151c0f00ed/time/zoneinfo.go (about) 1 // Copyright 2011 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 "sync" 10 "syscall" 11 ) 12 13 //go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go 14 15 // A Location maps time instants to the zone in use at that time. 16 // Typically, the Location represents the collection of time offsets 17 // in use in a geographical area. For many Locations the time offset varies 18 // depending on whether daylight savings time is in use at the time instant. 19 type Location struct { 20 name string 21 zone []zone 22 tx []zoneTrans 23 24 // Most lookups will be for the current time. 25 // To avoid the binary search through tx, keep a 26 // static one-element cache that gives the correct 27 // zone for the time when the Location was created. 28 // if cacheStart <= t < cacheEnd, 29 // lookup can return cacheZone. 30 // The units for cacheStart and cacheEnd are seconds 31 // since January 1, 1970 UTC, to match the argument 32 // to lookup. 33 cacheStart int64 34 cacheEnd int64 35 cacheZone *zone 36 } 37 38 // A zone represents a single time zone such as CET. 39 type zone struct { 40 name string // abbreviated name, "CET" 41 offset int // seconds east of UTC 42 isDST bool // is this zone Daylight Savings Time? 43 } 44 45 // A zoneTrans represents a single time zone transition. 46 type zoneTrans struct { 47 when int64 // transition time, in seconds since 1970 GMT 48 index uint8 // the index of the zone that goes into effect at that time 49 isstd, isutc bool // ignored - no idea what these mean 50 } 51 52 // alpha and omega are the beginning and end of time for zone 53 // transitions. 54 const ( 55 alpha = -1 << 63 // math.MinInt64 56 omega = 1<<63 - 1 // math.MaxInt64 57 ) 58 59 // UTC represents Universal Coordinated Time (UTC). 60 var UTC *Location = &utcLoc 61 62 // utcLoc is separate so that get can refer to &utcLoc 63 // and ensure that it never returns a nil *Location, 64 // even if a badly behaved client has changed UTC. 65 var utcLoc = Location{name: "UTC"} 66 67 // Local represents the system's local time zone. 68 // On Unix systems, Local consults the TZ environment 69 // variable to find the time zone to use. No TZ means 70 // use the system default /etc/localtime. 71 // TZ="" means use UTC. 72 // TZ="foo" means use file foo in the system timezone directory. 73 var Local *Location = &localLoc 74 75 // localLoc is separate so that initLocal can initialize 76 // it even if a client has changed Local. 77 var localLoc Location 78 var localOnce sync.Once 79 80 func (l *Location) get() *Location { 81 if l == nil { 82 return &utcLoc 83 } 84 if l == &localLoc { 85 localOnce.Do(initLocal) 86 } 87 return l 88 } 89 90 // String returns a descriptive name for the time zone information, 91 // corresponding to the name argument to LoadLocation or FixedZone. 92 func (l *Location) String() string { 93 return l.get().name 94 } 95 96 // FixedZone returns a Location that always uses 97 // the given zone name and offset (seconds east of UTC). 98 func FixedZone(name string, offset int) *Location { 99 l := &Location{ 100 name: name, 101 zone: []zone{{name, offset, false}}, 102 tx: []zoneTrans{{alpha, 0, false, false}}, 103 cacheStart: alpha, 104 cacheEnd: omega, 105 } 106 l.cacheZone = &l.zone[0] 107 return l 108 } 109 110 // lookup returns information about the time zone in use at an 111 // instant in time expressed as seconds since January 1, 1970 00:00:00 UTC. 112 // 113 // The returned information gives the name of the zone (such as "CET"), 114 // the start and end times bracketing sec when that zone is in effect, 115 // the offset in seconds east of UTC (such as -5*60*60), and whether 116 // the daylight savings is being observed at that time. 117 func (l *Location) lookup(sec int64) (name string, offset int, start, end int64) { 118 l = l.get() 119 120 if len(l.zone) == 0 { 121 name = "UTC" 122 offset = 0 123 start = alpha 124 end = omega 125 return 126 } 127 128 if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd { 129 name = zone.name 130 offset = zone.offset 131 start = l.cacheStart 132 end = l.cacheEnd 133 return 134 } 135 136 if len(l.tx) == 0 || sec < l.tx[0].when { 137 zone := &l.zone[l.lookupFirstZone()] 138 name = zone.name 139 offset = zone.offset 140 start = alpha 141 if len(l.tx) > 0 { 142 end = l.tx[0].when 143 } else { 144 end = omega 145 } 146 return 147 } 148 149 // Binary search for entry with largest time <= sec. 150 // Not using sort.Search to avoid dependencies. 151 tx := l.tx 152 end = omega 153 lo := 0 154 hi := len(tx) 155 for hi-lo > 1 { 156 m := lo + (hi-lo)/2 157 lim := tx[m].when 158 if sec < lim { 159 end = lim 160 hi = m 161 } else { 162 lo = m 163 } 164 } 165 zone := &l.zone[tx[lo].index] 166 name = zone.name 167 offset = zone.offset 168 start = tx[lo].when 169 // end = maintained during the search 170 return 171 } 172 173 // lookupFirstZone returns the index of the time zone to use for times 174 // before the first transition time, or when there are no transition 175 // times. 176 // 177 // The reference implementation in localtime.c from 178 // https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz 179 // implements the following algorithm for these cases: 180 // 1) If the first zone is unused by the transitions, use it. 181 // 2) Otherwise, if there are transition times, and the first 182 // transition is to a zone in daylight time, find the first 183 // non-daylight-time zone before and closest to the first transition 184 // zone. 185 // 3) Otherwise, use the first zone that is not daylight time, if 186 // there is one. 187 // 4) Otherwise, use the first zone. 188 func (l *Location) lookupFirstZone() int { 189 // Case 1. 190 if !l.firstZoneUsed() { 191 return 0 192 } 193 194 // Case 2. 195 if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST { 196 for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- { 197 if !l.zone[zi].isDST { 198 return zi 199 } 200 } 201 } 202 203 // Case 3. 204 for zi := range l.zone { 205 if !l.zone[zi].isDST { 206 return zi 207 } 208 } 209 210 // Case 4. 211 return 0 212 } 213 214 // firstZoneUsed reports whether the first zone is used by some 215 // transition. 216 func (l *Location) firstZoneUsed() bool { 217 for _, tx := range l.tx { 218 if tx.index == 0 { 219 return true 220 } 221 } 222 return false 223 } 224 225 // lookupName returns information about the time zone with 226 // the given name (such as "EST") at the given pseudo-Unix time 227 // (what the given time of day would be in UTC). 228 func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) { 229 l = l.get() 230 231 // First try for a zone with the right name that was actually 232 // in effect at the given time. (In Sydney, Australia, both standard 233 // and daylight-savings time are abbreviated "EST". Using the 234 // offset helps us pick the right one for the given time. 235 // It's not perfect: during the backward transition we might pick 236 // either one.) 237 for i := range l.zone { 238 zone := &l.zone[i] 239 if zone.name == name { 240 nam, offset, _, _ := l.lookup(unix - int64(zone.offset)) 241 if nam == zone.name { 242 return offset, true 243 } 244 } 245 } 246 247 // Otherwise fall back to an ordinary name match. 248 for i := range l.zone { 249 zone := &l.zone[i] 250 if zone.name == name { 251 return zone.offset, true 252 } 253 } 254 255 // Otherwise, give up. 256 return 257 } 258 259 // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment 260 // syntax too, but I don't feel like implementing it today. 261 262 var errLocation = errors.New("time: invalid location name") 263 264 var zoneinfo *string 265 var zoneinfoOnce sync.Once 266 267 // LoadLocation returns the Location with the given name. 268 // 269 // If the name is "" or "UTC", LoadLocation returns UTC. 270 // If the name is "Local", LoadLocation returns Local. 271 // 272 // Otherwise, the name is taken to be a location name corresponding to a file 273 // in the IANA Time Zone database, such as "America/New_York". 274 // 275 // The time zone database needed by LoadLocation may not be 276 // present on all systems, especially non-Unix systems. 277 // LoadLocation looks in the directory or uncompressed zip file 278 // named by the ZONEINFO environment variable, if any, then looks in 279 // known installation locations on Unix systems, 280 // and finally looks in $GOROOT/lib/time/zoneinfo.zip. 281 func LoadLocation(name string) (*Location, error) { 282 if name == "" || name == "UTC" { 283 return UTC, nil 284 } 285 if name == "Local" { 286 return Local, nil 287 } 288 if containsDotDot(name) || name[0] == '/' || name[0] == '\\' { 289 // No valid IANA Time Zone name contains a single dot, 290 // much less dot dot. Likewise, none begin with a slash. 291 return nil, errLocation 292 } 293 zoneinfoOnce.Do(func() { 294 env, _ := syscall.Getenv("ZONEINFO") 295 zoneinfo = &env 296 }) 297 var firstErr error 298 if *zoneinfo != "" { 299 if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil { 300 if z, err := LoadLocationFromTZData(name, zoneData); err == nil { 301 return z, nil 302 } 303 firstErr = err 304 } else if err != syscall.ENOENT { 305 firstErr = err 306 } 307 } 308 if z, err := loadLocation(name, zoneSources); err == nil { 309 return z, nil 310 } else if firstErr == nil { 311 firstErr = err 312 } 313 return nil, firstErr 314 } 315 316 // containsDotDot reports whether s contains "..". 317 func containsDotDot(s string) bool { 318 if len(s) < 2 { 319 return false 320 } 321 for i := 0; i < len(s)-1; i++ { 322 if s[i] == '.' && s[i+1] == '.' { 323 return true 324 } 325 } 326 return false 327 }