Source file
src/time/zoneinfo.go
1
2
3
4
5 package time
6
7 import (
8 "errors"
9 "sync"
10 "syscall"
11 )
12
13
14
15
16
17
18
19 type Location struct {
20 name string
21 zone []zone
22 tx []zoneTrans
23
24
25
26
27
28
29 extend string
30
31
32
33
34
35
36
37
38
39
40 cacheStart int64
41 cacheEnd int64
42 cacheZone *zone
43 }
44
45
46 type zone struct {
47 name string
48 offset int
49 isDST bool
50 }
51
52
53 type zoneTrans struct {
54 when int64
55 index uint8
56 isstd, isutc bool
57 }
58
59
60
61 const (
62 alpha = -1 << 63
63 omega = 1<<63 - 1
64 )
65
66
67 var UTC *Location = &utcLoc
68
69
70
71
72 var utcLoc = Location{name: "UTC"}
73
74
75
76
77
78
79
80 var Local *Location = &localLoc
81
82
83
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
98
99 func (l *Location) String() string {
100 return l.get().name
101 }
102
103 var unnamedFixedZones []*Location
104 var unnamedFixedZonesOnce sync.Once
105
106
107
108 func FixedZone(name string, offset int) *Location {
109
110
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
139
140
141
142
143
144
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
181
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
201 isDST = zone.isDST
202
203
204
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 func (l *Location) lookupFirstZone() int {
230
231 if !l.firstZoneUsed() {
232 return 0
233 }
234
235
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
245 for zi := range l.zone {
246 if !l.zone[zi].isDST {
247 return zi
248 }
249 }
250
251
252 return 0
253 }
254
255
256
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
267
268
269
270
271
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
287
288
289 stdOffset = -stdOffset
290
291 if len(s) == 0 || s[0] == ',' {
292
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
303 }
304 }
305 if !ok {
306 return "", 0, 0, 0, false, false
307 }
308
309 if len(s) == 0 {
310
311 s = ",M3.2.0,M11.1.0"
312 }
313
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
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
343
344
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
353
354
355
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
366
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
396
397
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
411
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
452 type ruleKind int
453
454 const (
455 ruleJulian ruleKind = iota
456 ruleDOY
457 ruleMonthWeekDay
458 )
459
460
461 type rule struct {
462 kind ruleKind
463 day int
464 week int
465 mon int
466 time int
467 }
468
469
470
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
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
531
532
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
558
559
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
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
584
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
606
607
608 func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
609 l = l.get()
610
611
612
613
614
615
616
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
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
636 return
637 }
638
639
640
641
642 var errLocation = errors.New("time: invalid location name")
643
644 var zoneinfo *string
645 var zoneinfoOnce sync.Once
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
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
671
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
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