1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
from datetime import datetime
from typing import List
from typing import Optional
from pendulum.constants import DAYS_PER_YEAR
from pendulum.constants import SECS_PER_YEAR
from pendulum.helpers import is_leap
from pendulum.helpers import local_time
from pendulum.helpers import timestamp
from pendulum.helpers import week_day
from .posix_timezone import PosixTimezone
from .transition import Transition
from .transition_type import TransitionType
class Timezone:
def __init__(
self,
transitions, # type: List[Transition]
posix_rule=None, # type: Optional[PosixTimezone]
extended=True, # type: bool
):
self._posix_rule = posix_rule
self._transitions = transitions
if extended:
self._extends()
@property
def transitions(self): # type: () -> List[Transition]
return self._transitions
@property
def posix_rule(self):
return self._posix_rule
def _extends(self):
if not self._posix_rule:
return
posix = self._posix_rule
if not posix.dst_abbr:
# std only
# The future specification should match the last/default transition
ttype = self._transitions[-1].ttype
if not self._check_ttype(ttype, posix.std_offset, False, posix.std_abbr):
raise ValueError("Posix spec does not match last transition")
return
if len(self._transitions) < 2:
raise ValueError("Too few transitions for POSIX spec")
# Extend the transitions for an additional 400 years
# using the future specification
# The future specification should match the last two transitions,
# and those transitions should have different is_dst flags.
tr0 = self._transitions[-1]
tr1 = self._transitions[-2]
tt0 = tr0.ttype
tt1 = tr1.ttype
if tt0.is_dst():
dst = tt0
std = tt1
else:
dst = tt1
std = tt0
self._check_ttype(dst, posix.dst_offset, True, posix.dst_abbr)
self._check_ttype(std, posix.std_offset, False, posix.std_abbr)
# Add the transitions to tr1 and back to tr0 for each extra year.
last_year = local_time(tr0.local, 0, 0)[0]
leap_year = is_leap(last_year)
jan1 = datetime(last_year, 1, 1)
jan1_time = timestamp(jan1)
jan1_weekday = week_day(jan1.year, jan1.month, jan1.day) % 7
if local_time(tr1.local, 0, 0)[0] != last_year:
# Add a single extra transition to align to a calendar year.
if tt0.is_dst():
pt1 = posix.dst_end
else:
pt1 = posix.dst_start
tr1_offset = pt1.trans_offset(leap_year, jan1_weekday)
tr = Transition(jan1_time + tr1_offset - tt0.offset, tr1.ttype, tr0)
tr0 = tr
tr1 = tr0
tt0 = tr0.ttype
tt1 = tr1.ttype
if tt0.is_dst():
pt1 = posix.dst_end
pt0 = posix.dst_start
else:
pt1 = posix.dst_start
pt0 = posix.dst_end
tr = tr0
for year in range(last_year + 1, last_year + 401):
jan1_time += SECS_PER_YEAR[leap_year]
jan1_weekday = (jan1_weekday + DAYS_PER_YEAR[leap_year]) % 7
leap_year = not leap_year and is_leap(year)
tr1_offset = pt1.trans_offset(leap_year, jan1_weekday)
tr = Transition(jan1_time + tr1_offset - tt0.offset, tt1, tr)
self._transitions.append(tr)
tr0_offset = pt0.trans_offset(leap_year, jan1_weekday)
tr = Transition(jan1_time + tr0_offset - tt1.offset, tt0, tr)
self._transitions.append(tr)
def _check_ttype(
self,
ttype, # type: TransitionType
offset, # type: int
is_dst, # type: bool
abbr, # type: str
): # type: (...) -> bool
return (
ttype.offset == offset
and ttype.is_dst() == is_dst
and ttype.abbreviation == abbr
)
|