From 1199780155f666b6806d563a29d093a251664009 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 30 Jan 2021 09:13:47 +0100 Subject: Adding upstream version 2.1.2. Signed-off-by: Daniel Baumann --- pendulum/tz/zoneinfo/timezone.py | 128 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 pendulum/tz/zoneinfo/timezone.py (limited to 'pendulum/tz/zoneinfo/timezone.py') 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 + ) -- cgit v1.2.3