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
20
21
22
23 type Location struct {
24 name string
25 zone []zone
26 tx []zoneTrans
27
28
29
30
31
32
33 extend string
34
35
36
37
38
39
40
41
42
43
44 cacheStart int64
45 cacheEnd int64
46 cacheZone *zone
47 }
48
49
50 type zone struct {
51 name string
52 offset int
53 isDST bool
54 }
55
56
57 type zoneTrans struct {
58 when int64
59 index uint8
60 isstd, isutc bool
61 }
62
63
64
65 const (
66 alpha = -1 << 63
67 omega = 1<<63 - 1
68 )
69
70
71 var UTC *Location = &utcLoc
72
73
74
75
76 var utcLoc = Location{name: "UTC"}
77
78
79
80
81
82
83
84 var Local *Location = &localLoc
85
86
87
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
102
103 func (l *Location) String() string {
104 return l.get().name
105 }
106
107 var unnamedFixedZones []*Location
108 var unnamedFixedZonesOnce sync.Once
109
110
111
112 func FixedZone(name string, offset int) *Location {
113
114
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
143
144
145
146
147
148
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
185
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
205 isDST = zone.isDST
206
207
208
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233 func (l *Location) lookupFirstZone() int {
234
235 if !l.firstZoneUsed() {
236 return 0
237 }
238
239
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
249 for zi := range l.zone {
250 if !l.zone[zi].isDST {
251 return zi
252 }
253 }
254
255
256 return 0
257 }
258
259
260
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
271
272
273
274
275
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
291
292
293 stdOffset = -stdOffset
294
295 if len(s) == 0 || s[0] == ',' {
296
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
307 }
308 }
309 if !ok {
310 return "", 0, 0, 0, false, false
311 }
312
313 if len(s) == 0 {
314
315 s = ",M3.2.0,M11.1.0"
316 }
317
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
335
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
344
345
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
354
355
356
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
367
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
397
398
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
412
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
453 type ruleKind int
454
455 const (
456 ruleJulian ruleKind = iota
457 ruleDOY
458 ruleMonthWeekDay
459 )
460
461
462 type rule struct {
463 kind ruleKind
464 day int
465 week int
466 mon int
467 time int
468 }
469
470
471
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
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
532
533
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
559
560
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
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
585
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
607
608
609 func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
610 l = l.get()
611
612
613
614
615
616
617
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
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
637 return
638 }
639
640
641
642
643 var errLocation = errors.New("time: invalid location name")
644
645 var zoneinfo *string
646 var zoneinfoOnce sync.Once
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
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
672
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
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