def calculate_effective_schedule_start_dt(schedule): """ Calculation of the first start date to improve performance """ tz = timezone.get_default_timezone() programme_start_dt = tz.localize( datetime.datetime.combine(schedule.programme.start_date, datetime.time()) ).astimezone(pytz.utc) if schedule.programme.start_date else None programme_end_dt = tz.localize( datetime.datetime.combine(schedule.programme.end_date, datetime.time(23, 59, 59)) ).astimezone(pytz.utc) if schedule.programme.end_date else None # If there are no rrules if not schedule.recurrences: if programme_start_dt and programme_start_dt > schedule.start_dt: return None if programme_end_dt and schedule.start_dt > programme_end_dt: return None return schedule.start_dt # Get first date after_dt = schedule.start_dt if programme_start_dt: after_dt = max(schedule.start_dt, programme_start_dt) first_start_dt = fix_recurrence_dst(recurrence_after( schedule.recurrences, transform_dt_to_default_tz(after_dt), transform_dt_to_default_tz(schedule.start_dt))) if first_start_dt: if programme_end_dt and programme_end_dt < first_start_dt: return None return first_start_dt return None
def calculate_effective_schedule_start_dt(schedule): """ Calculation of the first start date to improve performance """ programme_start_dt = schedule.programme.start_dt programme_end_dt = schedule.programme.end_dt # If there are no rrules if not schedule.has_recurrences(): if programme_start_dt and programme_start_dt > schedule.start_dt: return None if programme_end_dt and schedule.start_dt > programme_end_dt: return None return schedule.start_dt # Get first date after_dt = schedule.start_dt if programme_start_dt: after_dt = max(schedule.start_dt, programme_start_dt) first_start_dt = fix_recurrence_dst(recurrence_after( schedule.recurrences, transform_dt_to_default_tz(after_dt), transform_dt_to_default_tz(schedule.start_dt))) if first_start_dt: if programme_end_dt and programme_end_dt < first_start_dt: return None return first_start_dt return None
def calculate_effective_schedule_start_dt(schedule): """ Calculation of the first start date to improve performance """ programme_start_dt = schedule.programme.start_dt programme_end_dt = schedule.programme.end_dt # If there are no rrules if not schedule.has_recurrences(): if programme_start_dt and programme_start_dt > schedule.start_dt: return None if programme_end_dt and schedule.start_dt > programme_end_dt: return None return schedule.start_dt # Get first date after_dt = schedule.start_dt if programme_start_dt: after_dt = max(schedule.start_dt, programme_start_dt) first_start_dt = fix_recurrence_dst(recurrence_after( schedule.recurrences, transform_dt_to_default_tz(after_dt), transform_dt_to_default_tz(schedule.start_dt))) if first_start_dt: if programme_end_dt and programme_end_dt < first_start_dt: return None return first_start_dt return None
def calculate_effective_schedule_end_dt(schedule): """ Calculation of the last end date to improve performance """ tz = timezone.get_default_timezone() programme_start_dt = tz.localize( datetime.datetime.combine( schedule.programme.start_date, datetime.time())).astimezone( pytz.utc) if schedule.programme.start_date else None programme_end_dt = tz.localize( datetime.datetime.combine( schedule.programme.end_date, datetime.time( 23, 59, 59))).astimezone( pytz.utc) if schedule.programme.end_date else None runtime = datetime.timedelta(minutes=schedule.programme._runtime) # If there are no rrules if not schedule.recurrences: if not schedule.effective_start_dt: # WARNING: this depends on effective_start_dt return None # returning None if there is no effective_start_dt return schedule.start_dt + runtime # If we have a programme restriction if programme_end_dt: last_effective_start_date = fix_recurrence_dst( recurrence_before(schedule.recurrences, transform_dt_to_default_tz(programme_end_dt), transform_dt_to_default_tz(schedule.start_dt))) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return last_effective_start_date + runtime rrules_until_dates = [ _rrule.until for _rrule in schedule.recurrences.rrules ] # If we have a rrule without a until date we don't know the last date if any([x is None for x in rrules_until_dates]): return None possible_limit_dates = schedule.recurrences.rdates + rrules_until_dates if not possible_limit_dates: return None # Get the biggest possible start_date. It could be that the biggest date is excluded biggest_date = max(possible_limit_dates) last_effective_start_date = schedule.recurrences.before( transform_dt_to_default_tz(biggest_date), True, dtstart=transform_dt_to_default_tz(schedule.start_dt)) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return fix_recurrence_dst(last_effective_start_date) + runtime return None
def date_after(self, after): after_date = self._merge_after(after) if not after_date: return after_date = transform_dt_to_default_tz(after_date) start_dt = transform_dt_to_default_tz(self.start_dt) date = recurrence_after(self.recurrences, after_date, start_dt) return fix_recurrence_dst(date)
def date_after(self, after): after_date = self._merge_after(after) if not after_date: return after_date = transform_dt_to_default_tz(after_date) start_dt = transform_dt_to_default_tz(self.start_dt) date = recurrence_after(self.recurrences, after_date, start_dt) return fix_recurrence_dst(date)
def calculate_effective_schedule_end_dt(schedule): """ Calculation of the last end date to improve performance """ tz = timezone.get_default_timezone() programme_start_dt = tz.localize( datetime.datetime.combine(schedule.programme.start_date, datetime.time()) ).astimezone(pytz.utc) if schedule.programme.start_date else None programme_end_dt = tz.localize( datetime.datetime.combine(schedule.programme.end_date, datetime.time(23, 59, 59)) ).astimezone(pytz.utc) if schedule.programme.end_date else None runtime = datetime.timedelta(minutes=schedule.programme._runtime) # If there are no rrules if not schedule.recurrences: if not schedule.effective_start_dt: # WARNING: this depends on effective_start_dt return None # returning None if there is no effective_start_dt return schedule.start_dt + runtime # If we have a programme restriction if programme_end_dt: last_effective_start_date = fix_recurrence_dst(recurrence_before( schedule.recurrences, transform_dt_to_default_tz(programme_end_dt), transform_dt_to_default_tz(schedule.start_dt))) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return last_effective_start_date + runtime rrules_until_dates = [_rrule.until for _rrule in schedule.recurrences.rrules] # If we have a rrule without a until date we don't know the last date if any([x is None for x in rrules_until_dates]): return None possible_limit_dates = schedule.recurrences.rdates + rrules_until_dates if not possible_limit_dates: return None # Get the biggest possible start_date. It could be that the biggest date is excluded biggest_date = max(possible_limit_dates) last_effective_start_date = schedule.recurrences.before( transform_dt_to_default_tz(biggest_date), True, dtstart=transform_dt_to_default_tz(schedule.start_dt)) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return fix_recurrence_dst(last_effective_start_date) + runtime return None
def calculate_effective_schedule_end_dt(schedule): """ Calculation of the last end date to improve performance """ programme_start_dt = schedule.programme.start_dt programme_end_dt = schedule.programme.end_dt # If there are no rrules if not schedule.has_recurrences(): if not schedule.effective_start_dt: # WARNING: this depends on effective_start_dt return None # returning None if there is no effective_start_dt return schedule.start_dt + schedule.runtime # If we have a programme restriction if programme_end_dt: last_effective_start_date = fix_recurrence_dst( recurrence_before(schedule.recurrences, transform_dt_to_default_tz(programme_end_dt), transform_dt_to_default_tz(schedule.start_dt))) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return last_effective_start_date + schedule.runtime rrules_until_dates = [ _rrule.until for _rrule in schedule.recurrences.rrules ] # If we have a rrule without a until date we don't know the last date if any([x is None for x in rrules_until_dates]): return None possible_limit_dates = schedule.recurrences.rdates + rrules_until_dates if not possible_limit_dates: return None # Get the biggest possible start_date. It could be that the biggest date is excluded biggest_date = max(possible_limit_dates) last_effective_start_date = schedule.recurrences.before( transform_dt_to_default_tz(biggest_date), True, dtstart=transform_dt_to_default_tz(schedule.start_dt)) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return fix_recurrence_dst(last_effective_start_date) + schedule.runtime return None
def test_transform_dt_to_default_tz(self): utc_dt = pytz.utc.localize(datetime.datetime(2017, 1, 1, 0, 00, 00)) spain_dt = transform_dt_to_default_tz(utc_dt) self.assertEquals(spain_dt.tzinfo.zone, 'Europe/Madrid') self.assertEquals( spain_dt, SPAIN_TZ.localize(datetime.datetime(2017, 1, 1, 1, 0, 0)))
def recording_schedules(request): podcast_config = PodcastConfiguration.get_global() default_tz = timezone.get_default_timezone() start = default_tz.localize(datetime.datetime.strptime(request.GET.get('start'), '%Y-%m-%d %H:%M:%S')) next_hours = int(request.GET.get("next_hours") or podcast_config.next_events) json_list = [] next_transmissions = Transmission.between( start, start + datetime.timedelta(hours=next_hours), schedules=Schedule.objects.filter(calendar__is_active=True, type='L') ) for transmission in next_transmissions: try: episode = Episode.objects.get(issue_date=transmission.start, programme=transmission.programme) except Episode.DoesNotExist: episode = Episode.objects.create_episode(transmission.start, transmission.programme) issue_date = transform_dt_to_default_tz(transmission.start) start_dt = issue_date + datetime.timedelta(seconds=podcast_config.start_delay) duration = transmission.schedule.runtime.seconds - podcast_config.start_delay - podcast_config.end_delay json_entry = { 'id': transmission.programme.id, 'issue_date': issue_date.strftime('%Y-%m-%d %H-%M-%S'), 'start': start_dt.strftime('%Y-%m-%d %H-%M-%S'), 'duration': str(duration), 'genre': transmission.programme.get_category_display(), 'programme_name': transmission.programme.slug, 'title': episode.title, 'author': transmission.programme.name, 'album': _('Season') + ' ' + str(episode.season), 'track': episode.number_in_season } json_list.append(json_entry) return HttpResponse(json.dumps(json_list), content_type='application/json')
def form_valid(self, form): cleaned_data = form.clean() schedule = cleaned_data['schedule'] transmission_dt = cleaned_data['transmission_dt'] action = cleaned_data.get('action') if not schedule.has_recurrences() or action == DeleteScheduleForm.DELETE_ALL: schedule.delete() elif action == DeleteScheduleForm.DELETE_ONLY_THIS: schedule.exclude_date(transmission_dt) schedule.save() elif action == DeleteScheduleForm.DELETE_THIS_AND_FOLLOWING: # until_dt is the end of the previous day until_dt = timezone.get_default_timezone().localize( datetime.datetime.combine( transform_dt_to_default_tz(transmission_dt).date() - datetime.timedelta(days=1), datetime.time(23, 59, 59) ) ) # removing rdates rules that are bigger than until_dt schedule.recurrences.rdates = [_dt for _dt in schedule.recurrences.rdates if _dt > until_dt] # Add a until constraint to all rules (except if they have a date more restrictive) for rrule in schedule.recurrences.rrules: if not rrule.until or rrule.until > until_dt: rrule.until = until_dt schedule.save() return HttpResponse( json.dumps({'result': 'ok'}), content_type='application/json' )
def form_valid(self, form): cleaned_data = form.clean() schedule = cleaned_data['schedule'] transmission_dt = cleaned_data['transmission_dt'] action = cleaned_data.get('action') if not schedule.has_recurrences( ) or action == DeleteScheduleForm.DELETE_ALL: schedule.delete() elif action == DeleteScheduleForm.DELETE_ONLY_THIS: schedule.exclude_date(transmission_dt) schedule.save() elif action == DeleteScheduleForm.DELETE_THIS_AND_FOLLOWING: # until_dt is the end of the previous day until_dt = timezone.get_default_timezone().localize( datetime.datetime.combine( transform_dt_to_default_tz(transmission_dt).date() - datetime.timedelta(days=1), datetime.time(23, 59, 59))) # removing rdates rules that are bigger than until_dt schedule.recurrences.rdates = [ _dt for _dt in schedule.recurrences.rdates if _dt > until_dt ] # Add a until constraint to all rules (except if they have a date more restrictive) for rrule in schedule.recurrences.rrules: if not rrule.until or rrule.until > until_dt: rrule.until = until_dt schedule.save() return HttpResponse(json.dumps({'result': 'ok'}), content_type='application/json')
def calculate_effective_schedule_end_dt(schedule): """ Calculation of the last end date to improve performance """ programme_start_dt = schedule.programme.start_dt programme_end_dt = schedule.programme.end_dt # If there are no rrules if not schedule.has_recurrences(): if not schedule.effective_start_dt: # WARNING: this depends on effective_start_dt return None # returning None if there is no effective_start_dt return schedule.start_dt + schedule.runtime # If we have a programme restriction if programme_end_dt: last_effective_start_date = fix_recurrence_dst(recurrence_before( schedule.recurrences, transform_dt_to_default_tz(programme_end_dt), transform_dt_to_default_tz(schedule.start_dt))) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return last_effective_start_date + schedule.runtime rrules_until_dates = [_rrule.until for _rrule in schedule.recurrences.rrules] # If we have a rrule without a until date we don't know the last date if any([x is None for x in rrules_until_dates]): return None possible_limit_dates = schedule.recurrences.rdates + rrules_until_dates if not possible_limit_dates: return None # Get the biggest possible start_date. It could be that the biggest date is excluded biggest_date = max(possible_limit_dates) last_effective_start_date = schedule.recurrences.before( transform_dt_to_default_tz(biggest_date), True, dtstart=transform_dt_to_default_tz(schedule.start_dt)) if last_effective_start_date: if programme_start_dt and programme_start_dt > last_effective_start_date: return None return fix_recurrence_dst(last_effective_start_date) + schedule.runtime return None
def _update_recurrence_dates(self): """ Fix for django-recurrence 1.3 We need to update the internal until datetime to include the whole day """ default_tz = timezone.get_default_timezone() for rrule in self.recurrences.rrules: if rrule.until: rrule.until = default_tz.localize(datetime.datetime.combine( transform_dt_to_default_tz(rrule.until).date(), datetime.time(23, 59, 59)))
def _update_recurrence_dates(self): """ Fix for django-recurrence 1.3 We need to update the internal until datetime to include the whole day """ default_tz = timezone.get_default_timezone() for rrule in self.recurrences.rrules: if rrule.until: rrule.until = default_tz.localize(datetime.datetime.combine( transform_dt_to_default_tz(rrule.until).date(), datetime.time(23, 59, 59)))
def dates_between(self, after, before): """ Return a sorted list of dates between after and before """ after_date = self._merge_after(after) if not after_date: return after_date = transform_dt_to_default_tz(after_date) before_date = transform_dt_to_default_tz(self._merge_before(before)) start_dt = transform_dt_to_default_tz(self.start_dt) # We need to send the dates in the default timezone recurrence_dates_between = self.recurrences.between(after_date, before_date, inc=True, dtstart=start_dt) # Special case to include started episodes date_before = self.date_before(after_date) if date_before and date_before < after_date < date_before + self.runtime: yield date_before # Date was already fixed for date in recurrence_dates_between: yield fix_recurrence_dst(date) # Fixing date
def dates_between(self, after, before): """ Return a sorted list of dates between after and before """ after_date = self._merge_after(after) if not after_date: return after_date = transform_dt_to_default_tz(after_date) before_date = transform_dt_to_default_tz(self._merge_before(before)) start_dt = transform_dt_to_default_tz(self.start_dt) # We need to send the dates in the default timezone recurrence_dates_between = self.recurrences.between(after_date, before_date, inc=True, dtstart=start_dt) # Special case to include started episodes date_before = self.date_before(after_date) if date_before and date_before < after_date < date_before + self.runtime: yield date_before # Date was already fixed for date in recurrence_dates_between: yield fix_recurrence_dst(date) # Fixing date
def calculate_effective_schedule_start_dt(schedule): """ Calculation of the first start date to improve performance """ tz = timezone.get_default_timezone() programme_start_dt = tz.localize( datetime.datetime.combine( schedule.programme.start_date, datetime.time())).astimezone( pytz.utc) if schedule.programme.start_date else None programme_end_dt = tz.localize( datetime.datetime.combine( schedule.programme.end_date, datetime.time( 23, 59, 59))).astimezone( pytz.utc) if schedule.programme.end_date else None # If there are no rrules if not schedule.recurrences: if programme_start_dt and programme_start_dt > schedule.start_dt: return None if programme_end_dt and schedule.start_dt > programme_end_dt: return None return schedule.start_dt # Get first date after_dt = schedule.start_dt if programme_start_dt: after_dt = max(schedule.start_dt, programme_start_dt) first_start_dt = fix_recurrence_dst( recurrence_after(schedule.recurrences, transform_dt_to_default_tz(after_dt), transform_dt_to_default_tz(schedule.start_dt))) if first_start_dt: if programme_end_dt and programme_end_dt < first_start_dt: return None return first_start_dt return None
def date_before(self, before): before_date = transform_dt_to_default_tz(self._merge_before(before)) start_dt = transform_dt_to_default_tz(self.start_dt) date = recurrence_before(self.recurrences, before_date, start_dt) return fix_recurrence_dst(date)
def date(self): local_dt = transform_dt_to_default_tz(self.datetime) return local_dt.date()
def date_before(self, before): before_date = transform_dt_to_default_tz(self._merge_before(before)) start_dt = transform_dt_to_default_tz(self.start_dt) date = recurrence_before(self.recurrences, before_date, start_dt) return fix_recurrence_dst(date)
def include_date(self, dt): local_dt = transform_dt_to_default_tz(dt) self.recurrences.exdates.remove(fix_recurrence_date(self.start_dt, local_dt)) ExcludedDates.objects.get(schedule=self, datetime=dt).delete()
def exclude_date(self, dt): local_dt = transform_dt_to_default_tz(dt) self.recurrences.exdates.append(fix_recurrence_date(self.start_dt, local_dt)) ExcludedDates.objects.create(schedule=self, datetime=dt)
def date(self): local_dt = transform_dt_to_default_tz(self.datetime) return local_dt.date()
def test_transform_dt_to_default_tz(self): utc_dt = pytz.utc.localize(datetime.datetime(2017, 1, 1, 0, 00, 00)) spain_dt = transform_dt_to_default_tz(utc_dt) self.assertEqual(spain_dt.tzinfo.zone, 'Europe/Madrid') self.assertEqual(spain_dt, SPAIN_TZ.localize(datetime.datetime(2017, 1, 1, 1, 0, 0)))
def exclude_date(self, dt): local_dt = transform_dt_to_default_tz(dt) self.recurrences.exdates.append(fix_recurrence_date(self.start_dt, local_dt)) ExcludedDates.objects.create(schedule=self, datetime=dt)
def include_date(self, dt): local_dt = transform_dt_to_default_tz(dt) self.recurrences.exdates.remove(fix_recurrence_date(self.start_dt, local_dt)) ExcludedDates.objects.get(schedule=self, datetime=dt).delete()