Beispiel #1
0
def test_datetimes_can_exclude_imaginary():
    # The day of a spring-forward transition; 2am is imaginary
    australia = {
        "min_value": dt.datetime(2020, 10, 4),
        "max_value": dt.datetime(2020, 10, 5),
        "timezones": just(pytz.timezone("Australia/Sydney")),
    }
    # Ireland uses  *negative* offset DST, which means that our sloppy interpretation
    # of "is_dst=not fold" bypasses the filter for imaginary times.  This is basically
    # unfixable without redesigning pytz per PEP-495, and it's much more likely to be
    # replaced by dateutil or PEP-615 zoneinfo in the standard library instead.
    # (we use both so an optimistic `is_dst=bool(fold)` also fails the test)
    ireland = {
        "min_value": dt.datetime(2019, 3, 31),
        "max_value": dt.datetime(2019, 4, 1),
        "timezones": just(pytz.timezone("Europe/Dublin")),
    }
    # Sanity check: fail unless those days contain an imaginary hour to filter out
    find_any(
        datetimes(**australia, allow_imaginary=True),
        lambda x: not datetime_exists(x),
    )
    find_any(
        datetimes(**ireland, allow_imaginary=True),
        lambda x: not datetime_exists(x),
    )
    # Assert that with allow_imaginary=False we only generate existing datetimes.
    assert_all_examples(
        datetimes(**australia, allow_imaginary=False)
        | datetimes(**ireland, allow_imaginary=False),
        datetime_exists,
    )
Beispiel #2
0
    def update_computed_fields_no_save(self):
        affects_fields = ['next_run', 'dtstart', 'dtend']
        starting_values = {}
        for field_name in affects_fields:
            starting_values[field_name] = getattr(self, field_name)

        future_rs = Schedule.rrulestr(self.rrule)

        if self.enabled:
            next_run_actual = future_rs.after(now())
            if next_run_actual is not None:
                if not datetime_exists(next_run_actual):
                    # skip imaginary dates, like 2:30 on DST boundaries
                    next_run_actual = future_rs.after(next_run_actual)
                next_run_actual = next_run_actual.astimezone(pytz.utc)
        else:
            next_run_actual = None

        self.next_run = next_run_actual
        try:
            self.dtstart = future_rs[0].astimezone(pytz.utc)
        except IndexError:
            self.dtstart = None
        self.dtend = None
        if 'until' in self.rrule.lower() or 'count' in self.rrule.lower():
            try:
                self.dtend = future_rs[-1].astimezone(pytz.utc)
            except IndexError:
                self.dtend = None

        changed = any(getattr(self, field_name) != starting_values[field_name] for field_name in affects_fields)
        return changed
    def update_computed_fields(self):
        future_rs = Schedule.rrulestr(self.rrule)
        next_run_actual = future_rs.after(now())

        if next_run_actual is not None:
            if not datetime_exists(next_run_actual):
                # skip imaginary dates, like 2:30 on DST boundaries
                next_run_actual = future_rs.after(next_run_actual)
            next_run_actual = next_run_actual.astimezone(pytz.utc)

        self.next_run = next_run_actual
        try:
            self.dtstart = future_rs[0].astimezone(pytz.utc)
        except IndexError:
            self.dtstart = None
        self.dtend = None
        if 'until' in self.rrule.lower() or 'count' in self.rrule.lower():
            try:
                self.dtend = future_rs[-1].astimezone(pytz.utc)
            except IndexError:
                self.dtend = None
        emit_channel_notification('schedules-changed',
                                  dict(id=self.id, group_name='schedules'))
        with ignore_inventory_computed_fields():
            self.unified_job_template.update_computed_fields()
def test_datetimes_can_exclude_imaginary():
    find_any(
        datetimes(**DAY_WITH_IMAGINARY_HOUR_KWARGS, allow_imaginary=True),
        lambda x: not tz.datetime_exists(x),
    )
    assert_all_examples(
        datetimes(**DAY_WITH_IMAGINARY_HOUR_KWARGS, allow_imaginary=False),
        tz.datetime_exists,
    )
Beispiel #5
0
    def shift(self, **kwargs):
        """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
        according to inputs.

        Use pluralized property names to relatively shift their current value:

        >>> import arrow
        >>> arw = arrow.utcnow()
        >>> arw
        <Arrow [2013-05-11T22:27:34.787885+00:00]>
        >>> arw.shift(years=1, months=-1)
        <Arrow [2014-04-11T22:27:34.787885+00:00]>

        Day-of-the-week relative shifting can use either Python's weekday numbers
        (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's
        day instances (MO, TU .. SU).  When using weekday numbers, the returned
        date will always be greater than or equal to the starting date.

        Using the above code (which is a Saturday) and asking it to shift to Saturday:

        >>> arw.shift(weekday=5)
        <Arrow [2013-05-11T22:27:34.787885+00:00]>

        While asking for a Monday:

        >>> arw.shift(weekday=0)
        <Arrow [2013-05-13T22:27:34.787885+00:00]>

        """

        relative_kwargs = {}
        additional_attrs = ["weeks", "quarters", "weekday"]

        for key, value in kwargs.items():

            if key in self._ATTRS_PLURAL or key in additional_attrs:
                relative_kwargs[key] = value
            else:
                raise AttributeError(
                    "Invalid shift time frame. Please select one of the following: {}.".format(
                        ", ".join(self._ATTRS_PLURAL + additional_attrs)
                    )
                )

        # core datetime does not support quarters, translate to months.
        relative_kwargs.setdefault("months", 0)
        relative_kwargs["months"] += (
            relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER
        )

        current = self._datetime + relativedelta(**relative_kwargs)

        if not dateutil_tz.datetime_exists(current):
            current = dateutil_tz.resolve_imaginary(current)

        return self.fromdatetime(current)
