From fd97b0fc8d2575c59e4e24585a9d3a7330d5e4e7 Mon Sep 17 00:00:00 2001 From: Alejandro Casanovas Date: Thu, 1 Feb 2024 12:17:53 +0100 Subject: [PATCH] Fix issue #1011 with timezone conversion (from Windows timezones to IANA ones) using the python 3.9 ZoneInfo objects --- O365/connection.py | 22 ++++++++++++++-------- O365/utils/utils.py | 5 ++--- O365/utils/windows_tz.py | 9 ++++----- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/O365/connection.py b/O365/connection.py index 7d65e99c..7786ae74 100644 --- a/O365/connection.py +++ b/O365/connection.py @@ -14,7 +14,7 @@ from requests_oauthlib import OAuth2Session from stringcase import pascalcase, camelcase, snakecase from tzlocal import get_localzone -from zoneinfo import ZoneInfoNotFoundError +from zoneinfo import ZoneInfoNotFoundError, ZoneInfo from .utils import ME_RESOURCE, BaseTokenBackend, FileSystemTokenBackend, Token import datetime as dt @@ -96,13 +96,19 @@ def __init__(self, *, protocol_url=None, api_version=None, self.default_resource = default_resource or ME_RESOURCE self.use_default_casing = True if casing_function is None else False self.casing_function = casing_function or camelcase - if timezone and isinstance(timezone, str): - timezone = dt.timezone(timezone) - try: - self.timezone = timezone or get_localzone() - except ZoneInfoNotFoundError as e: - log.debug('Timezone not provided and the local timezone could not be found. Default to UTC.') - self.timezone = dt.timezone.utc + if timezone: + if isinstance(timezone, str): + # convert string to ZoneInfo + try: + timezone = ZoneInfo(timezone) + except ZoneInfoNotFoundError: + log.debug(f'Timezone {timezone} could not be found. Will default to UTC.') + timezone = ZoneInfo('UTC') + else: + if not isinstance(timezone, ZoneInfo): + raise ValueError(f'The timezone parameter must be either a string or a valid ZoneInfo instance.') + # get_localzone() from tzlocal will try to get the system local timezone and if not will return UTC + self.timezone = timezone or get_localzone() self.max_top_value = 500 # Max $top parameter value # define any keyword that can be different in this protocol diff --git a/O365/utils/utils.py b/O365/utils/utils.py index e26bd1eb..2a88cb4c 100644 --- a/O365/utils/utils.py +++ b/O365/utils/utils.py @@ -1,9 +1,8 @@ import datetime as dt -from zoneinfo import ZoneInfoNotFoundError import logging from collections import OrderedDict from enum import Enum -import zoneinfo +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from dateutil.parser import parse from stringcase import snakecase @@ -431,7 +430,7 @@ def _parse_date_time_time_zone(self, date_time_time_zone, is_all_day=False): local_tz = self.protocol.timezone if isinstance(date_time_time_zone, dict): try: - timezone = zoneinfo.ZoneInfo( + timezone = ZoneInfo( get_iana_tz(date_time_time_zone.get(self._cc('timeZone'), 'UTC'))) except ZoneInfoNotFoundError: timezone = local_tz diff --git a/O365/utils/windows_tz.py b/O365/utils/windows_tz.py index 85e7352e..28b8e60d 100644 --- a/O365/utils/windows_tz.py +++ b/O365/utils/windows_tz.py @@ -2,7 +2,7 @@ Mapping from iana timezones to windows timezones and vice versa """ from datetime import tzinfo -from zoneinfo import ZoneInfoNotFoundError +from zoneinfo import ZoneInfoNotFoundError, ZoneInfo # noinspection SpellCheckingInspection IANA_TO_WIN = { @@ -628,16 +628,15 @@ def get_iana_tz(windows_tz): return timezone -def get_windows_tz(iana_tz): +def get_windows_tz(iana_tz: ZoneInfo) -> str: """ Returns a valid windows TimeZone from a given pytz TimeZone (Iana/Olson Timezones) Note: Windows Timezones are SHIT!... no ... really THEY ARE HOLY FUCKING SHIT!. """ timezone = IANA_TO_WIN.get( - iana_tz.zone if isinstance(iana_tz, tzinfo) else iana_tz) + iana_tz.key if isinstance(iana_tz, tzinfo) else iana_tz) if timezone is None: - raise ZoneInfoNotFoundError( - "Can't find Iana TimeZone " + iana_tz.zone) + raise ZoneInfoNotFoundError(f"Can't find Iana timezone {iana_tz.key}") return timezone