2
3
5
package time
7
import (
8
"errors"
9
"sync"
10
"syscall"
11
)
13
15
16
17
18
19
20
21
22
23
type Location struct {
24
name string
25
zone []zone
26
tx []zoneTrans
28
29
30
31
32
33
extend string
35
36
37
38
39
40
41
42
43
44
cacheStart int64
45
cacheEnd int64
46
cacheZone *zone
47
}
49
50
type zone struct {
51
name string
52
offset int
53
isDST bool
54
}
56
57
type zoneTrans struct {
58
when int64
59
index uint8
60
isstd, isutc bool
61
}
63
64
65
const (
66
alpha = -1 << 63
67
omega = 1<<63 - 1
68
)
70
71
var UTC *Location = &utcLoc
73
74
75
76
var utcLoc = Location{name: "UTC"}
78
79
80
81
82
83
84
var Local *Location = &localLoc
86
87
88
var localLoc Location
89
var localOnce sync.Once
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
}
101
102
103
func (l *Location) String() string {
104
return l.get().name
105
}
107
var unnamedFixedZones []*Location
108
var unnamedFixedZonesOnce sync.Once
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
}
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
}
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()
152
if len(l.zone) == 0 {
153
name = "UTC"
154
offset = 0
155
start = alpha
156
end = omega
157
isDST = false
158
return
159
}
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
}
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
}
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
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
}
215
return
216
}
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
}
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
}
248
249
for zi := range l.zone {
250
if !l.zone[zi].isDST {
251
return zi
252
}
253
}
255
256
return 0
257
}
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
}
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
)
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
}
290
291
292
293
stdOffset = -stdOffset
295
if len(s) == 0 || s[0] == ',' {
296
297
return stdName, stdOffset, lastTxSec, omega, false, true
298
}
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
}
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:]
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
}
334
year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)
336
ysec := int64(yday*secondsPerDay) + sec%secondsPerDay
338
339
d := daysSinceEpoch(year)
340
abs := int64(d * secondsPerDay)
341
abs += absoluteToInternal + internalToUnix
343
startSec := int64(tzruleTime(year, startRule, stdOffset))
344
endSec := int64(tzruleTime(year, endRule, dstOffset))
345
dstIsDST, stdIsDST := true, false
346
347
348
349
if endSec < startSec {
350
startSec, endSec = endSec, startSec
351
stdName, dstName = dstName, stdName
352
stdOffset, dstOffset = dstOffset, stdOffset
353
stdIsDST, dstIsDST = dstIsDST, stdIsDST
354
}
356
357
358
359
360
if ysec < startSec {
361
return stdName, stdOffset, abs, startSec + abs, stdIsDST, true
362
} else if ysec >= endSec {
363
return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true
364
} else {
365
return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true
366
}
367
}
369
370
371
func tzsetName(s string) (string, string, bool) {
372
if len(s) == 0 {
373
return "", "", false
374
}
375
if s[0] != '<' {
376
for i, r := range s {
377
switch r {
378
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
379
if i < 3 {
380
return "", "", false
381
}
382
return s[:i], s[i:], true
383
}
384
}
385
if len(s) < 3 {
386
return "", "", false
387
}
388
return s, "", true
389
} else {
390
for i, r := range s {
391
if r == '>' {
392
return s[1:i], s[i+1:], true
393
}
394
}
395
return "", "", false
396
}
397
}
399
400
401
402
func tzsetOffset(s string) (offset int, rest string, ok bool) {
403
if len(s) == 0 {
404
return 0, "", false
405
}
406
neg := false
407
if s[0] == '+' {
408
s = s[1:]
409
} else if s[0] == '-' {
410
s = s[1:]
411
neg = true
412
}
414
415
416
var hours int
417
hours, s, ok = tzsetNum(s, 0, 24*7)
418
if !ok {
419
return 0, "", false
420
}
421
off := hours * secondsPerHour
422
if len(s) == 0 || s[0] != ':' {
423
if neg {
424
off = -off
425
}
426
return off, s, true
427
}
429
var mins int
430
mins, s, ok = tzsetNum(s[1:], 0, 59)
431
if !ok {
432
return 0, "", false
433
}
434
off += mins * secondsPerMinute
435
if len(s) == 0 || s[0] != ':' {
436
if neg {
437
off = -off
438
}
439
return off, s, true
440
}
442
var secs int
443
secs, s, ok = tzsetNum(s[1:], 0, 59)
444
if !ok {
445
return 0, "", false
446
}
447
off += secs
449
if neg {
450
off = -off
451
}
452
return off, s, true
453
}
455
456
type ruleKind int
458
const (
459
ruleJulian ruleKind = iota
460
ruleDOY
461
ruleMonthWeekDay
462
)
464
465
type rule struct {
466
kind ruleKind
467
day int
468
week int
469
mon int
470
time int
471
}
473
474
475
func tzsetRule(s string) (rule, string, bool) {
476
var r rule
477
if len(s) == 0 {
478
return rule{}, "", false
479
}
480
ok := false
481
if s[0] == 'J' {
482
var jday int
483
jday, s, ok = tzsetNum(s[1:], 1, 365)
484
if !ok {
485
return rule{}, "", false
486
}
487
r.kind = ruleJulian
488
r.day = jday
489
} else if s[0] == 'M' {
490
var mon int
491
mon, s, ok = tzsetNum(s[1:], 1, 12)
492
if !ok || len(s) == 0 || s[0] != '.' {
493
return rule{}, "", false
495
}
496
var week int
497
week, s, ok = tzsetNum(s[1:], 1, 5)
498
if !ok || len(s) == 0 || s[0] != '.' {
499
return rule{}, "", false
500
}
501
var day int
502
day, s, ok = tzsetNum(s[1:], 0, 6)
503
if !ok {
504
return rule{}, "", false
505
}
506
r.kind = ruleMonthWeekDay
507
r.day = day
508
r.week = week
509
r.mon = mon
510
} else {
511
var day int
512
day, s, ok = tzsetNum(s, 0, 365)
513
if !ok {
514
return rule{}, "", false
515
}
516
r.kind = ruleDOY
517
r.day = day
518
}
520
if len(s) == 0 || s[0] != '/' {
521
r.time = 2 * secondsPerHour
522
return r, s, true
523
}
525
offset, s, ok := tzsetOffset(s[1:])
526
if !ok {
527
return rule{}, "", false
528
}
529
r.time = offset
531
return r, s, true
532
}
534
535
536
537
func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
538
if len(s) == 0 {
539
return 0, "", false
540
}
541
num = 0
542
for i, r := range s {
543
if r < '0' || r > '9' {
544
if i == 0 || num < min {
545
return 0, "", false
546
}
547
return num, s[i:], true
548
}
549
num *= 10
550
num += int(r) - '0'
551
if num > max {
552
return 0, "", false
553
}
554
}
555
if num < min {
556
return 0, "", false
557
}
558
return num, "", true
559
}
561
562
563
564
func tzruleTime(year int, r rule, off int) int {
565
var s int
566
switch r.kind {
567
case ruleJulian:
568
s = (r.day - 1) * secondsPerDay
569
if isLeap(year) && r.day >= 60 {
570
s += secondsPerDay
571
}
572
case ruleDOY:
573
s = r.day * secondsPerDay
574
case ruleMonthWeekDay:
575
576
m1 := (r.mon+9)%12 + 1
577
yy0 := year
578
if r.mon <= 2 {
579
yy0--
580
}
581
yy1 := yy0 / 100
582
yy2 := yy0 % 100
583
dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
584
if dow < 0 {
585
dow += 7
586
}
587
588
589
d := r.day - dow
590
if d < 0 {
591
d += 7
592
}
593
for i := 1; i < r.week; i++ {
594
if d+7 >= daysIn(Month(r.mon), year) {
595
break
596
}
597
d += 7
598
}
599
d += int(daysBefore[r.mon-1])
600
if isLeap(year) && r.mon > 2 {
601
d++
602
}
603
s = d * secondsPerDay
604
}
606
return s + r.time - off
607
}
609
610
611
612
func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
613
l = l.get()
615
616
617
618
619
620
621
for i := range l.zone {
622
zone := &l.zone[i]
623
if zone.name == name {
624
nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
625
if nam == zone.name {
626
return offset, true
627
}
628
}
629
}
631
632
for i := range l.zone {
633
zone := &l.zone[i]
634
if zone.name == name {
635
return zone.offset, true
636
}
637
}
639
640
return
641
}
643
644
646
var errLocation = errors.New("time: invalid location name")
648
var zoneinfo *string
649
var zoneinfoOnce sync.Once
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
func LoadLocation(name string) (*Location, error) {
667
if name == "" || name == "UTC" {
668
return UTC, nil
669
}
670
if name == "Local" {
671
return Local, nil
672
}
673
if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
674
675
676
return nil, errLocation
677
}
678
zoneinfoOnce.Do(func() {
679
env, _ := syscall.Getenv("ZONEINFO")
680
zoneinfo = &env
681
})
682
var firstErr error
683
if *zoneinfo != "" {
684
if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
685
if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
686
return z, nil
687
}
688
firstErr = err
689
} else if err != syscall.ENOENT {
690
firstErr = err
691
}
692
}
693
if z, err := loadLocation(name, platformZoneSources); err == nil {
694
return z, nil
695
} else if firstErr == nil {
696
firstErr = err
697
}
698
return nil, firstErr
699
}
701
702
func containsDotDot(s string) bool {
703
if len(s) < 2 {
704
return false
705
}
706
for i := 0; i < len(s)-1; i++ {
707
if s[i] == '.' && s[i+1] == '.' {
708
return true
709
}
710
}
711
return false
712
}
View as plain text