Beispiel #6
0
 def get_problematic_time_value_error():
     if datetime_ambiguous(dt, tz):
         return ProblematicTimeValueError(
             '{!r} is ambiguous in time zone {!r} (because of '
             'daylight-saving-time intricacies...)'.format(dt, tz))
     else:
         assert not datetime_exists(dt, tz)
         return ProblematicTimeValueError(
             '{!r} does not exist in time zone {!r} (because of '
             'daylight-saving-time intricacies...)'.format(dt, tz))
Beispiel #7
0
    def update_computed_fields(self):
        future_rs = tzcron.Schedule(self.schedule.crontab, pytz.utc)
        next_run_actual = next(future_rs)

        if next_run_actual is not None:
            if not datetime_exists(next_run_actual):
                # skip imaginary dates, like 2:30 on DST boundaries
                next_run_actual = next(future_rs)
            next_run_actual = next_run_actual.astimezone(pytz.utc)

        self.next_run = next_run_actual
        emit_channel_notification('schedules-changed', dict(id=self.id, group_name='schedules'))
Beispiel #8
0
 def find_existing_unambiguous_dt(search_direction):
     assert search_direction in ('earlier', 'later')
     MAX_ATTEMPTS = 32
     delta = TIME_DELTA_ONE_HOUR
     if search_direction == 'earlier':
         delta = (-delta)
     for i in range(MAX_ATTEMPTS):
         tried_dt = dt + i*delta
         if datetime_exists(tried_dt, tz) and not datetime_ambiguous(tried_dt, tz):
             return tried_dt
     raise RuntimeError('failed to find existing unambiguous datetime '
                        'that would be close enough to dt={!r} (maybe '
                        'class of tz={!r} is defective?)'.format(dt, tz))
Beispiel #9
0
def localize(dt, tzi, is_dst=False):
    """
    Mimicks `pytz`'s `localize` function using the `fold` attribute.
    """
    if dt.tzinfo is not None:
        raise ValueError('localize can only be used with naive datetimes')

    if is_dst is None:
        # If is_dst is None, we want to raise an error for uncertain situations
        dt_out = dt.replace(tzinfo=tzi)
        if tz.datetime_ambiguous(dt_out):
            raise AmbiguousTimeError(f"Ambiguous time {dt} in zone {tzi}")
        elif not tz.datetime_exists(dt_out):
            raise NonExistentTimeError(
                f"Time {dt} does not exist in zone {tzi}")
    else:
        dt_out = dt.replace(fold=(not is_dst), tzinfo=tzi)

    return dt_out
Beispiel #10
0
    def shift(self, **kwargs):
        relatives = {}
        addattrs = ['weeks', 'quarters', 'weekday']
        for k, v in kwargs.items():
            if k in self._ATTR_MAP or k in addattrs:
                relatives[k] = v
            elif k in self._ATTRS + ['week', 'quarter']:
                relatives[f'{k}s'] = v
            else:
                supported = ', '.join(self._ATTRS_PLURAL + addattrs)
                raise ValueError(
                    f'timeframe not supported: {k!r} not in {supported}')

        relatives.setdefault('months', 0)
        relatives['months'] += relatives.pop('quarters', 0) * 3

        current = self._dt + relativedelta(**relatives)
        if not dtz.datetime_exists(current):
            current = dtz.resolve_imaginary(current)

        return self.fromdatetime(current, tzinfo=self.tzinfo)
Beispiel #11
0
 def get_reaction_if_time_is_problematic():
     if datetime_ambiguous(dt, tz):
         return on_ambiguous_time
     if not datetime_exists(dt, tz):
         return on_non_existent_time
     return None
