summaryrefslogtreecommitdiffstats
path: root/pendulum/tz/zoneinfo/timezone.py
diff options
context:
space:
mode:
Diffstat (limited to 'pendulum/tz/zoneinfo/timezone.py')
-rw-r--r--pendulum/tz/zoneinfo/timezone.py128
1 files changed, 128 insertions, 0 deletions
diff --git a/pendulum/tz/zoneinfo/timezone.py b/pendulum/tz/zoneinfo/timezone.py
new file mode 100644
index 0000000..2147774
--- /dev/null
+++ b/pendulum/tz/zoneinfo/timezone.py
@@ -0,0 +1,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
+ )