Skip to content

Commit 06c9756

Browse files
[release-branch.go1.15] time: use offset and isDST when caching zone from extend string
If the current time is computed from extend string and the zone file contains multiple zones with the same name, the lookup by name might find incorrect zone. This happens for example with the slim Europe/Dublin time zone file in the embedded zip. This zone file has last transition in 1996 and rest is covered by extend string. tzset returns IST as the zone name to use, but there are two records with IST name. Lookup by name finds the wrong one. We need to check offset and isDST too. In case we can't find an existing zone, we allocate a new zone so that we use correct offset and isDST. I have renamed zone variable to zones as it shadowed the zone type that we need to allocate the cached zone. Backport note: this change also incorporates portions of CL 264077. For #45370 Fixes #45384 Change-Id: I43d416d009e20878261156c821a5784e2407ed1f Reviewed-on: https://go-review.googlesource.com/c/go/+/307212 Trust: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]>
1 parent 8c163e8 commit 06c9756

File tree

3 files changed

+68
-40
lines changed

3 files changed

+68
-40
lines changed

src/time/zoneinfo.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func (l *Location) lookup(sec int64) (name string, offset int, start, end int64)
178178
// If we're at the end of the known zone transitions,
179179
// try the extend string.
180180
if lo == len(tx)-1 && l.extend != "" {
181-
if ename, eoffset, estart, eend, ok := tzset(l.extend, end, sec); ok {
181+
if ename, eoffset, estart, eend, _, ok := tzset(l.extend, end, sec); ok {
182182
return ename, eoffset, estart, eend
183183
}
184184
}
@@ -244,7 +244,7 @@ func (l *Location) firstZoneUsed() bool {
244244
// We call this a tzset string since in C the function tzset reads TZ.
245245
// The return values are as for lookup, plus ok which reports whether the
246246
// parse succeeded.
247-
func tzset(s string, initEnd, sec int64) (name string, offset int, start, end int64, ok bool) {
247+
func tzset(s string, initEnd, sec int64) (name string, offset int, start, end int64, isDST, ok bool) {
248248
var (
249249
stdName, dstName string
250250
stdOffset, dstOffset int
@@ -255,7 +255,7 @@ func tzset(s string, initEnd, sec int64) (name string, offset int, start, end in
255255
stdOffset, s, ok = tzsetOffset(s)
256256
}
257257
if !ok {
258-
return "", 0, 0, 0, false
258+
return "", 0, 0, 0, false, false
259259
}
260260

261261
// The numbers in the tzset string are added to local time to get UTC,
@@ -265,7 +265,7 @@ func tzset(s string, initEnd, sec int64) (name string, offset int, start, end in
265265

266266
if len(s) == 0 || s[0] == ',' {
267267
// No daylight savings time.
268-
return stdName, stdOffset, initEnd, omega, true
268+
return stdName, stdOffset, initEnd, omega, false, true
269269
}
270270

271271
dstName, s, ok = tzsetName(s)
@@ -278,7 +278,7 @@ func tzset(s string, initEnd, sec int64) (name string, offset int, start, end in
278278
}
279279
}
280280
if !ok {
281-
return "", 0, 0, 0, false
281+
return "", 0, 0, 0, false, false
282282
}
283283

284284
if len(s) == 0 {
@@ -287,19 +287,19 @@ func tzset(s string, initEnd, sec int64) (name string, offset int, start, end in
287287
}
288288
// The TZ definition does not mention ';' here but tzcode accepts it.
289289
if s[0] != ',' && s[0] != ';' {
290-
return "", 0, 0, 0, false
290+
return "", 0, 0, 0, false, false
291291
}
292292
s = s[1:]
293293

294294
var startRule, endRule rule
295295
startRule, s, ok = tzsetRule(s)
296296
if !ok || len(s) == 0 || s[0] != ',' {
297-
return "", 0, 0, 0, false
297+
return "", 0, 0, 0, false, false
298298
}
299299
s = s[1:]
300300
endRule, s, ok = tzsetRule(s)
301301
if !ok || len(s) > 0 {
302-
return "", 0, 0, 0, false
302+
return "", 0, 0, 0, false, false
303303
}
304304

305305
year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)
@@ -313,22 +313,27 @@ func tzset(s string, initEnd, sec int64) (name string, offset int, start, end in
313313

314314
startSec := int64(tzruleTime(year, startRule, stdOffset))
315315
endSec := int64(tzruleTime(year, endRule, dstOffset))
316+
dstIsDST, stdIsDST := true, false
317+
// Note: this is a flipping of "DST" and "STD" while retaining the labels
318+
// This happens in southern hemispheres. The labelling here thus is a little
319+
// inconsistent with the goal.
316320
if endSec < startSec {
317321
startSec, endSec = endSec, startSec
318322
stdName, dstName = dstName, stdName
319323
stdOffset, dstOffset = dstOffset, stdOffset
324+
stdIsDST, dstIsDST = dstIsDST, stdIsDST
320325
}
321326

322327
// The start and end values that we return are accurate
323328
// close to a daylight savings transition, but are otherwise
324329
// just the start and end of the year. That suffices for
325330
// the only caller that cares, which is Date.
326331
if ysec < startSec {
327-
return stdName, stdOffset, abs, startSec + abs, true
332+
return stdName, stdOffset, abs, startSec + abs, stdIsDST, true
328333
} else if ysec >= endSec {
329-
return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, true
334+
return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true
330335
} else {
331-
return dstName, dstOffset, startSec + abs, endSec + abs, true
336+
return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true
332337
}
333338
}
334339

src/time/zoneinfo_read.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
247247
// This also avoids a panic later when we add and then use a fake transition (golang.org/issue/29437).
248248
return nil, badData
249249
}
250-
zone := make([]zone, nzone)
251-
for i := range zone {
250+
zones := make([]zone, nzone)
251+
for i := range zones {
252252
var ok bool
253253
var n uint32
254254
if n, ok = zonedata.big4(); !ok {
@@ -257,22 +257,22 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
257257
if uint32(int(n)) != n {
258258
return nil, badData
259259
}
260-
zone[i].offset = int(int32(n))
260+
zones[i].offset = int(int32(n))
261261
var b byte
262262
if b, ok = zonedata.byte(); !ok {
263263
return nil, badData
264264
}
265-
zone[i].isDST = b != 0
265+
zones[i].isDST = b != 0
266266
if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
267267
return nil, badData
268268
}
269-
zone[i].name = byteString(abbrev[b:])
269+
zones[i].name = byteString(abbrev[b:])
270270
if runtime.GOOS == "aix" && len(name) > 8 && (name[:8] == "Etc/GMT+" || name[:8] == "Etc/GMT-") {
271271
// There is a bug with AIX 7.2 TL 0 with files in Etc,
272272
// GMT+1 will return GMT-1 instead of GMT+1 or -01.
273273
if name != "Etc/GMT+0" {
274274
// GMT+0 is OK
275-
zone[i].name = name[4:]
275+
zones[i].name = name[4:]
276276
}
277277
}
278278
}
@@ -295,7 +295,7 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
295295
}
296296
}
297297
tx[i].when = n
298-
if int(txzones[i]) >= len(zone) {
298+
if int(txzones[i]) >= len(zones) {
299299
return nil, badData
300300
}
301301
tx[i].index = txzones[i]
@@ -314,7 +314,7 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
314314
}
315315

316316
// Committed to succeed.
317-
l := &Location{zone: zone, tx: tx, name: name, extend: extend}
317+
l := &Location{zone: zones, tx: tx, name: name, extend: extend}
318318

319319
// Fill in the cache with information about right now,
320320
// since that will be the most common lookup.
@@ -323,33 +323,43 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
323323
if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
324324
l.cacheStart = tx[i].when
325325
l.cacheEnd = omega
326-
zoneIdx := tx[i].index
326+
l.cacheZone = &l.zone[tx[i].index]
327327
if i+1 < len(tx) {
328328
l.cacheEnd = tx[i+1].when
329329
} else if l.extend != "" {
330330
// If we're at the end of the known zone transitions,
331331
// try the extend string.
332-
if name, _, estart, eend, ok := tzset(l.extend, l.cacheEnd, sec); ok {
332+
if name, offset, estart, eend, isDST, ok := tzset(l.extend, l.cacheEnd, sec); ok {
333333
l.cacheStart = estart
334334
l.cacheEnd = eend
335-
// Find the zone that is returned by tzset,
336-
// the last transition is not always the correct zone.
337-
for i, z := range l.zone {
338-
if z.name == name {
339-
zoneIdx = uint8(i)
340-
break
335+
// Find the zone that is returned by tzset to avoid allocation if possible.
336+
if zoneIdx := findZone(l.zone, name, offset, isDST); zoneIdx != -1 {
337+
l.cacheZone = &l.zone[zoneIdx]
338+
} else {
339+
l.cacheZone = &zone{
340+
name: name,
341+
offset: offset,
342+
isDST: isDST,
341343
}
342344
}
343345
}
344346
}
345-
l.cacheZone = &l.zone[zoneIdx]
346347
break
347348
}
348349
}
349350

350351
return l, nil
351352
}
352353

354+
func findZone(zones []zone, name string, offset int, isDST bool) int {
355+
for i, z := range zones {
356+
if z.name == name && z.offset == offset && z.isDST == isDST {
357+
return i
358+
}
359+
}
360+
return -1
361+
}
362+
353363
// loadTzinfoFromDirOrZip returns the contents of the file with the given name
354364
// in dir. dir can either be an uncompressed zip file, or a directory.
355365
func loadTzinfoFromDirOrZip(dir, name string) ([]byte, error) {

0 commit comments

Comments
 (0)