Beispiel #12
0
def assume_no_dst_inconsistency_bug(dt, key, is_dst=False):  # prama: nocover
    # pytz and zoneinfo have bugs around the correct value for dst(), see, e.g.
    # Until those are fixed, we'll try to avoid these "sore spots" with a
    # combination of one-offs and rough heuristics.

    if len(key) == 3 and key.lower() == "utc":
        uz = pds._compat.UTC
    else:
        uz = pds._compat.get_timezone(key)

    ###########
    # One-offs
    if PY2:
        # https://github.com/dateutil/dateutil/issues/1048
        hypothesis.assume(
            uz.dst(dt)
            or not ((uz.dst(dt + timedelta(hours=24)) or ZERO) < ZERO))

        # https://github.com/dateutil/dateutil/issues/1049
        hypothesis.assume(not (key == "Asia/Colombo" and datetime(1942, 1, 5)
                               <= dt <= datetime(1945, 10, 17)))

        # https://github.com/dateutil/dateutil/issues/1050
        hypothesis.assume(not (key == "America/Iqaluit" and datetime(
            1942, 7, 31) <= dt <= datetime(1945, 10, 1)))

        # Possibly another manifestation of dateutil/dateutil#1050
        hypothesis.assume(not (
            (key == "MET" or key == "CET")
            and datetime(1916, 5, 1) <= dt <= datetime(1916, 10, 2)))

        # dateutil isn't currently up to PEP 495's spec during ambiguous times,
        # which means occasionally it's an ambiguous time and neither side is
        # DST.
        from dateutil import tz

        hypothesis.assume(tz.datetime_exists(dt, uz))

    # bpo-40930: https://bugs.python.org/issue40930
    hypothesis.assume(not (key == "Pacific/Rarotonga" and datetime(
        1978, 11, 11) <= dt <= datetime(1991, 3, 2) and uz.dst(dt)))
    hypothesis.assume(not (key == "America/Montevideo" and (
        datetime(1923, 1, 1) <= dt <= datetime(1927, 1, 1) or
        datetime(1942, 12, 14) <= dt <= datetime(1943, 3, 13)) and uz.dst(dt)))

    # Argentina switched from -03 (STD) to -03 (DST) to -03 (STD) during this
    # interval, for whatever reason. pytz calls this dst() == 0, zoneinfo calls
    # this dst() == 1:00.
    hypothesis.assume(not (key in _ARGENTINA_ZONES and datetime(1999, 10, 3) <=
                           dt <= datetime(2000, 3, 4)))

    # bpo-40933: https://bugs.python.org/issue40933
    hypothesis.assume(not (key == "Europe/Minsk" and datetime(1941, 6, 27) <=
                           dt <= datetime(1943, 3, 30)))

    # Issue with pytz: America/Louisville transitioned from EST→CDT→EST in
    # 1974, but `pytz` returns timedelta(0) for CDT.
    hypothesis.assume(not (
        (key == "America/Louisville" or key == "America/Kentucky/Louisville"
         or key == "America/Indiana/Marengo")
        and datetime(1974, 1, 6) <= dt <= datetime(1974, 10, 28)))

    # Same deal with the RussiaAsia rule in 1991, a transition to DST with no
    # corresponding change in the offset, then a transition bac
    hypothesis.assume(not (key == "Asia/Qyzylorda" and datetime(1991, 3, 31) <=
                           dt <= datetime(1991, 9, 30)))

    # Issue with pytz: Europe/Paris went from CEST (+2, STD) → CEST (+2, DST) →
    # WEMT (+2, DST) → WEST (+1, DST) → WEMT (+2, DST) → CET (+1, STD) between
    # 3 April 1944 and 16 September 1945. pytz doesn't detect that WEMT is a
    # DST zone, though.
    hypothesis.assume(not (key == "Europe/Paris" and datetime(1944, 10, 8) <=
                           dt <= datetime(1945, 4, 3)))

    ###########
    # Bugs around incorrect double DST
    # https://github.com/stub42/pytz/issues/44
    # bpo-40931: https://bugs.python.org/issue40931
    pz = pytz.timezone(key)
    dt_pytz = pz.localize(dt, is_dst=is_dst)
    dt_uz = dt.replace(tzinfo=uz)
    dt_uz = enfold(dt_uz, fold=not is_dst)
    if abs(dt_pytz.dst()) <= timedelta(hours=1) and abs(
            dt_uz.dst() or timedelta(0)) <= timedelta(hours=1):
        return

    hypothesis.assume(dt_uz.dst() == dt_pytz.dst())
Beispiel #13
0
def test_datetimes_can_exclude_imaginary(kw):
    # Sanity check: fail unless those days contain an imaginary hour to filter out
    find_any(datetimes(**kw, allow_imaginary=True), lambda x: not datetime_exists(x))

    # Assert that with allow_imaginary=False we only generate existing datetimes.
    assert_all_examples(datetimes(**kw, allow_imaginary=False), datetime_exists)
Beispiel #14
0
 def imaginary(self):
     return not dtz.datetime_exists(self._dt)
def test_dateutil_exists_our_not_exists_are_inverse(value):
    assert datetime_does_not_exist(value) == (not tz.datetime_exists(value))
Beispiel #16
0
    def imaginary(self):
        """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone."""

        return not dateutil_tz.datetime_exists(self._datetime)
Beispiel #17
0
def is_imaginary(dt):
    return not tz.datetime_exists(dt)