Skip to content

Commit 3900de7

Browse files
author
Alejandro Casanovas
committed
Event set start and end dates won't force timezone to be protocol timezone
1 parent c9e5c70 commit 3900de7

File tree

2 files changed

+33
-22
lines changed

2 files changed

+33
-22
lines changed

O365/calendar.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import calendar
22
import datetime as dt
33
import logging
4+
from zoneinfo import ZoneInfo
45

56
# noinspection PyPep8Naming
67
from bs4 import BeautifulSoup as bs
@@ -551,10 +552,9 @@ def __init__(self, parent, response_status):
551552
self.response_time = None
552553
if self.response_time:
553554
try:
554-
self.response_time = parse(self.response_time).astimezone(
555-
self.protocol.timezone)
555+
self.response_time = parse(self.response_time).astimezone(self.protocol.timezone)
556556
except OverflowError:
557-
log.debug("Couldn't parse event response time: {}".format(self.response_time))
557+
log.debug(f"Couldn't parse event response time: {self.response_time}")
558558
self.response_time = None
559559
else:
560560
self.response_time = None
@@ -1066,8 +1066,9 @@ def start(self, value):
10661066
if value.tzinfo is None:
10671067
# localize datetime
10681068
value = value.replace(tzinfo=self.protocol.timezone)
1069-
elif value.tzinfo != self.protocol.timezone:
1070-
value = value.astimezone(self.protocol.timezone)
1069+
else:
1070+
if not isinstance(value.tzinfo, ZoneInfo):
1071+
raise ValueError('TimeZone data must be set using ZoneInfo objects')
10711072
self.__start = value
10721073
if not self.end:
10731074
self.end = self.__start + dt.timedelta(minutes=30)
@@ -1093,8 +1094,9 @@ def end(self, value):
10931094
if value.tzinfo is None:
10941095
# localize datetime
10951096
value = value.replace(tzinfo=self.protocol.timezone)
1096-
elif value.tzinfo != self.protocol.timezone:
1097-
value = value.astimezone(self.protocol.timezone)
1097+
else:
1098+
if not isinstance(value.tzinfo, ZoneInfo):
1099+
raise ValueError('TimeZone data must be set using ZoneInfo objects')
10981100
self.__end = value
10991101
self._track_changes.add(self._cc('end'))
11001102

