def test_serialize_complex_schedule(): dt = pendulum.datetime(2019, 1, 3) s = schedules.Schedule( # fire every hour clocks=[clocks.IntervalClock(timedelta(hours=1))], # only on weekdays filters=[filters.is_weekday], # only at 9am or 3pm or_filters=[ filters.at_time(pendulum.time(9)), filters.between_times(pendulum.time(15), pendulum.time(15)), ], # not on january 8 not_filters=[filters.between_dates(1, 8, 1, 8)], # add three hours adjustments=[adjustments.add(timedelta(hours=3))], ) s2 = serialize_and_deserialize(s) assert s2.next(8, after=dt) == [ dt.replace(hour=12), dt.replace(hour=18), dt.add(days=1).replace(hour=12), dt.add(days=1).replace(hour=18), # skip weekend dt.add(days=4).replace(hour=12), dt.add(days=4).replace(hour=18), # skip jan 8! dt.add(days=6).replace(hour=12), dt.add(days=6).replace(hour=18), ]
class Bill(models.Model): STANDING_CHARGE = Decimal('0.36') CALL_CHARGE = Decimal('0.09') REDUCED_HOURS = (pendulum.time(22), pendulum.time(6)) start_record = models.ForeignKey(CallRecord, related_name='start_record_bill', on_delete=models.CASCADE, help_text='The start record of the pair') end_record = models.ForeignKey(CallRecord, related_name='end_record_bill', on_delete=models.CASCADE, help_text='The end record of the pair') price = models.DecimalField(max_digits=10, decimal_places=2, help_text='The bill price') @property def start_date(self): return self.start_record.timestamp.date() @property def start_time(self): return self.start_record.timestamp.time() @property def duration(self): start_date = pendulum.instance(self.start_record.timestamp) end_date = pendulum.instance(self.end_record.timestamp) return (end_date - start_date) def _calculate_price(self): price = Decimal('0.0') start_date = pendulum.instance(self.start_record.timestamp) end_date = pendulum.instance(self.end_record.timestamp) period = pendulum.period(start_date, end_date) if period.total_minutes() >= 1: min_reduced_hour, max_reduced_hour = self.REDUCED_HOURS for date in period.range('minutes'): time = date.time() if time >= min_reduced_hour or time < max_reduced_hour: continue if date == start_date: # period.range is inclusive. We only compute complete cycles of # minutes. continue price += Decimal('0.09') price += self.STANDING_CHARGE return price.quantize(Decimal('0.01')) def save(self, *args, **kwargs): self.price = self._calculate_price() return super().save(*args, **kwargs)
def test_equal_to_true(): t1 = pendulum.time(1, 2, 3) t2 = pendulum.time(1, 2, 3) t3 = time(1, 2, 3) assert t1 == t2 assert t1 == t3
def test_create_schedule_multiple_filters(): # jan 3 was a thursday dt = pendulum.datetime(2019, 1, 3) s = schedules.Schedule( # fire every hour clocks=[clocks.IntervalClock(timedelta(hours=1))], # only on weekdays filters=[filters.is_weekday], # only at 9am or 3pm or_filters=[ filters.between_times(pendulum.time(9), pendulum.time(9)), filters.between_times(pendulum.time(15), pendulum.time(15)), ], # not on january 8 not_filters=[filters.between_dates(1, 8, 1, 8)], ) assert s.next(8, after=dt) == [ dt.replace(hour=9), dt.replace(hour=15), dt.add(days=1).replace(hour=9), dt.add(days=1).replace(hour=15), # skip weekend dt.add(days=4).replace(hour=9), dt.add(days=4).replace(hour=15), # skip jan 8! dt.add(days=6).replace(hour=9), dt.add(days=6).replace(hour=15), ]
def test_less_than_or_equal_true(): t1 = pendulum.time(1, 2, 2) t2 = pendulum.time(1, 2, 3) t3 = time(1, 2, 3) assert t1 <= t2 assert t1 <= t3
def test_greater_than_or_equal_false(): t1 = pendulum.time(1, 2, 2) t2 = pendulum.time(1, 2, 3) t3 = time(1, 2, 3) assert not t1 >= t2 assert not t1 >= t3
def test_less_than_true(): t1 = pendulum.time(1, 2, 2) t2 = pendulum.time(1, 2, 3) t3 = time(1, 2, 3) assert t1 < t2 assert t1 < t3
def test_less_than_false(): t1 = pendulum.time(1, 2, 3) t2 = pendulum.time(1, 2, 2) t3 = time(1, 2, 2) assert not t1 < t2 assert not t1 < t3
def test_greater_than_or_equal_true_equal(): t1 = pendulum.time(1, 2, 3) t2 = pendulum.time(1, 2, 3) t3 = time(1, 2, 3) assert t1 >= t2 assert t1 >= t3
def test_greater_than_true(): t1 = pendulum.time(1, 2, 3) t2 = pendulum.time(1, 2, 2) t3 = time(1, 2, 2) assert t1 > t2 assert t1 > t3
def test_farthest_with_time(): instance = pendulum.time(12, 34, 56) t1 = pendulum.time(12, 34, 54) t2 = pendulum.time(12, 34, 59) farthest = instance.farthest(t1, t2) assert_time(farthest, 12, 34, 59)
def test_closest_with_time(): instance = pendulum.time(12, 34, 56) t1 = pendulum.time(12, 34, 54) t2 = pendulum.time(12, 34, 59) closest = instance.closest(t1, t2) assert_time(closest, 12, 34, 54)
def test_equal_to_false(): t1 = pendulum.time(1, 2, 3) t2 = pendulum.time(1, 2, 4) t3 = time(1, 2, 4) assert t1 != t2 assert t1 != t3
def test_farthest_with_equals(): instance = pendulum.time(12, 34, 56) t1 = pendulum.time(12, 34, 56) t2 = pendulum.time(12, 34, 59) farthest = instance.farthest(t1, t2) assert t2 == farthest
def test_farthest(): instance = pendulum.time(12, 34, 56) t1 = pendulum.time(12, 34, 54) t2 = pendulum.time(12, 34, 59) farthest = instance.farthest(t1, t2) assert t2 == farthest farthest = instance.farthest(t2, t1) assert t2 == farthest
def test_closest(): instance = pendulum.time(12, 34, 56) t1 = pendulum.time(12, 34, 54) t2 = pendulum.time(12, 34, 59) closest = instance.closest(t1, t2) assert t1 == closest closest = instance.closest(t2, t1) assert t1 == closest
def test_init_with_missing_values(): t = pendulum.time(12, 34, 56) assert_time(t, 12, 34, 56, 0) t = pendulum.time(12, 34) assert_time(t, 12, 34, 0, 0) t = pendulum.time(12) assert_time(t, 12, 0, 0, 0)
def test_create_schedule_multiple_exclusive_filters(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule( clocks=[clocks.IntervalClock(timedelta(hours=1))], filters=[ filters.between_times(pendulum.time(9), pendulum.time(10)), filters.between_times(pendulum.time(15), pendulum.time(16)), ], ) assert s.next(6, after=dt) == []
def test_create_schedule_filters(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule( clocks=[clocks.IntervalClock(timedelta(hours=1))], filters=[filters.between_times(pendulum.time(9), pendulum.time(10))], ) assert s.next(6, after=dt) == [ dt.add(days=0).replace(hour=9), dt.add(days=0).replace(hour=10), dt.add(days=1).replace(hour=9), dt.add(days=1).replace(hour=10), dt.add(days=2).replace(hour=9), dt.add(days=2).replace(hour=10), ]
def test_add_timedelta(): delta = timedelta(seconds=45, microseconds=123456) d = pendulum.time(3, 12, 15, 654321) d = d.add_timedelta(delta) assert d.minute == 13 assert d.second == 0 assert d.microsecond == 777777 d = pendulum.time(3, 12, 15, 654321) d = d + delta assert d.minute == 13 assert d.second == 0 assert d.microsecond == 777777
def test_addition_invalid_type(): d = pendulum.time(3, 12, 15, 654321) with pytest.raises(TypeError): d + 3 with pytest.raises(TypeError): 3 + d
def offset_from_now(time): x = pendulum.utcnow().time() if time > x: x = x.diff(time) hours = x.hours minutes = x.minutes else: x = pendulum.time(23, 59, 59) - time.diff(x) hours = x.hour minutes = x.minute if hours == 0: return "{0}m".format(minutes) else: return "{0}h{1:02d}m".format(hours, minutes)
def test_create_schedule_filters_2(): dt = pendulum.datetime(2019, 1, 1) s = schedules.Schedule( clocks=[clocks.IntervalClock(timedelta(minutes=15))], filters=[filters.at_time(pendulum.time(2, 45))], ) assert s.next(6, after=dt) == [ dt.add(days=0, hours=2, minutes=45), dt.add(days=1, hours=2, minutes=45), dt.add(days=2, hours=2, minutes=45), dt.add(days=3, hours=2, minutes=45), dt.add(days=4, hours=2, minutes=45), dt.add(days=5, hours=2, minutes=45), ]
def _set(self, value): # Force to appropriate Pendulum instance for consistency if value is not None: if self.input_type != self._type_interval: if self.input_type == self._type_date: # Pendulum date if isinstance(value, date): value = pendulum.DateTime.combine( value, pendulum.time(0)) elif self.input_type == self._type_time: # Pendulum time if isinstance(value, time): value = pendulum.DateTime.combine( pendulum.today().date(), value) # Convert to Pendulum instance in UTC value = UTC.convert(pendulum.instance(value)) # Drop nanosecond precision to match Mongo precision value = value.set(microsecond=int( math.floor(value.microsecond / 1000) * 1000)) return super(DatetimeField, self)._set(value)
def _set(self, value): self.validate_value(value) # Force to appropriate Pendulum instance for consistency if value is not None: if self.input_type == self._type_interval: # Pendulum interval value = pendulum.interval.instance(value) else: if self.input_type == self._type_date: # Pendulum date if isinstance(value, date): value = pendulum.combine(value, pendulum.time(0)) elif self.input_type == self._type_time: # Pendulum time if isinstance(value, time): value = pendulum.combine(pendulum.date.today(), value) # Convert to Pendulum instance in UTC value = UTC.convert(pendulum.instance(value)) return super(DatetimeField, self)._set(value)
def test_add_minutes_positive(): assert pendulum.time(12, 34, 56).add(minutes=1).minute == 35
def _parse(text, **options): """ Parses a string with the given options. :param text: The string to parse. :type text: str :rtype: mixed """ # Handling special cases if text == "now": return pendulum.now() parsed = base_parse(text, **options) if isinstance(parsed, datetime.datetime): return pendulum.datetime( parsed.year, parsed.month, parsed.day, parsed.hour, parsed.minute, parsed.second, parsed.microsecond, tz=parsed.tzinfo or options.get("tz", UTC), ) if isinstance(parsed, datetime.date): return pendulum.date(parsed.year, parsed.month, parsed.day) if isinstance(parsed, datetime.time): return pendulum.time( parsed.hour, parsed.minute, parsed.second, parsed.microsecond ) if isinstance(parsed, _Interval): if parsed.duration is not None: duration = parsed.duration if parsed.start is not None: dt = pendulum.instance(parsed.start, tz=options.get("tz", UTC)) return pendulum.period( dt, dt.add( years=duration.years, months=duration.months, weeks=duration.weeks, days=duration.remaining_days, hours=duration.hours, minutes=duration.minutes, seconds=duration.remaining_seconds, microseconds=duration.microseconds, ), ) dt = pendulum.instance(parsed.end, tz=options.get("tz", UTC)) return pendulum.period( dt.subtract( years=duration.years, months=duration.months, weeks=duration.weeks, days=duration.remaining_days, hours=duration.hours, minutes=duration.minutes, seconds=duration.remaining_seconds, microseconds=duration.microseconds, ), dt, ) return pendulum.period( pendulum.instance(parsed.start, tz=options.get("tz", UTC)), pendulum.instance(parsed.end, tz=options.get("tz", UTC)), ) if CDuration and isinstance(parsed, CDuration): return pendulum.duration( years=parsed.years, months=parsed.months, weeks=parsed.weeks, days=parsed.days, hours=parsed.hours, minutes=parsed.minutes, seconds=parsed.seconds, microseconds=parsed.microseconds, ) return parsed
def _parse(text, **options): """ Parses a string with the given options. :param text: The string to parse. :type text: str :rtype: mixed """ # Handling special cases if text == "now": return pendulum.now() parsed = base_parse(text, **options) if isinstance(parsed, datetime.datetime): return pendulum.datetime( parsed.year, parsed.month, parsed.day, parsed.hour, parsed.minute, parsed.second, parsed.microsecond, tz=parsed.tzinfo or options.get("tz", UTC), ) if isinstance(parsed, datetime.date): return pendulum.date(parsed.year, parsed.month, parsed.day) if isinstance(parsed, datetime.time): return pendulum.time(parsed.hour, parsed.minute, parsed.second, parsed.microsecond) if isinstance(parsed, _Interval): if parsed.duration is not None: duration = parsed.duration if parsed.start is not None: dt = pendulum.instance(parsed.start, tz=options.get("tz", UTC)) return pendulum.period( dt, dt.add( years=duration.years, months=duration.months, weeks=duration.weeks, days=duration.remaining_days, hours=duration.hours, minutes=duration.minutes, seconds=duration.remaining_seconds, microseconds=duration.microseconds, ), ) dt = pendulum.instance(parsed.end, tz=options.get("tz", UTC)) return pendulum.period( dt.subtract( years=duration.years, months=duration.months, weeks=duration.weeks, days=duration.remaining_days, hours=duration.hours, minutes=duration.minutes, seconds=duration.remaining_seconds, microseconds=duration.microseconds, ), dt, ) return pendulum.period( pendulum.instance(parsed.start, tz=options.get("tz", UTC)), pendulum.instance(parsed.end, tz=options.get("tz", UTC)), ) if CDuration and isinstance(parsed, CDuration): return pendulum.duration( years=parsed.years, months=parsed.months, weeks=parsed.weeks, days=parsed.days, hours=parsed.hours, minutes=parsed.minutes, seconds=parsed.seconds, microseconds=parsed.microseconds, ) return parsed
def test_closest_with_equals(): instance = pendulum.time(12, 34, 56) t1 = pendulum.time(12, 34, 56) t2 = pendulum.time(12, 34, 59) closest = instance.closest(t1, t2) assert t1 == closest
def test_add_hours_zero(): assert pendulum.time(12, 34, 56).add(hours=0).hour == 12
def test_not_equal_to_none(): t1 = pendulum.time(1, 2, 3) assert t1 != None
def test_init(): t = pendulum.time(12, 34, 56, 123456) assert_time(t, 12, 34, 56, 123456)
def test_add_hours_positive(): assert pendulum.time(12, 34, 56).add(hours=1).hour == 13
def test_not_equal_to_none(): t1 = pendulum.time(1, 2, 3) assert t1 != None # noqa
def test_add_minutes_zero(): assert pendulum.time(12, 34, 56).add(minutes=0).minute == 34
def test_add_minutes_negative(): assert pendulum.time(12, 34, 56).add(minutes=-1).minute == 33
def test_add_seconds_positive(): assert pendulum.time(12, 34, 56).add(seconds=1).second == 57
) # Run empty_task_1 if cond1 executes between 2020-10-10 14:00:00 and 2020-10-10 15:00:00 cond1 >> [empty_task_11, empty_task_21] # [END howto_branch_datetime_operator] dag2 = DAG( dag_id="example_branch_datetime_operator_2", start_date=pendulum.datetime(2021, 1, 1, tz="UTC"), catchup=False, tags=["example"], schedule="@daily", ) # [START howto_branch_datetime_operator_next_day] empty_task_12 = EmptyOperator(task_id='date_in_range', dag=dag2) empty_task_22 = EmptyOperator(task_id='date_outside_range', dag=dag2) cond2 = BranchDateTimeOperator( task_id='datetime_branch', follow_task_ids_if_true=['date_in_range'], follow_task_ids_if_false=['date_outside_range'], target_upper=pendulum.time(0, 0, 0), target_lower=pendulum.time(15, 0, 0), dag=dag2, ) # Since target_lower happens after target_upper, target_upper will be moved to the following day # Run empty_task_1 if cond2 executes between 15:00:00, and 00:00:00 of the following day cond2 >> [empty_task_12, empty_task_22] # [END howto_branch_datetime_operator_next_day]
def test_add_timedelta_with_days(): delta = timedelta(days=3, seconds=45, microseconds=123456) d = pendulum.time(3, 12, 15, 654321) with pytest.raises(TypeError): d.add_timedelta(delta)
def test_add_hours_negative(): assert pendulum.time(12, 34, 56).add(hours=-1).hour == 11
def test_at_time(test_times): test_dt, result = test_times filter_fn = filters.at_time(pendulum.time(3, 30)) assert filter_fn(test_dt) is result
def test_add_seconds_zero(): assert pendulum.time(12, 34, 56).add(seconds=0).second == 56
def test_add_seconds_negative(): assert pendulum.time(12, 34, 56).add(seconds=-1).second == 55