def _mocked_event_list(): """Provide fixture to mock a list of events.""" return [ CalendarEvent( summary="Test event 2", start=dtparser.parse("2022-01-04T00:00:00Z"), end=dtparser.parse("2022-01-04T05:00:00Z"), location="Test location", description="Test description", ), CalendarEvent( summary="Test event", start=dtparser.parse("2022-01-03T00:00:00Z"), end=dtparser.parse("2022-01-03T05:00:00Z"), location="Test location", description="Test description", ), CalendarEvent( summary="Test event 3", start=dtparser.parse("2022-01-05T00:00:00Z"), end=dtparser.parse("2022-01-05T05:00:00Z"), location="Test location", description="Test description", ), ]
def calendar_event(self) -> CalendarEvent | None: """Return the next upcoming calendar event.""" if not self.event: return None if not self.event.get(END) or self.event.get(ALL_DAY): start = self.event[START].date() return CalendarEvent( summary=self.event[SUMMARY], start=start, end=start + timedelta(days=1), ) return CalendarEvent(summary=self.event[SUMMARY], start=self.event[START], end=self.event[END])
async def async_get_events(self, hass, start_date, end_date) -> list[CalendarEvent]: """Get all events in a specific time frame.""" try: events = await get_lessons( self.client, date_from=start_date, date_to=end_date, ) except UnauthorizedCertificateException as err: raise ConfigEntryAuthFailed( "The certificate is not authorized, please authorize integration again" ) from err except ClientConnectorError as err: if self.available: _LOGGER.warning( "Connection error - please check your internet connection: %s", err) events = [] event_list = [] for item in events: event = CalendarEvent( start=datetime.combine(item["date"], item["time"].from_), end=datetime.combine(item["date"], item["time"].to), summary=item["lesson"], location=item["room"], description=item["teacher"], ) event_list.append(event) return event_list
def _ical_event(self, start, end, from_date, event) -> CalendarEvent | None: """Ensure that events are within the start and end.""" # Skip this event if it's in the past if end.date() < from_date.date(): _LOGGER.debug("This event has already ended") return None # Ignore events that ended this midnight. if ( end.date() == from_date.date() and end.hour == 0 and end.minute == 0 and end.second == 0 ): _LOGGER.debug("This event has already ended") return None _LOGGER.debug( "Start: %s Tzinfo: %s Default: %s StartAs %s", str(start), str(start.tzinfo), self.timezone, start.astimezone(self.timezone), ) cal_event = CalendarEvent( description=event.get("DESCRIPTION"), end=end.astimezone(self.timezone), location=event.get("LOCATION"), summary=event.get("SUMMARY", "Unknown"), start=start.astimezone(self.timezone), ) _LOGGER.debug("Event to add: %s", str(CalendarEvent)) return cal_event
async def async_update(self, hass): """Do the update.""" start_of_day_utc = dt.as_utc(dt.start_of_local_day()) results = await self.async_o365_get_events( hass, start_of_day_utc, start_of_day_utc + timedelta(days=1), ) if not results: return results = list(results) results.sort(key=lambda x: self.to_datetime(x.start)) vevent = self._get_root_event(results) if vevent is None: _LOGGER.debug( "No matching event found in the %d results for %s", len(results), self._entity_id, ) self.event = None return self.event = CalendarEvent( self.get_hass_date(vevent.start, vevent.is_all_day), self.get_hass_date(self.get_end_date(vevent), vevent.is_all_day), vevent.subject, clean_html(vevent.body), vevent.location["displayName"], )
def _convert(self, collection) -> CalendarEvent: """Convert an collection into a Home Assistant calendar event.""" return CalendarEvent( summary=collection.type, start=collection.date, end=collection.date + timedelta(days=1), )
def calendar_data_current() -> CalendarEvent: """Representation of a Demo Calendar for a current event.""" middle_of_event = dt_util.now() - datetime.timedelta(minutes=30) return CalendarEvent( start=middle_of_event, end=middle_of_event + datetime.timedelta(minutes=60), summary="Current Event", )
def calendar_data_future() -> CalendarEvent: """Representation of a Demo Calendar for a future event.""" one_hour_from_now = dt_util.now() + datetime.timedelta(minutes=30) return CalendarEvent( start=one_hour_from_now, end=one_hour_from_now + datetime.timedelta(minutes=60), summary="Future Event", )
def _get_calendar_event(event: Event) -> CalendarEvent: """Return a CalendarEvent from an API event.""" return CalendarEvent( summary=event.summary, start=event.start.value, end=event.end.value, description=event.description, location=event.location, )
def _mocked_event(): """Provide fixture to mock a single event.""" return CalendarEvent( summary="Test event", start=dtparser.parse("2022-01-03T00:00:00"), end=dtparser.parse("2022-01-03T05:00:00"), location="Test location", description="Test description", )
def _mocked_event_allday(): """Provide fixture to mock a single all day event.""" return CalendarEvent( summary="Test event", start=dtparser.parse("2022-01-03").date(), end=dtparser.parse("2022-01-04").date(), location="Test location", description="Test description", )
async def async_get_events( self, hass: HomeAssistant, start_datetime: datetime, end_datetime: datetime ) -> list[CalendarEvent]: """Get all tasks in a specific time frame.""" events: list[CalendarEvent] = [] if SENSOR_PLATFORM not in hass.data[DOMAIN]: return events start_date = start_datetime.date() end_date = end_datetime.date() for entity in self.entities: if ( entity not in hass.data[DOMAIN][SENSOR_PLATFORM] or hass.data[DOMAIN][SENSOR_PLATFORM][entity].hidden ): continue garbage_collection = hass.data[DOMAIN][SENSOR_PLATFORM][entity] start = garbage_collection.get_next_date(start_date, True) while start is not None and start_date <= start <= end_date: try: end = start + timedelta(days=1) except TypeError: end = start name = ( garbage_collection.name if garbage_collection.name is not None else "Unknown" ) if garbage_collection.expire_after is None: event = CalendarEvent( summary=name, start=start, end=end, ) else: event = CalendarEvent( summary=name, start=datetime.combine(start, datetime.min.time()), end=datetime.combine(start, garbage_collection.expire_after), ) events.append(event) start = garbage_collection.get_next_date( start + timedelta(days=1), True ) return events
async def async_get_events(self, hass: HomeAssistant, start_date: datetime, end_date: datetime) -> list[CalendarEvent]: """Return calendar events within a datetime range.""" events: list[CalendarEvent] = [] for waste_type, waste_dates in self.coordinator.data.items(): events.extend( CalendarEvent( summary=WASTE_TYPE_TO_DESCRIPTION[waste_type], start=waste_date, end=waste_date, ) for waste_date in waste_dates if start_date.date() <= waste_date <= end_date.date()) return events
async def async_get_events(self, hass, start_date, end_date): """Get all tasks in a specific time frame.""" if self._id is None: project_task_data = [ task for task in self._api.state[TASKS] if not self._project_id_whitelist or task[PROJECT_ID] in self._project_id_whitelist ] else: project_data = await hass.async_add_executor_job( self._api.projects.get_data, self._id ) project_task_data = project_data[TASKS] events = [] for task in project_task_data: if task["due"] is None: continue # @NOTE: _parse_due_date always returns the date in UTC time. due_date: datetime | None = _parse_due_date( task["due"], self._api.state["user"]["tz_info"]["hours"] ) if not due_date: continue midnight = dt.as_utc( dt.parse_datetime( due_date.strftime("%Y-%m-%d") + "T00:00:00" + self._api.state["user"]["tz_info"]["gmt_string"] ) ) if start_date < due_date < end_date: due_date_value: datetime | date = due_date if due_date == midnight: # If the due date has no time data, return just the date so that it # will render correctly as an all day event on a calendar. due_date_value = due_date.date() event = CalendarEvent( summary=task["content"], start=due_date_value, end=due_date_value, ) events.append(event) return events
async def async_update(self) -> None: """Get the latest data.""" try: events = await get_lessons(self.client) if not self.available: _LOGGER.info("Restored connection with API") self._attr_available = True if events == []: events = await get_lessons( self.client, date_to=date.today() + timedelta(days=7), ) if events == []: self._event = None return except UnauthorizedCertificateException as err: raise ConfigEntryAuthFailed( "The certificate is not authorized, please authorize integration again" ) from err except ClientConnectorError as err: if self.available: _LOGGER.warning( "Connection error - please check your internet connection: %s", err) self._attr_available = False return new_event = min( events, key=lambda d: ( datetime.combine(d["date"], d["time"].to) < datetime.now(), abs( datetime.combine(d["date"], d["time"].to) - datetime.now() ), ), ) self._event = CalendarEvent( start=datetime.combine(new_event["date"], new_event["time"].from_), end=datetime.combine(new_event["date"], new_event["time"].to), summary=new_event["lesson"], location=new_event["room"], description=new_event["teacher"], )
async def async_update(self) -> None: """Get the latest data.""" next_dates = {} for entity in self.entities: if self._hass.data[DOMAIN][SENSOR_PLATFORM][entity].next_date is not None: next_dates[entity] = self._hass.data[DOMAIN][SENSOR_PLATFORM][ entity ].next_date if len(next_dates) > 0: entity_id = min(next_dates.keys(), key=(lambda k: next_dates[k])) start = next_dates[entity_id] end = start + timedelta(days=1) name = self._hass.data[DOMAIN][SENSOR_PLATFORM][entity_id].name self.event = CalendarEvent( summary=name, start=start, end=end, )
def get_event_list(self, start, end, include_all_day: bool) -> list[CalendarEvent]: """Get a list of events. Gets the events from start to end, including or excluding all day events. :param start the earliest start time of events to return :type datetime :param end the latest start time of events to return :type datetime :param include_all_day if true, all day events will be included. :type boolean :returns a list of events, or an empty list :rtype list[CalendarEvent] """ event_list = [] if self._calendar is not None: # ics 0.8 takes datetime not Arrow objects # ar_start = start # ar_end = end ar_start = arrowget(start) ar_end = arrowget(end) for event in self._calendar.timeline.included(ar_start, ar_end): if event.all_day and not include_all_day: continue summary = "" # ics 0.8 uses 'summary' reliably, older versions use 'name' # if hasattr(event, "summary"): # summary = event.summary # elif hasattr(event, "name"): summary = event.name calendar_event = CalendarEvent( summary=summary, start=ParserICS.get_date(event.begin, event.all_day), end=ParserICS.get_date(event.end, event.all_day), location=event.location, description=event.description, ) event_list.append(calendar_event) return event_list
def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" next_waste_pickup_type = None next_waste_pickup_date = None for waste_type, waste_dates in self.coordinator.data.items(): if (waste_dates and (next_waste_pickup_date is None or waste_dates[0] # type: ignore[unreachable] < next_waste_pickup_date) and waste_dates[0] >= dt_util.now().date()): next_waste_pickup_date = waste_dates[0] next_waste_pickup_type = waste_type self._event = None if next_waste_pickup_date is not None and next_waste_pickup_type is not None: self._event = CalendarEvent( summary=WASTE_TYPE_TO_DESCRIPTION[next_waste_pickup_type], start=next_waste_pickup_date, end=next_waste_pickup_date, ) super()._handle_coordinator_update()
def get_current_event(self, include_all_day: bool, now: datetime, days: int) -> Optional[CalendarEvent]: """Get the current or next event. Gets the current event, or the next upcoming event with in the specified number of days, if there is no current event. :param include_all_day if true, all day events will be included. :type boolean :param now the current date and time :type datetime :param days the number of days to check for an upcoming event :type int :returns a CalendarEvent or None """ if self._calendar is None: return None temp_event = None end = now + timedelta(days=days) for event in self._calendar.timeline.included(arrowget(now), arrowget(end)): if event.all_day and not include_all_day: continue if ParserICS.is_event_newer(temp_event, event): temp_event = event if temp_event is None: return None # if hasattr(event, "summary"): # summary = temp_event.summary # elif hasattr(event, "name"): summary = temp_event.name return CalendarEvent( summary=summary, start=ParserICS.get_date(temp_event.begin, temp_event.all_day), end=ParserICS.get_date(temp_event.end, temp_event.all_day), location=temp_event.location, description=temp_event.description, )
async def async_get_events(self, hass: HomeAssistant, start_date: datetime, end_date: datetime) -> list[CalendarEvent]: """Get all events in a specific time frame.""" # Get event list from the current calendar vevent_list = await hass.async_add_executor_job( self.calendar.date_search, start_date, end_date) event_list = [] for event in vevent_list: if not hasattr(event.instance, "vevent"): _LOGGER.warning("Skipped event with missing 'vevent' property") continue vevent = event.instance.vevent if not self.is_matching(vevent, self.search): continue event_list.append( CalendarEvent( summary=vevent.summary.value, start=vevent.dtstart.value, end=self.get_end_date(vevent), location=self.get_attr_value(vevent, "location"), description=self.get_attr_value(vevent, "description"), )) return event_list
async def async_get_events(self, hass, start_date, end_date): """Get the via async.""" results = await self.async_o365_get_events(hass, start_date, end_date) if not results: return vevent_list = list(results) vevent_list.sort(key=attrgetter("start")) event_list = [] for vevent in vevent_list: # data = format_event_data(event, self.calendar.calendar_id) # data["start"] = self.get_hass_date(data["start"], event.is_all_day) # data["end"] = self.get_hass_date(data["end"], event.is_all_day) # event_list.append(data) event = CalendarEvent( self.get_hass_date(vevent.start, vevent.is_all_day), self.get_hass_date(self.get_end_date(vevent), vevent.is_all_day), vevent.subject, clean_html(vevent.body), vevent.location["displayName"], ) event_list.append(event) return event_list
def update(self): """Get the latest data.""" start_of_today = dt.start_of_local_day() start_of_tomorrow = dt.start_of_local_day() + timedelta(days=self.days) # We have to retrieve the results for the whole day as the server # won't return events that have already started results = self.calendar.date_search(start_of_today, start_of_tomorrow) # Create new events for each recurrence of an event that happens today. # For recurring events, some servers return the original event with recurrence rules # and they would not be properly parsed using their original start/end dates. new_events = [] for event in results: if not hasattr(event.instance, "vevent"): _LOGGER.warning("Skipped event with missing 'vevent' property") continue vevent = event.instance.vevent for start_dt in vevent.getrruleset() or []: _start_of_today = start_of_today _start_of_tomorrow = start_of_tomorrow if self.is_all_day(vevent): start_dt = start_dt.date() _start_of_today = _start_of_today.date() _start_of_tomorrow = _start_of_tomorrow.date() if _start_of_today <= start_dt < _start_of_tomorrow: new_event = event.copy() new_vevent = new_event.instance.vevent if hasattr(new_vevent, "dtend"): dur = new_vevent.dtend.value - new_vevent.dtstart.value new_vevent.dtend.value = start_dt + dur new_vevent.dtstart.value = start_dt new_events.append(new_event) elif _start_of_tomorrow <= start_dt: break vevents = [ event.instance.vevent for event in results + new_events if hasattr(event.instance, "vevent") ] # dtstart can be a date or datetime depending if the event lasts a # whole day. Convert everything to datetime to be able to sort it vevents.sort(key=lambda x: self.to_datetime(x.dtstart.value)) vevent = next( (vevent for vevent in vevents if (self.is_matching(vevent, self.search) and ( not self.is_all_day(vevent) or self.include_all_day) and not self.is_over(vevent))), None, ) # If no matching event could be found if vevent is None: _LOGGER.debug( "No matching event found in the %d results for %s", len(vevents), self.calendar.name, ) self.event = None return # Populate the entity attributes with the event values (summary, offset) = extract_offset(vevent.summary.value, OFFSET) self.event = CalendarEvent( summary=summary, start=vevent.dtstart.value, end=self.get_end_date(vevent), location=self.get_attr_value(vevent, "location"), description=self.get_attr_value(vevent, "description"), ) self.offset = offset