@@ -1371,7 +1373,7 @@ def get_occurrences(self, start, end, *, limit=None, query=None, order_by=None,
13711373
:rtype: list[Event] or Pagination
13721374
"""
13731375
if self.event_type != EventType.SeriesMaster:
1374-
# you can only get occurrences if its a seriesMaster
1376+
# you can only get occurrences if it's a seriesMaster
13751377
return []
13761378

13771379
url = self.build_url(
@@ -1397,6 +1399,7 @@ def get_occurrences(self, start, end, *, limit=None, query=None, order_by=None,
13971399
if start.tzinfo is None:
13981400
# if it's a naive datetime, localize the datetime.
13991401
start = start.replace(tzinfo=self.protocol.timezone) # localize datetime into local tz
1402+
# TODO: convert to utc when quering?
14001403
if start.tzinfo != dt.timezone.utc:
14011404
start = start.astimezone(dt.timezone.utc) # transform local datetime to utc
14021405

@@ -1406,6 +1409,7 @@ def get_occurrences(self, start, end, *, limit=None, query=None, order_by=None,
14061409
if end.tzinfo is None:
14071410
# if it's a naive datetime, localize the datetime.
14081411
end = end.replace(tzinfo=self.protocol.timezone) # localize datetime into local tz
1412+
# TODO: convert to utc when quering?
14091413
if end.tzinfo != dt.timezone.utc:
14101414
end = end.astimezone(dt.timezone.utc) # transform local datetime to utc
14111415

@@ -1439,8 +1443,7 @@ def delete(self):
14391443
if self.object_id is None:
14401444
raise RuntimeError('Attempting to delete an unsaved event')
14411445

1442-
url = self.build_url(
1443-
self._endpoints.get('event').format(id=self.object_id))
1446+
url = self.build_url(self._endpoints.get('event').format(id=self.object_id))
14441447

14451448
response = self.con.delete(url)
14461449

@@ -1458,16 +1461,13 @@ def save(self):
14581461
# update event
14591462
if not self._track_changes:
14601463
return True # there's nothing to update
1461-
url = self.build_url(
1462-
self._endpoints.get('event').format(id=self.object_id))
1464+
url = self.build_url(self._endpoints.get('event').format(id=self.object_id))
14631465
method = self.con.patch
14641466
data = self.to_api_data(restrict_keys=self._track_changes)
14651467
else:
14661468
# new event
14671469
if self.calendar_id:
1468-
url = self.build_url(
1469-
self._endpoints.get('event_calendar').format(
1470-
id=self.calendar_id))
1470+
url = self.build_url(self._endpoints.get('event_calendar').format(id=self.calendar_id))
14711471
else:
14721472
url = self.build_url(self._endpoints.get('event_default'))
14731473
method = self.con.post
@@ -1721,7 +1721,7 @@ def delete(self):
17211721

17221722
def get_events(self, limit=25, *, query=None, order_by=None, batch=None,
17231723
download_attachments=False, include_recurring=True):
1724-
""" Get events from the this Calendar
1724+
""" Get events from this Calendar
17251725
17261726
:param int limit: max no. of events to get. Over 999 uses batch.
17271727
:param query: applies a OData filter to the request

O365/utils/utils.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
from collections import OrderedDict
44
from enum import Enum
5+
from typing import Union, Dict
56
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
67

78
from dateutil.parser import parse
@@ -419,10 +420,15 @@ def _cc(self, dict_key):
419420
""" Alias for protocol.convert_case """
420421
return self.protocol.convert_case(dict_key)
421422

422-
def _parse_date_time_time_zone(self, date_time_time_zone, is_all_day=False):
423-
""" Parses and convert to protocol timezone a dateTimeTimeZone resource
423+
def _parse_date_time_time_zone(self,
424+
date_time_time_zone: Union[dict, str],
425+
is_all_day: bool = False) -> Union[dt.datetime, None]:
426+
"""
427+
Parses and convert to protocol timezone a dateTimeTimeZone resource
424428
This resource is a dict with a date time and a windows timezone
425429
This is a common structure on Microsoft apis so it's included here.
430+
431+
Returns a dt.datetime with the datime converted to protocol timezone
426432
"""
427433
if date_time_time_zone is None:
428434
return None
@@ -432,12 +438,13 @@ def _parse_date_time_time_zone(self, date_time_time_zone, is_all_day=False):
432438
try:
433439
timezone = get_iana_tz(date_time_time_zone.get(self._cc('timeZone'), 'UTC'))
434440
except ZoneInfoNotFoundError:
441+
log.debug('TimeZone not found. Using protocol timezone instead.')
435442
timezone = local_tz
436443
date_time = date_time_time_zone.get(self._cc('dateTime'), None)
437444
try:
438445
date_time = parse(date_time).replace(tzinfo=timezone) if date_time else None
439446
except OverflowError as e:
440-
log.debug('Could not parse dateTimeTimeZone: {}. Error: {}'.format(date_time_time_zone, str(e)))
447+
log.debug(f'Could not parse dateTimeTimeZone: {date_time_time_zone}. Error: {e}')
441448
date_time = None
442449

443450
if date_time and timezone != local_tz:
@@ -450,14 +457,16 @@ def _parse_date_time_time_zone(self, date_time_time_zone, is_all_day=False):
450457
try:
451458
date_time = parse(date_time_time_zone).replace(tzinfo=local_tz) if date_time_time_zone else None
452459
except Exception as e:
453-
log.debug('Could not parse dateTimeTimeZone: {}. Error: {}'.format(date_time_time_zone, str(e)))
460+
log.debug(f'Could not parse dateTimeTimeZone: {date_time_time_zone}. Error: {e}')
454461
date_time = None
455462

456463
return date_time
457464

458-
def _build_date_time_time_zone(self, date_time):
459-
""" Converts a datetime to a dateTimeTimeZone resource """
465+
def _build_date_time_time_zone(self, date_time: dt.datetime) -> Dict[str, str]:
466+
""" Converts a datetime to a dateTimeTimeZone resource Dict[datetime, windows timezone] """
460467
timezone = None
468+
469+
# extract timezone ZoneInfo from provided datetime
461470
if date_time.tzinfo is not None:
462471
if isinstance(date_time.tzinfo, ZoneInfo):
463472
timezone = date_time.tzinfo
@@ -471,6 +480,7 @@ def _build_date_time_time_zone(self, date_time):
471480
else:
472481
raise ValueError("Unexpected tzinfo class. Can't convert to ZoneInfo.")
473482

483+
# convert ZoneInfo timezone (IANA) to a string windows timezone
474484
timezone = get_windows_tz(timezone or self.protocol.timezone)
475485

476486
return {
@@ -960,6 +970,7 @@ def _parse_filter_word(self, word):
960970
if word.tzinfo is None:
961971
# if it's a naive datetime, localize the datetime.
962972
word = word.replace(tzinfo=self.protocol.timezone) # localize datetime into local tz
973+
# TODO: remove UTC CONVERSION: not affected when quering calendar events
963974
if word.tzinfo != dt.timezone.utc:
964975
word = word.astimezone(
965976
dt.timezone.utc) # transform local datetime to utc

0 commit comments

Comments
 (0)