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

View as plain text