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  	// Compute start of year in seconds since Unix epoch,
   335  	// and seconds since then to get to sec.
   336  	year, yday := absSeconds(sec + unixToInternal + internalToAbsolute).days().yearYday()
   337  	ysec := int64((yday-1)*secondsPerDay) + sec%secondsPerDay
   338  	ystart := sec - ysec
   339  
   340  	startSec := int64(tzruleTime(year, startRule, stdOffset))
   341  	endSec := int64(tzruleTime(year, endRule, dstOffset))
   342  	dstIsDST, stdIsDST := true, false
   343  	// Note: this is a flipping of "DST" and "STD" while retaining the labels
   344  	// This happens in southern hemispheres. The labelling here thus is a little
   345  	// inconsistent with the goal.
   346  	if endSec < startSec {
   347  		startSec, endSec = endSec, startSec
   348  		stdName, dstName = dstName, stdName
   349  		stdOffset, dstOffset = dstOffset, stdOffset
   350  		stdIsDST, dstIsDST = dstIsDST, stdIsDST
   351  	}
   352  
   353  	// The start and end values that we return are accurate
   354  	// close to a daylight savings transition, but are otherwise
   355  	// just the start and end of the year. That suffices for
   356  	// the only caller that cares, which is Date.
   357  	if ysec < startSec {
   358  		return stdName, stdOffset, ystart, startSec + ystart, stdIsDST, true
   359  	} else if ysec >= endSec {
   360  		return stdName, stdOffset, endSec + ystart, ystart + 365*secondsPerDay, stdIsDST, true
   361  	} else {
   362  		return dstName, dstOffset, startSec + ystart, endSec + ystart, dstIsDST, true
   363  	}
   364  }
   365  
   366  // tzsetName returns the timezone name at the start of the tzset string s,
   367  // and the remainder of s, and reports whether the parsing is OK.
   368  func tzsetName(s string) (string, string, bool) {
   369  	if len(s) == 0 {
   370  		return "", "", false
   371  	}
   372  	if s[0] != '<' {
   373  		for i, r := range s {
   374  			switch r {
   375  			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
   376  				if i < 3 {
   377  					return "", "", false
   378  				}
   379  				return s[:i], s[i:], true
   380  			}
   381  		}
   382  		if len(s) < 3 {
   383  			return "", "", false
   384  		}
   385  		return s, "", true
   386  	} else {
   387  		for i, r := range s {
   388  			if r == '>' {
   389  				return s[1:i], s[i+1:], true
   390  			}
   391  		}
   392  		return "", "", false
   393  	}
   394  }
   395  
   396  // tzsetOffset returns the timezone offset at the start of the tzset string s,
   397  // and the remainder of s, and reports whether the parsing is OK.
   398  // The timezone offset is returned as a number of seconds.
   399  func tzsetOffset(s string) (offset int, rest string, ok bool) {
   400  	if len(s) == 0 {
   401  		return 0, "", false
   402  	}
   403  	neg := false
   404  	if s[0] == '+' {
   405  		s = s[1:]
   406  	} else if s[0] == '-' {
   407  		s = s[1:]
   408  		neg = true
   409  	}
   410  
   411  	// The tzdata code permits values up to 24 * 7 here,
   412  	// although POSIX does not.
   413  	var hours int
   414  	hours, s, ok = tzsetNum(s, 0, 24*7)
   415  	if !ok {
   416  		return 0, "", false
   417  	}
   418  	off := hours * secondsPerHour
   419  	if len(s) == 0 || s[0] != ':' {
   420  		if neg {
   421  			off = -off
   422  		}
   423  		return off, s, true
   424  	}
   425  
   426  	var mins int
   427  	mins, s, ok = tzsetNum(s[1:], 0, 59)
   428  	if !ok {
   429  		return 0, "", false
   430  	}
   431  	off += mins * secondsPerMinute
   432  	if len(s) == 0 || s[0] != ':' {
   433  		if neg {
   434  			off = -off
   435  		}
   436  		return off, s, true
   437  	}
   438  
   439  	var secs int
   440  	secs, s, ok = tzsetNum(s[1:], 0, 59)
   441  	if !ok {
   442  		return 0, "", false
   443  	}
   444  	off += secs
   445  
   446  	if neg {
   447  		off = -off
   448  	}
   449  	return off, s, true
   450  }
   451  
   452  // ruleKind is the kinds of rules that can be seen in a tzset string.
   453  type ruleKind int
   454  
   455  const (
   456  	ruleJulian ruleKind = iota
   457  	ruleDOY
   458  	ruleMonthWeekDay
   459  )
   460  
   461  // rule is a rule read from a tzset string.
   462  type rule struct {
   463  	kind ruleKind
   464  	day  int
   465  	week int
   466  	mon  int
   467  	time int // transition time
   468  }
   469  
   470  // tzsetRule parses a rule from a tzset string.
   471  // It returns the rule, and the remainder of the string, and reports success.
   472  func tzsetRule(s string) (rule, string, bool) {
   473  	var r rule
   474  	if len(s) == 0 {
   475  		return rule{}, "", false
   476  	}
   477  	ok := false
   478  	if s[0] == 'J' {
   479  		var jday int
   480  		jday, s, ok = tzsetNum(s[1:], 1, 365)
   481  		if !ok {
   482  			return rule{}, "", false
   483  		}
   484  		r.kind = ruleJulian
   485  		r.day = jday
   486  	} else if s[0] == 'M' {
   487  		var mon int
   488  		mon, s, ok = tzsetNum(s[1:], 1, 12)
   489  		if !ok || len(s) == 0 || s[0] != '.' {
   490  			return rule{}, "", false
   491  
   492  		}
   493  		var week int
   494  		week, s, ok = tzsetNum(s[1:], 1, 5)
   495  		if !ok || len(s) == 0 || s[0] != '.' {
   496  			return rule{}, "", false
   497  		}
   498  		var day int
   499  		day, s, ok = tzsetNum(s[1:], 0, 6)
   500  		if !ok {
   501  			return rule{}, "", false
   502  		}
   503  		r.kind = ruleMonthWeekDay
   504  		r.day = day
   505  		r.week = week
   506  		r.mon = mon
   507  	} else {
   508  		var day int
   509  		day, s, ok = tzsetNum(s, 0, 365)
   510  		if !ok {
   511  			return rule{}, "", false
   512  		}
   513  		r.kind = ruleDOY
   514  		r.day = day
   515  	}
   516  
   517  	if len(s) == 0 || s[0] != '/' {
   518  		r.time = 2 * secondsPerHour // 2am is the default
   519  		return r, s, true
   520  	}
   521  
   522  	offset, s, ok := tzsetOffset(s[1:])
   523  	if !ok {
   524  		return rule{}, "", false
   525  	}
   526  	r.time = offset
   527  
   528  	return r, s, true
   529  }
   530  
   531  // tzsetNum parses a number from a tzset string.
   532  // It returns the number, and the remainder of the string, and reports success.
   533  // The number must be between min and max.
   534  func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
   535  	if len(s) == 0 {
   536  		return 0, "", false
   537  	}
   538  	num = 0
   539  	for i, r := range s {
   540  		if r < '0' || r > '9' {
   541  			if i == 0 || num < min {
   542  				return 0, "", false
   543  			}
   544  			return num, s[i:], true
   545  		}
   546  		num *= 10
   547  		num += int(r) - '0'
   548  		if num > max {
   549  			return 0, "", false
   550  		}
   551  	}
   552  	if num < min {
   553  		return 0, "", false
   554  	}
   555  	return num, "", true
   556  }
   557  
   558  // tzruleTime takes a year, a rule, and a timezone offset,
   559  // and returns the number of seconds since the start of the year
   560  // that the rule takes effect.
   561  func tzruleTime(year int, r rule, off int) int {
   562  	var s int
   563  	switch r.kind {
   564  	case ruleJulian:
   565  		s = (r.day - 1) * secondsPerDay
   566  		if isLeap(year) && r.day >= 60 {
   567  			s += secondsPerDay
   568  		}
   569  	case ruleDOY:
   570  		s = r.day * secondsPerDay
   571  	case ruleMonthWeekDay:
   572  		// Zeller's Congruence.
   573  		m1 := (r.mon+9)%12 + 1
   574  		yy0 := year
   575  		if r.mon <= 2 {
   576  			yy0--
   577  		}
   578  		yy1 := yy0 / 100
   579  		yy2 := yy0 % 100
   580  		dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
   581  		if dow < 0 {
   582  			dow += 7
   583  		}
   584  		// Now dow is the day-of-week of the first day of r.mon.
   585  		// Get the day-of-month of the first "dow" day.
   586  		d := r.day - dow
   587  		if d < 0 {
   588  			d += 7
   589  		}
   590  		for i := 1; i < r.week; i++ {
   591  			if d+7 >= daysIn(Month(r.mon), year) {
   592  				break
   593  			}
   594  			d += 7
   595  		}
   596  		d += int(daysBefore(Month(r.mon)))
   597  		if isLeap(year) && r.mon > 2 {
   598  			d++
   599  		}
   600  		s = d * secondsPerDay
   601  	}
   602  
   603  	return s + r.time - off
   604  }
   605  
   606  // lookupName returns information about the time zone with
   607  // the given name (such as "EST") at the given pseudo-Unix time
   608  // (what the given time of day would be in UTC).
   609  func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
   610  	l = l.get()
   611  
   612  	// First try for a zone with the right name that was actually
   613  	// in effect at the given time. (In Sydney, Australia, both standard
   614  	// and daylight-savings time are abbreviated "EST". Using the
   615  	// offset helps us pick the right one for the given time.
   616  	// It's not perfect: during the backward transition we might pick
   617  	// either one.)
   618  	for i := range l.zone {
   619  		zone := &l.zone[i]
   620  		if zone.name == name {
   621  			nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
   622  			if nam == zone.name {
   623  				return offset, true
   624  			}
   625  		}
   626  	}
   627  
   628  	// Otherwise fall back to an ordinary name match.
   629  	for i := range l.zone {
   630  		zone := &l.zone[i]
   631  		if zone.name == name {
   632  			return zone.offset, true
   633  		}
   634  	}
   635  
   636  	// Otherwise, give up.
   637  	return
   638  }
   639  
   640  // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
   641  // syntax too, but I don't feel like implementing it today.
   642  
   643  var errLocation = errors.New("time: invalid location name")
   644  
   645  var zoneinfo *string
   646  var zoneinfoOnce sync.Once
   647  
   648  // LoadLocation returns the Location with the given name.
   649  //
   650  // If the name is "" or "UTC", LoadLocation returns UTC.
   651  // If the name is "Local", LoadLocation returns Local.
   652  //
   653  // Otherwise, the name is taken to be a location name corresponding to a file
   654  // in the IANA Time Zone database, such as "America/New_York".
   655  //
   656  // LoadLocation looks for the IANA Time Zone database in the following
   657  // locations in order:
   658  //
   659  //   - the directory or uncompressed zip file named by the ZONEINFO environment variable
   660  //   - on a Unix system, the system standard installation location
   661  //   - $GOROOT/lib/time/zoneinfo.zip
   662  //   - the time/tzdata package, if it was imported
   663  func LoadLocation(name string) (*Location, error) {
   664  	if name == "" || name == "UTC" {
   665  		return UTC, nil
   666  	}
   667  	if name == "Local" {
   668  		return Local, nil
   669  	}
   670  	if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
   671  		// No valid IANA Time Zone name contains a single dot,
   672  		// much less dot dot. Likewise, none begin with a slash.
   673  		return nil, errLocation
   674  	}
   675  	zoneinfoOnce.Do(func() {
   676  		env, _ := syscall.Getenv("ZONEINFO")
   677  		zoneinfo = &env
   678  	})
   679  	var firstErr error
   680  	if *zoneinfo != "" {
   681  		if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
   682  			if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
   683  				return z, nil
   684  			}
   685  			firstErr = err
   686  		} else if err != syscall.ENOENT {
   687  			firstErr = err
   688  		}
   689  	}
   690  	if z, err := loadLocation(name, platformZoneSources); err == nil {
   691  		return z, nil
   692  	} else if firstErr == nil {
   693  		firstErr = err
   694  	}
   695  	return nil, firstErr
   696  }
   697  
   698  // containsDotDot reports whether s contains "..".
   699  func containsDotDot(s string) bool {
   700  	if len(s) < 2 {
   701  		return false
   702  	}
   703  	for i := 0; i < len(s)-1; i++ {
   704  		if s[i] == '.' && s[i+1] == '.' {
   705  			return true
   706  		}
   707  	}
   708  	return false
   709  }
   710  

View as plain text