Source file src/time/zoneinfo.go

     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  	// The tzdata information can be followed by a string that describes
    25  	// how to handle DST transitions not recorded in zoneTrans.
    26  	// The format is the TZ environment variable without a colon; see
    27  	// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html.
    28  	// Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0
    29  	extend string
    30  
    31  	// Most lookups will be for the current time.
    32  	// To avoid the binary search through tx, keep a
    33  	// static one-element cache that gives the correct
    34  	// zone for the time when the Location was created.
    35  	// if cacheStart <= t < cacheEnd,
    36  	// lookup can return cacheZone.
    37  	// The units for cacheStart and cacheEnd are seconds
    38  	// since January 1, 1970 UTC, to match the argument
    39  	// to lookup.
    40  	cacheStart int64
    41  	cacheEnd   int64
    42  	cacheZone  *zone
    43  }
    44  
    45  // A zone represents a single time zone such as CET.
    46  type zone struct {
    47  	name   string // abbreviated name, "CET"
    48  	offset int    // seconds east of UTC
    49  	isDST  bool   // is this zone Daylight Savings Time?
    50  }
    51  
    52  // A zoneTrans represents a single time zone transition.
    53  type zoneTrans struct {
    54  	when         int64 // transition time, in seconds since 1970 GMT
    55  	index        uint8 // the index of the zone that goes into effect at that time
    56  	isstd, isutc bool  // ignored - no idea what these mean
    57  }
    58  
    59  // alpha and omega are the beginning and end of time for zone
    60  // transitions.
    61  const (
    62  	alpha = -1 << 63  // math.MinInt64
    63  	omega = 1<<63 - 1 // math.MaxInt64
    64  )
    65  
    66  // UTC represents Universal Coordinated Time (UTC).
    67  var UTC *Location = &utcLoc
    68  
    69  // utcLoc is separate so that get can refer to &utcLoc
    70  // and ensure that it never returns a nil *Location,
    71  // even if a badly behaved client has changed UTC.
    72  var utcLoc = Location{name: "UTC"}
    73  
    74  // Local represents the system's local time zone.
    75  // On Unix systems, Local consults the TZ environment
    76  // variable to find the time zone to use. No TZ means
    77  // use the system default /etc/localtime.
    78  // TZ="" means use UTC.
    79  // TZ="foo" means use file foo in the system timezone directory.
    80  var Local *Location = &localLoc
    81  
    82  // localLoc is separate so that initLocal can initialize
    83  // it even if a client has changed Local.
    84  var localLoc Location
    85  var localOnce sync.Once
    86  
    87  func (l *Location) get() *Location {
    88  	if l == nil {
    89  		return &utcLoc
    90  	}
    91  	if l == &localLoc {
    92  		localOnce.Do(initLocal)
    93  	}
    94  	return l
    95  }
    96  
    97  // String returns a descriptive name for the time zone information,
    98  // corresponding to the name argument to LoadLocation or FixedZone.
    99  func (l *Location) String() string {
   100  	return l.get().name
   101  }
   102  
   103  var unnamedFixedZones []*Location
   104  var unnamedFixedZonesOnce sync.Once
   105  
   106  // FixedZone returns a Location that always uses
   107  // the given zone name and offset (seconds east of UTC).
   108  func FixedZone(name string, offset int) *Location {
   109  	// Most calls to FixedZone have an unnamed zone with an offset by the hour.
   110  	// Optimize for that case by returning the same *Location for a given hour.
   111  	const hoursBeforeUTC = 12
   112  	const hoursAfterUTC = 14
   113  	hour := offset / 60 / 60
   114  	if name == "" && -hoursBeforeUTC <= hour && hour <= +hoursAfterUTC && hour*60*60 == offset {
   115  		unnamedFixedZonesOnce.Do(func() {
   116  			unnamedFixedZones = make([]*Location, hoursBeforeUTC+1+hoursAfterUTC)
   117  			for hr := -hoursBeforeUTC; hr <= +hoursAfterUTC; hr++ {
   118  				unnamedFixedZones[hr+hoursBeforeUTC] = fixedZone("", hr*60*60)
   119  			}
   120  		})
   121  		return unnamedFixedZones[hour+hoursBeforeUTC]
   122  	}
   123  	return fixedZone(name, offset)
   124  }
   125  
   126  func fixedZone(name string, offset int) *Location {
   127  	l := &Location{
   128  		name:       name,
   129  		zone:       []zone{{name, offset, false}},
   130  		tx:         []zoneTrans{{alpha, 0, false, false}},
   131  		cacheStart: alpha,
   132  		cacheEnd:   omega,
   133  	}
   134  	l.cacheZone = &l.zone[0]
   135  	return l
   136  }
   137  
   138  // lookup returns information about the time zone in use at an
   139  // instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
   140  //
   141  // The returned information gives the name of the zone (such as "CET"),
   142  // the start and end times bracketing sec when that zone is in effect,
   143  // the offset in seconds east of UTC (such as -5*60*60), and whether
   144  // the daylight savings is being observed at that time.
   145  func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) {
   146  	l = l.get()
   147  
   148  	if len(l.zone) == 0 {
   149  		name = "UTC"
   150  		offset = 0
   151  		start = alpha
   152  		end = omega
   153  		isDST = false
   154  		return
   155  	}
   156  
   157  	if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
   158  		name = zone.name
   159  		offset = zone.offset
   160  		start = l.cacheStart
   161  		end = l.cacheEnd
   162  		isDST = zone.isDST
   163  		return
   164  	}
   165  
   166  	if len(l.tx) == 0 || sec < l.tx[0].when {
   167  		zone := &l.zone[l.lookupFirstZone()]
   168  		name = zone.name
   169  		offset = zone.offset
   170  		start = alpha
   171  		if len(l.tx) > 0 {
   172  			end = l.tx[0].when
   173  		} else {
   174  			end = omega
   175  		}
   176  		isDST = zone.isDST
   177  		return
   178  	}
   179  
   180  	// Binary search for entry with largest time <= sec.
   181  	// Not using sort.Search to avoid dependencies.
   182  	tx := l.tx
   183  	end = omega
   184  	lo := 0
   185  	hi := len(tx)
   186  	for hi-lo > 1 {
   187  		m := lo + (hi-lo)/2
   188  		lim := tx[m].when
   189  		if sec < lim {
   190  			end = lim
   191  			hi = m
   192  		} else {
   193  			lo = m
   194  		}
   195  	}
   196  	zone := &l.zone[tx[lo].index]
   197  	name = zone.name
   198  	offset = zone.offset
   199  	start = tx[lo].when
   200  	// end = maintained during the search
   201  	isDST = zone.isDST
   202  
   203  	// If we're at the end of the known zone transitions,
   204  	// try the extend string.
   205  	if lo == len(tx)-1 && l.extend != "" {
   206  		if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, start, sec); ok {
   207  			return ename, eoffset, estart, eend, eisDST
   208  		}
   209  	}
   210  
   211  	return
   212  }
   213  
   214  // lookupFirstZone returns the index of the time zone to use for times
   215  // before the first transition time, or when there are no transition
   216  // times.
   217  //
   218  // The reference implementation in localtime.c from
   219  // https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
   220  // implements the following algorithm for these cases:
   221  //  1. If the first zone is unused by the transitions, use it.
   222  //  2. Otherwise, if there are transition times, and the first
   223  //     transition is to a zone in daylight time, find the first
   224  //     non-daylight-time zone before and closest to the first transition
   225  //     zone.
   226  //  3. Otherwise, use the first zone that is not daylight time, if
   227  //     there is one.
   228  //  4. Otherwise, use the first zone.
   229  func (l *Location) lookupFirstZone() int {
   230  	// Case 1.
   231  	if !l.firstZoneUsed() {
   232  		return 0
   233  	}
   234  
   235  	// Case 2.
   236  	if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
   237  		for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
   238  			if !l.zone[zi].isDST {
   239  				return zi
   240  			}
   241  		}
   242  	}
   243  
   244  	// Case 3.
   245  	for zi := range l.zone {
   246  		if !l.zone[zi].isDST {
   247  			return zi
   248  		}
   249  	}
   250  
   251  	// Case 4.
   252  	return 0
   253  }
   254  
   255  // firstZoneUsed reports whether the first zone is used by some
   256  // transition.
   257  func (l *Location) firstZoneUsed() bool {
   258  	for _, tx := range l.tx {
   259  		if tx.index == 0 {
   260  			return true
   261  		}
   262  	}
   263  	return false
   264  }
   265  
   266  // tzset takes a timezone string like the one found in the TZ environment
   267  // variable, the time of the last time zone transition expressed as seconds
   268  // since January 1, 1970 00:00:00 UTC, and a time expressed the same way.
   269  // We call this a tzset string since in C the function tzset reads TZ.
   270  // The return values are as for lookup, plus ok which reports whether the
   271  // parse succeeded.
   272  func tzset(s string, lastTxSec, sec int64) (name string, offset int, start, end int64, isDST, ok bool) {
   273  	var (
   274  		stdName, dstName     string
   275  		stdOffset, dstOffset int
   276  	)
   277  
   278  	stdName, s, ok = tzsetName(s)
   279  	if ok {
   280  		stdOffset, s, ok = tzsetOffset(s)
   281  	}
   282  	if !ok {
   283  		return "", 0, 0, 0, false, false
   284  	}
   285  
   286  	// The numbers in the tzset string are added to local time to get UTC,
   287  	// but our offsets are added to UTC to get local time,
   288  	// so we negate the number we see here.
   289  	stdOffset = -stdOffset
   290  
   291  	if len(s) == 0 || s[0] == ',' {
   292  		// No daylight savings time.
   293  		return stdName, stdOffset, lastTxSec, omega, false, true
   294  	}
   295  
   296  	dstName, s, ok = tzsetName(s)
   297  	if ok {
   298  		if len(s) == 0 || s[0] == ',' {
   299  			dstOffset = stdOffset + secondsPerHour
   300  		} else {
   301  			dstOffset, s, ok = tzsetOffset(s)
   302  			dstOffset = -dstOffset // as with stdOffset, above
   303  		}
   304  	}
   305  	if !ok {
   306  		return "", 0, 0, 0, false, false
   307  	}
   308  
   309  	if len(s) == 0 {
   310  		// Default DST rules per tzcode.
   311  		s = ",M3.2.0,M11.1.0"
   312  	}
   313  	// The TZ definition does not mention ';' here but tzcode accepts it.
   314  	if s[0] != ',' && s[0] != ';' {
   315  		return "", 0, 0, 0, false, false
   316  	}
   317  	s = s[1:]
   318  
   319  	var startRule, endRule rule
   320  	startRule, s, ok = tzsetRule(s)
   321  	if !ok || len(s) == 0 || s[0] != ',' {
   322  		return "", 0, 0, 0, false, false
   323  	}
   324  	s = s[1:]
   325  	endRule, s, ok = tzsetRule(s)
   326  	if !ok || len(s) > 0 {
   327  		return "", 0, 0, 0, false, false
   328  	}
   329  
   330  	year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)
   331  
   332  	ysec := int64(yday*secondsPerDay) + sec%secondsPerDay
   333  
   334  	// Compute start of year in seconds since Unix epoch.
   335  	d := daysSinceEpoch(year)
   336  	abs := int64(d * secondsPerDay)
   337  	abs += absoluteToInternal + internalToUnix
   338  
   339  	startSec := int64(tzruleTime(year, startRule, stdOffset))
   340  	endSec := int64(tzruleTime(year, endRule, dstOffset))
   341  	dstIsDST, stdIsDST := true, false
   342  	// Note: this is a flipping of "DST" and "STD" while retaining the labels
   343  	// This happens in southern hemispheres. The labelling here thus is a little
   344  	// inconsistent with the goal.
   345  	if endSec < startSec {
   346  		startSec, endSec = endSec, startSec
   347  		stdName, dstName = dstName, stdName
   348  		stdOffset, dstOffset = dstOffset, stdOffset
   349  		stdIsDST, dstIsDST = dstIsDST, stdIsDST
   350  	}
   351  
   352  	// The start and end values that we return are accurate
   353  	// close to a daylight savings transition, but are otherwise
   354  	// just the start and end of the year. That suffices for
   355  	// the only caller that cares, which is Date.
   356  	if ysec < startSec {
   357  		return stdName, stdOffset, abs, startSec + abs, stdIsDST, true
   358  	} else if ysec >= endSec {
   359  		return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true
   360  	} else {
   361  		return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true
   362  	}
   363  }
   364  
   365  // tzsetName returns the timezone name at the start of the tzset string s,
   366  // and the remainder of s, and reports whether the parsing is OK.
   367  func tzsetName(s string) (string, string, bool) {
   368  	if len(s) == 0 {
   369  		return "", "", false
   370  	}
   371  	if s[0] != '<' {
   372  		for i, r := range s {
   373  			switch r {
   374  			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
   375  				if i < 3 {
   376  					return "", "", false
   377  				}
   378  				return s[:i], s[i:], true
   379  			}
   380  		}
   381  		if len(s) < 3 {
   382  			return "", "", false
   383  		}
   384  		return s, "", true
   385  	} else {
   386  		for i, r := range s {
   387  			if r == '>' {
   388  				return s[1:i], s[i+1:], true
   389  			}
   390  		}
   391  		return "", "", false
   392  	}
   393  }
   394  
   395  // tzsetOffset returns the timezone offset at the start of the tzset string s,
   396  // and the remainder of s, and reports whether the parsing is OK.
   397  // The timezone offset is returned as a number of seconds.
   398  func tzsetOffset(s string) (offset int, rest string, ok bool) {
   399  	if len(s) == 0 {
   400  		return 0, "", false
   401  	}
   402  	neg := false
   403  	if s[0] == '+' {
   404  		s = s[1:]
   405  	} else if s[0] == '-' {
   406  		s = s[1:]
   407  		neg = true
   408  	}
   409  
   410  	// The tzdata code permits values up to 24 * 7 here,
   411  	// although POSIX does not.
   412  	var hours int
   413  	hours, s, ok = tzsetNum(s, 0, 24*7)
   414  	if !ok {
   415  		return 0, "", false
   416  	}
   417  	off := hours * secondsPerHour
   418  	if len(s) == 0 || s[0] != ':' {
   419  		if neg {
   420  			off = -off
   421  		}
   422  		return off, s, true
   423  	}
   424  
   425  	var mins int
   426  	mins, s, ok = tzsetNum(s[1:], 0, 59)
   427  	if !ok {
   428  		return 0, "", false
   429  	}
   430  	off += mins * secondsPerMinute
   431  	if len(s) == 0 || s[0] != ':' {
   432  		if neg {
   433  			off = -off
   434  		}
   435  		return off, s, true
   436  	}
   437  
   438  	var secs int
   439  	secs, s, ok = tzsetNum(s[1:], 0, 59)
   440  	if !ok {
   441  		return 0, "", false
   442  	}
   443  	off += secs
   444  
   445  	if neg {
   446  		off = -off
   447  	}
   448  	return off, s, true
   449  }
   450  
   451  // ruleKind is the kinds of rules that can be seen in a tzset string.
   452  type ruleKind int
   453  
   454  const (
   455  	ruleJulian ruleKind = iota
   456  	ruleDOY
   457  	ruleMonthWeekDay
   458  )
   459  
   460  // rule is a rule read from a tzset string.
   461  type rule struct {
   462  	kind ruleKind
   463  	day  int
   464  	week int
   465  	mon  int
   466  	time int // transition time
   467  }
   468  
   469  // tzsetRule parses a rule from a tzset string.
   470  // It returns the rule, and the remainder of the string, and reports success.
   471  func tzsetRule(s string) (rule, string, bool) {
   472  	var r rule
   473  	if len(s) == 0 {
   474  		return rule{}, "", false
   475  	}
   476  	ok := false
   477  	if s[0] == 'J' {
   478  		var jday int
   479  		jday, s, ok = tzsetNum(s[1:], 1, 365)
   480  		if !ok {
   481  			return rule{}, "", false
   482  		}
   483  		r.kind = ruleJulian
   484  		r.day = jday
   485  	} else if s[0] == 'M' {
   486  		var mon int
   487  		mon, s, ok = tzsetNum(s[1:], 1, 12)
   488  		if !ok || len(s) == 0 || s[0] != '.' {
   489  			return rule{}, "", false
   490  
   491  		}
   492  		var week int
   493  		week, s, ok = tzsetNum(s[1:], 1, 5)
   494  		if !ok || len(s) == 0 || s[0] != '.' {
   495  			return rule{}, "", false
   496  		}
   497  		var day int
   498  		day, s, ok = tzsetNum(s[1:], 0, 6)
   499  		if !ok {
   500  			return rule{}, "", false
   501  		}
   502  		r.kind = ruleMonthWeekDay
   503  		r.day = day
   504  		r.week = week
   505  		r.mon = mon
   506  	} else {
   507  		var day int
   508  		day, s, ok = tzsetNum(s, 0, 365)
   509  		if !ok {
   510  			return rule{}, "", false
   511  		}
   512  		r.kind = ruleDOY
   513  		r.day = day
   514  	}
   515  
   516  	if len(s) == 0 || s[0] != '/' {
   517  		r.time = 2 * secondsPerHour // 2am is the default
   518  		return r, s, true
   519  	}
   520  
   521  	offset, s, ok := tzsetOffset(s[1:])
   522  	if !ok {
   523  		return rule{}, "", false
   524  	}
   525  	r.time = offset
   526  
   527  	return r, s, true
   528  }
   529  
   530  // tzsetNum parses a number from a tzset string.
   531  // It returns the number, and the remainder of the string, and reports success.
   532  // The number must be between min and max.
   533  func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
   534  	if len(s) == 0 {
   535  		return 0, "", false
   536  	}
   537  	num = 0
   538  	for i, r := range s {
   539  		if r < '0' || r > '9' {
   540  			if i == 0 || num < min {
   541  				return 0, "", false
   542  			}
   543  			return num, s[i:], true
   544  		}
   545  		num *= 10
   546  		num += int(r) - '0'
   547  		if num > max {
   548  			return 0, "", false
   549  		}
   550  	}
   551  	if num < min {
   552  		return 0, "", false
   553  	}
   554  	return num, "", true
   555  }
   556  
   557  // tzruleTime takes a year, a rule, and a timezone offset,
   558  // and returns the number of seconds since the start of the year
   559  // that the rule takes effect.
   560  func tzruleTime(year int, r rule, off int) int {
   561  	var s int
   562  	switch r.kind {
   563  	case ruleJulian:
   564  		s = (r.day - 1) * secondsPerDay
   565  		if isLeap(year) && r.day >= 60 {
   566  			s += secondsPerDay
   567  		}
   568  	case ruleDOY:
   569  		s = r.day * secondsPerDay
   570  	case ruleMonthWeekDay:
   571  		// Zeller's Congruence.
   572  		m1 := (r.mon+9)%12 + 1
   573  		yy0 := year
   574  		if r.mon <= 2 {
   575  			yy0--
   576  		}
   577  		yy1 := yy0 / 100
   578  		yy2 := yy0 % 100
   579  		dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
   580  		if dow < 0 {
   581  			dow += 7
   582  		}
   583  		// Now dow is the day-of-week of the first day of r.mon.
   584  		// Get the day-of-month of the first "dow" day.
   585  		d := r.day - dow
   586  		if d < 0 {
   587  			d += 7
   588  		}
   589  		for i := 1; i < r.week; i++ {
   590  			if d+7 >= daysIn(Month(r.mon), year) {
   591  				break
   592  			}
   593  			d += 7
   594  		}
   595  		d += int(daysBefore[r.mon-1])
   596  		if isLeap(year) && r.mon > 2 {
   597  			d++
   598  		}
   599  		s = d * secondsPerDay
   600  	}
   601  
   602  	return s + r.time - off
   603  }
   604  
   605  // lookupName returns information about the time zone with
   606  // the given name (such as "EST") at the given pseudo-Unix time
   607  // (what the given time of day would be in UTC).
   608  func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
   609  	l = l.get()
   610  
   611  	// First try for a zone with the right name that was actually
   612  	// in effect at the given time. (In Sydney, Australia, both standard
   613  	// and daylight-savings time are abbreviated "EST". Using the
   614  	// offset helps us pick the right one for the given time.
   615  	// It's not perfect: during the backward transition we might pick
   616  	// either one.)
   617  	for i := range l.zone {
   618  		zone := &l.zone[i]
   619  		if zone.name == name {
   620  			nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
   621  			if nam == zone.name {
   622  				return offset, true
   623  			}
   624  		}
   625  	}
   626  
   627  	// Otherwise fall back to an ordinary name match.
   628  	for i := range l.zone {
   629  		zone := &l.zone[i]
   630  		if zone.name == name {
   631  			return zone.offset, true
   632  		}
   633  	}
   634  
   635  	// Otherwise, give up.
   636  	return
   637  }
   638  
   639  // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
   640  // syntax too, but I don't feel like implementing it today.
   641  
   642  var errLocation = errors.New("time: invalid location name")
   643  
   644  var zoneinfo *string
   645  var zoneinfoOnce sync.Once
   646  
   647  // LoadLocation returns the Location with the given name.
   648  //
   649  // If the name is "" or "UTC", LoadLocation returns UTC.
   650  // If the name is "Local", LoadLocation returns Local.
   651  //
   652  // Otherwise, the name is taken to be a location name corresponding to a file
   653  // in the IANA Time Zone database, such as "America/New_York".
   654  //
   655  // LoadLocation looks for the IANA Time Zone database in the following
   656  // locations in order:
   657  //
   658  //   - the directory or uncompressed zip file named by the ZONEINFO environment variable
   659  //   - on a Unix system, the system standard installation location
   660  //   - $GOROOT/lib/time/zoneinfo.zip
   661  //   - the time/tzdata package, if it was imported
   662  func LoadLocation(name string) (*Location, error) {
   663  	if name == "" || name == "UTC" {
   664  		return UTC, nil
   665  	}
   666  	if name == "Local" {
   667  		return Local, nil
   668  	}
   669  	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
   670  		// No valid IANA Time Zone name contains a single dot,
   671  		// much less dot dot. Likewise, none begin with a slash.
   672  		return nil, errLocation
   673  	}
   674  	zoneinfoOnce.Do(func() {
   675  		env, _ := syscall.Getenv("ZONEINFO")
   676  		zoneinfo = &env
   677  	})
   678  	var firstErr error
   679  	if *zoneinfo != "" {
   680  		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
   681  			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
   682  				return z, nil
   683  			}
   684  			firstErr = err
   685  		} else if err != syscall.ENOENT {
   686  			firstErr = err
   687  		}
   688  	}
   689  	if z, err := loadLocation(name, platformZoneSources); err == nil {
   690  		return z, nil
   691  	} else if firstErr == nil {
   692  		firstErr = err
   693  	}
   694  	return nil, firstErr
   695  }
   696  
   697  // containsDotDot reports whether s contains "..".
   698  func containsDotDot(s string) bool {
   699  	if len(s) < 2 {
   700  		return false
   701  	}
   702  	for i := 0; i < len(s)-1; i++ {
   703  		if s[i] == '.' && s[i+1] == '.' {
   704  			return true
   705  		}
   706  	}
   707  	return false
   708  }
   709  

View as plain text