Repository: ansible/awx
File: awx/main/models/schedules.py (Python)

Code before:
        raise RuntimeError("Cannot fast forward rrule, frequency must be HOURLY or MINUTELY")

    interval = rrule._interval if rrule._interval else 1
    if rrule._freq == dateutil.rrule.HOURLY:
        interval *= 60 * 60
    elif rrule._freq == dateutil.rrule.MINUTELY:
        interval *= 60

    if type(interval) == float and not interval.is_integer():
        raise RuntimeError("Cannot fast forward rule, interval is a fraction of a second")

    seconds_since_dtstart = (now() - rrule._dtstart).total_seconds()

    # fast forward to 7 days ago
    fast_forward_seconds = seconds_since_dtstart - SECONDS_IN_WEEK

    # make sure at least one chunk of interval can fit inside the time period we are trying to fast forward
    if interval > fast_forward_seconds:
        raise RuntimeError(f"Cannot fast forward rrule {rrule}, interval is greater than the fast forward amount of {fast_forward_seconds} seconds")

    # it is important to fast forward by a number that is divisible by
    # interval. For example, if interval is 7 hours, we fast forward by 7, 14, 21, etc. hours.
    # Otherwise, the occurrences after the fast forward might not match the ones before.
    # x // y is integer division, lopping off any remainder, so that we get the outcome we want.
    new_start = rrule._dtstart + datetime.timedelta(seconds=(fast_forward_seconds // interval) * interval)
    return new_start


class ScheduleFilterMethods(object):
    def enabled(self, enabled=True):
        return self.filter(enabled=enabled)

    def before(self, dt):
        return self.filter(next_run__lt=dt)

    def after(self, dt):
        return self.filter(next_run__gt=dt)

    def between(self, begin, end):
        return self.after(begin).before(end)


class ScheduleQuerySet(ScheduleFilterMethods, QuerySet):
    pass


class ScheduleManager(ScheduleFilterMethods, models.Manager):
    use_for_related_objects = True

    def get_queryset(self):
        return ScheduleQuerySet(self.model, using=self._db)

Review comment (refactor):
Seeing this code comment, makes me think that this line is hard to follow, and it might not belong on this abstraction layer. I believe that such things can be labeled with descriptive variable names (or function names for that matter), improving the perception.
Wouldn't it be easier to follow if this were to read
```python
return _compute_interval_aligned_datetime(rrule._dtstart, fast_forward_seconds, interval)
```
or
```python
return rrule._dtstart + _compute_interval_aligned_time_shift(fast_forward_seconds, interval)
```
?

Code after:
        rrules[i] = _fast_forward_rrule(rule, ref_dt=ref_dt)
    return rrules


def _fast_forward_rrule(rrule, ref_dt=None):
    '''
    Utility to fast forward an rrule, maintaining consistency in the resulting
    occurrences.

    Uses the .replace() method to update the rrule with a newer dtstart
    The operation ensures that the original occurrences (based on the original dtstart)
    will match the occurrences after changing the dtstart.

    Returns a new rrule with a new dtstart
    '''
    if rrule._freq not in {dateutil.rrule.HOURLY, dateutil.rrule.MINUTELY}:
        return rrule

    if ref_dt is None:
        ref_dt = now()

    if rrule._dtstart > ref_dt:
        return rrule

    interval = rrule._interval if rrule._interval else 1
    if rrule._freq == dateutil.rrule.HOURLY:
        interval *= 60 * 60
    elif rrule._freq == dateutil.rrule.MINUTELY:
        interval *= 60

    # if after converting to seconds the interval is still a fraction,
    # just return original rrule
    if isinstance(interval, float) and not interval.is_integer():
        return rrule

    seconds_since_dtstart = (ref_dt - rrule._dtstart).total_seconds()

    # it is important to fast forward by a number that is divisible by
    # interval. For example, if interval is 7 hours, we fast forward by 7, 14, 21, etc. hours.
    # Otherwise, the occurrences after the fast forward might not match the ones before.
    # x // y is integer division, lopping off any remainder, so that we get the outcome we want.
    interval_aligned_offset = datetime.timedelta(seconds=(seconds_since_dtstart // interval) * interval)
    new_start = rrule._dtstart + interval_aligned_offset
    new_rrule = rrule.replace(dtstart=new_start)
    return new_rrule


class ScheduleFilterMethods(object):
    def enabled(self, enabled=True):
        return self.filter(enabled=enabled)