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
104
105 func FixedZone(name string, offset int) *Location {
106 l := &Location{
107 name: name,
108 zone: []zone{{name, offset, false}},
109 tx: []zoneTrans{{alpha, 0, false, false}},
110 cacheStart: alpha,
111 cacheEnd: omega,
112 }
113 l.cacheZone = &l.zone[0]
114 return l
115 }
116
117
118
119
120
121
122
123
124 func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) {
125 l = l.get()
126
127 if len(l.zone) == 0 {
128 name = "UTC"
129 offset = 0
130 start = alpha
131 end = omega
132 isDST = false
133 return
134 }
135
136 if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
137 name = zone.name
138 offset = zone.offset
139 start = l.cacheStart
140 end = l.cacheEnd
141 isDST = zone.isDST
142 return
143 }
144
145 if len(l.tx) == 0 || sec < l.tx[0].when {
146 zone := &l.zone[l.lookupFirstZone()]
147 name = zone.name
148 offset = zone.offset
149 start = alpha
150 if len(l.tx) > 0 {
151 end = l.tx[0].when
152 } else {
153 end = omega
154 }
155 isDST = zone.isDST
156 return
157 }
158
159
160
161 tx := l.tx
162 end = omega
163 lo := 0
164 hi := len(tx)
165 for hi-lo > 1 {
166 m := lo + (hi-lo)/2
167 lim := tx[m].when
168 if sec < lim {
169 end = lim
170 hi = m
171 } else {
172 lo = m
173 }
174 }
175 zone := &l.zone[tx[lo].index]
176 name = zone.name
177 offset = zone.offset
178 start = tx[lo].when
179
180 isDST = zone.isDST
181
182
183
184 if lo == len(tx)-1 && l.extend != "" {
185 if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, end, sec); ok {
186 return ename, eoffset, estart, eend, eisDST
187 }
188 }
189
190 return
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 func (l *Location) lookupFirstZone() int {
209
210 if !l.firstZoneUsed() {
211 return 0
212 }
213
214
215 if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
216 for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
217 if !l.zone[zi].isDST {
218 return zi
219 }
220 }
221 }
222
223
224 for zi := range l.zone {
225 if !l.zone[zi].isDST {
226 return zi
227 }
228 }
229
230
231 return 0
232 }
233
234
235
236 func (l *Location) firstZoneUsed() bool {
237 for _, tx := range l.tx {
238 if tx.index == 0 {
239 return true
240 }
241 }
242 return false
243 }
244
245
246
247
248
249
250
251 func tzset(s string, initEnd, sec int64) (name string, offset int, start, end int64, isDST, ok bool) {
252 var (
253 stdName, dstName string
254 stdOffset, dstOffset int
255 )
256
257 stdName, s, ok = tzsetName(s)
258 if ok {
259 stdOffset, s, ok = tzsetOffset(s)
260 }
261 if !ok {
262 return "", 0, 0, 0, false, false
263 }
264
265
266
267
268 stdOffset = -stdOffset
269
270 if len(s) == 0 || s[0] == ',' {
271
272 return stdName, stdOffset, initEnd, omega, false, true
273 }
274
275 dstName, s, ok = tzsetName(s)
276 if ok {
277 if len(s) == 0 || s[0] == ',' {
278 dstOffset = stdOffset + secondsPerHour
279 } else {
280 dstOffset, s, ok = tzsetOffset(s)
281 dstOffset = -dstOffset
282 }
283 }
284 if !ok {
285 return "", 0, 0, 0, false, false
286 }
287
288 if len(s) == 0 {
289
290 s = ",M3.2.0,M11.1.0"
291 }
292
293 if s[0] != ',' && s[0] != ';' {
294 return "", 0, 0, 0, false, false
295 }
296 s = s[1:]
297
298 var startRule, endRule rule
299 startRule, s, ok = tzsetRule(s)
300 if !ok || len(s) == 0 || s[0] != ',' {
301 return "", 0, 0, 0, false, false
302 }
303 s = s[1:]
304 endRule, s, ok = tzsetRule(s)
305 if !ok || len(s) > 0 {
306 return "", 0, 0, 0, false, false
307 }
308
309 year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)
310
311 ysec := int64(yday*secondsPerDay) + sec%secondsPerDay
312
313
314 d := daysSinceEpoch(year)
315 abs := int64(d * secondsPerDay)
316 abs += absoluteToInternal + internalToUnix
317
318 startSec := int64(tzruleTime(year, startRule, stdOffset))
319 endSec := int64(tzruleTime(year, endRule, dstOffset))
320 dstIsDST, stdIsDST := true, false
321
322
323
324 if endSec < startSec {
325 startSec, endSec = endSec, startSec
326 stdName, dstName = dstName, stdName
327 stdOffset, dstOffset = dstOffset, stdOffset
328 stdIsDST, dstIsDST = dstIsDST, stdIsDST
329 }
330
331
332
333
334
335 if ysec < startSec {
336 return stdName, stdOffset, abs, startSec + abs, stdIsDST, true
337 } else if ysec >= endSec {
338 return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true
339 } else {
340 return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true
341 }
342 }
343
344
345
346 func tzsetName(s string) (string, string, bool) {
347 if len(s) == 0 {
348 return "", "", false
349 }
350 if s[0] != '<' {
351 for i, r := range s {
352 switch r {
353 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
354 if i < 3 {
355 return "", "", false
356 }
357 return s[:i], s[i:], true
358 }
359 }
360 if len(s) < 3 {
361 return "", "", false
362 }
363 return s, "", true
364 } else {
365 for i, r := range s {
366 if r == '>' {
367 return s[1:i], s[i+1:], true
368 }
369 }
370 return "", "", false
371 }
372 }
373
374
375
376
377 func tzsetOffset(s string) (offset int, rest string, ok bool) {
378 if len(s) == 0 {
379 return 0, "", false
380 }
381 neg := false
382 if s[0] == '+' {
383 s = s[1:]
384 } else if s[0] == '-' {
385 s = s[1:]
386 neg = true
387 }
388
389
390
391 var hours int
392 hours, s, ok = tzsetNum(s, 0, 24*7)
393 if !ok {
394 return 0, "", false
395 }
396 off := hours * secondsPerHour
397 if len(s) == 0 || s[0] != ':' {
398 if neg {
399 off = -off
400 }
401 return off, s, true
402 }
403
404 var mins int
405 mins, s, ok = tzsetNum(s[1:], 0, 59)
406 if !ok {
407 return 0, "", false
408 }
409 off += mins * secondsPerMinute
410 if len(s) == 0 || s[0] != ':' {
411 if neg {
412 off = -off
413 }
414 return off, s, true
415 }
416
417 var secs int
418 secs, s, ok = tzsetNum(s[1:], 0, 59)
419 if !ok {
420 return 0, "", false
421 }
422 off += secs
423
424 if neg {
425 off = -off
426 }
427 return off, s, true
428 }
429
430
431 type ruleKind int
432
433 const (
434 ruleJulian ruleKind = iota
435 ruleDOY
436 ruleMonthWeekDay
437 )
438
439
440 type rule struct {
441 kind ruleKind
442 day int
443 week int
444 mon int
445 time int
446 }
447
448
449
450 func tzsetRule(s string) (rule, string, bool) {
451 var r rule
452 if len(s) == 0 {
453 return rule{}, "", false
454 }
455 ok := false
456 if s[0] == 'J' {
457 var jday int
458 jday, s, ok = tzsetNum(s[1:], 1, 365)
459 if !ok {
460 return rule{}, "", false
461 }
462 r.kind = ruleJulian
463 r.day = jday
464 } else if s[0] == 'M' {
465 var mon int
466 mon, s, ok = tzsetNum(s[1:], 1, 12)
467 if !ok || len(s) == 0 || s[0] != '.' {
468 return rule{}, "", false
469
470 }
471 var week int
472 week, s, ok = tzsetNum(s[1:], 1, 5)
473 if !ok || len(s) == 0 || s[0] != '.' {
474 return rule{}, "", false
475 }
476 var day int
477 day, s, ok = tzsetNum(s[1:], 0, 6)
478 if !ok {
479 return rule{}, "", false
480 }
481 r.kind = ruleMonthWeekDay
482 r.day = day
483 r.week = week
484 r.mon = mon
485 } else {
486 var day int
487 day, s, ok = tzsetNum(s, 0, 365)
488 if !ok {
489 return rule{}, "", false
490 }
491 r.kind = ruleDOY
492 r.day = day
493 }
494
495 if len(s) == 0 || s[0] != '/' {
496 r.time = 2 * secondsPerHour
497 return r, s, true
498 }
499
500 offset, s, ok := tzsetOffset(s[1:])
501 if !ok {
502 return rule{}, "", false
503 }
504 r.time = offset
505
506 return r, s, true
507 }
508
509
510
511
512 func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
513 if len(s) == 0 {
514 return 0, "", false
515 }
516 num = 0
517 for i, r := range s {
518 if r < '0' || r > '9' {
519 if i == 0 || num < min {
520 return 0, "", false
521 }
522 return num, s[i:], true
523 }
524 num *= 10
525 num += int(r) - '0'
526 if num > max {
527 return 0, "", false
528 }
529 }
530 if num < min {
531 return 0, "", false
532 }
533 return num, "", true
534 }
535
536
537
538
539 func tzruleTime(year int, r rule, off int) int {
540 var s int
541 switch r.kind {
542 case ruleJulian:
543 s = (r.day - 1) * secondsPerDay
544 if isLeap(year) && r.day >= 60 {
545 s += secondsPerDay
546 }
547 case ruleDOY:
548 s = r.day * secondsPerDay
549 case ruleMonthWeekDay:
550
551 m1 := (r.mon+9)%12 + 1
552 yy0 := year
553 if r.mon <= 2 {
554 yy0--
555 }
556 yy1 := yy0 / 100
557 yy2 := yy0 % 100
558 dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
559 if dow < 0 {
560 dow += 7
561 }
562
563
564 d := r.day - dow
565 if d < 0 {
566 d += 7
567 }
568 for i := 1; i < r.week; i++ {
569 if d+7 >= daysIn(Month(r.mon), year) {
570 break
571 }
572 d += 7
573 }
574 d += int(daysBefore[r.mon-1])
575 if isLeap(year) && r.mon > 2 {
576 d++
577 }
578 s = d * secondsPerDay
579 }
580
581 return s + r.time - off
582 }
583
584
585
586
587 func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
588 l = l.get()
589
590
591
592
593
594
595
596 for i := range l.zone {
597 zone := &l.zone[i]
598 if zone.name == name {
599 nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
600 if nam == zone.name {
601 return offset, true
602 }
603 }
604 }
605
606
607 for i := range l.zone {
608 zone := &l.zone[i]
609 if zone.name == name {
610 return zone.offset, true
611 }
612 }
613
614
615 return
616 }
617
618
619
620
621 var errLocation = errors.New("time: invalid location name")
622
623 var zoneinfo *string
624 var zoneinfoOnce sync.Once
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641 func LoadLocation(name string) (*Location, error) {
642 if name == "" || name == "UTC" {
643 return UTC, nil
644 }
645 if name == "Local" {
646 return Local, nil
647 }
648 if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
649
650
651 return nil, errLocation
652 }
653 zoneinfoOnce.Do(func() {
654 env, _ := syscall.Getenv("ZONEINFO")
655 zoneinfo = &env
656 })
657 var firstErr error
658 if *zoneinfo != "" {
659 if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
660 if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
661 return z, nil
662 }
663 firstErr = err
664 } else if err != syscall.ENOENT {
665 firstErr = err
666 }
667 }
668 if z, err := loadLocation(name, zoneSources); err == nil {
669 return z, nil
670 } else if firstErr == nil {
671 firstErr = err
672 }
673 return nil, firstErr
674 }
675
676
677 func containsDotDot(s string) bool {
678 if len(s) < 2 {
679 return false
680 }
681 for i := 0; i < len(s)-1; i++ {
682 if s[i] == '.' && s[i+1] == '.' {
683 return true
684 }
685 }
686 return false
687 }
688
View as plain text