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