Module exchangelib.ewsdatetime

Expand source code
import datetime
import logging

try:
    import zoneinfo
except ImportError:
    from backports import zoneinfo

import tzlocal

from .errors import InvalidTypeError, NaiveDateTimeNotAllowed, UnknownTimeZone
from .winzone import IANA_TO_MS_TIMEZONE_MAP, MS_TIMEZONE_TO_IANA_MAP

log = logging.getLogger(__name__)


class EWSDate(datetime.date):
    """Extends the normal date implementation to satisfy EWS."""

    __slots__ = "_year", "_month", "_day", "_hashcode"

    def ewsformat(self):
        """ISO 8601 format to satisfy xs:date as interpreted by EWS. Example: 2009-01-15."""
        return self.isoformat()

    def __add__(self, other):
        dt = super().__add__(other)
        if isinstance(dt, self.__class__):
            return dt
        return self.from_date(dt)  # We want to return EWSDate objects

    def __iadd__(self, other):
        return self + other

    def __sub__(self, other):
        dt = super().__sub__(other)
        if isinstance(dt, datetime.timedelta):
            return dt
        if isinstance(dt, self.__class__):
            return dt
        return self.from_date(dt)  # We want to return EWSDate objects

    def __isub__(self, other):
        return self - other

    @classmethod
    def fromordinal(cls, n):
        dt = super().fromordinal(n)
        if isinstance(dt, cls):
            return dt
        return cls.from_date(dt)  # We want to return EWSDate objects

    @classmethod
    def from_date(cls, d):
        if type(d) is not datetime.date:
            raise InvalidTypeError("d", d, datetime.date)
        return cls(d.year, d.month, d.day)

    @classmethod
    def from_string(cls, date_string):
        # Sometimes, we'll receive a date string with time zone information. Not very useful.
        if date_string.endswith("Z"):
            date_fmt = "%Y-%m-%dZ"
        elif ":" in date_string:
            if "+" in date_string:
                date_fmt = "%Y-%m-%d+%H:%M"
            else:
                date_fmt = "%Y-%m-%d-%H:%M"
        else:
            date_fmt = "%Y-%m-%d"
        d = datetime.datetime.strptime(date_string, date_fmt).date()
        if isinstance(d, cls):
            return d
        return cls.from_date(d)  # We want to return EWSDate objects


class EWSDateTime(datetime.datetime):
    """Extends the normal datetime implementation to satisfy EWS."""

    __slots__ = "_year", "_month", "_day", "_hour", "_minute", "_second", "_microsecond", "_tzinfo", "_hashcode"

    def __new__(cls, *args, **kwargs):
        # pylint: disable=arguments-differ

        if len(args) == 8:
            tzinfo = args[7]
        else:
            tzinfo = kwargs.get("tzinfo")
        if isinstance(tzinfo, zoneinfo.ZoneInfo):
            # Don't allow pytz or dateutil timezones here. They are not safe to use as direct input for datetime()
            tzinfo = EWSTimeZone.from_timezone(tzinfo)
        if not isinstance(tzinfo, (EWSTimeZone, type(None))):
            raise InvalidTypeError("tzinfo", tzinfo, EWSTimeZone)
        if len(args) == 8:
            args = args[:7] + (tzinfo,)
        else:
            kwargs["tzinfo"] = tzinfo
        return super().__new__(cls, *args, **kwargs)

    def ewsformat(self):
        """ISO 8601 format to satisfy xs:datetime as interpreted by EWS. Examples:
        * 2009-01-15T13:45:56Z
        * 2009-01-15T13:45:56+01:00
        """
        if not self.tzinfo:
            raise ValueError(f"{self!r} must be timezone-aware")
        if self.tzinfo.key == "UTC":
            if self.microsecond:
                return self.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
            return self.strftime("%Y-%m-%dT%H:%M:%SZ")
        return self.isoformat()

    @classmethod
    def from_datetime(cls, d):
        if type(d) is not datetime.datetime:
            raise InvalidTypeError("d", d, datetime.datetime)
        if d.tzinfo is None:
            tz = None
        elif isinstance(d.tzinfo, EWSTimeZone):
            tz = d.tzinfo
        else:
            tz = EWSTimeZone.from_timezone(d.tzinfo)
        return cls(d.year, d.month, d.day, d.hour, d.minute, d.second, d.microsecond, tzinfo=tz)

    def astimezone(self, tz=None):
        if tz is None:
            tz = EWSTimeZone.localzone()
        t = super().astimezone(tz=tz).replace(tzinfo=tz)
        if isinstance(t, self.__class__):
            return t
        return self.from_datetime(t)  # We want to return EWSDateTime objects

    @classmethod
    def fromisoformat(cls, date_string):
        return cls.from_string(date_string)

    def __add__(self, other):
        t = super().__add__(other)
        if isinstance(t, self.__class__):
            return t
        return self.from_datetime(t)  # We want to return EWSDateTime objects

    def __iadd__(self, other):
        return self + other

    def __sub__(self, other):
        t = super().__sub__(other)
        if isinstance(t, datetime.timedelta):
            return t
        if isinstance(t, self.__class__):
            return t
        return self.from_datetime(t)  # We want to return EWSDateTime objects

    def __isub__(self, other):
        return self - other

    @classmethod
    def from_string(cls, date_string):
        # Parses several common datetime formats and returns time zone aware EWSDateTime objects
        if date_string.endswith("Z"):
            # UTC datetime
            return super().strptime(date_string, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC)
        if len(date_string) == 19:
            # This is probably a naive datetime. Don't allow this, but signal caller with an appropriate error
            local_dt = super().strptime(date_string, "%Y-%m-%dT%H:%M:%S")
            raise NaiveDateTimeNotAllowed(local_dt)
        # This is probably a datetime value with time zone information. This comes in the form '+/-HH:MM'.
        aware_dt = datetime.datetime.fromisoformat(date_string).astimezone(UTC).replace(tzinfo=UTC)
        if isinstance(aware_dt, cls):
            return aware_dt
        return cls.from_datetime(aware_dt)

    @classmethod
    def fromtimestamp(cls, t, tz=None):
        dt = super().fromtimestamp(t, tz=tz)
        if isinstance(dt, cls):
            return dt
        return cls.from_datetime(dt)  # We want to return EWSDateTime objects

    @classmethod
    def utcfromtimestamp(cls, t):
        dt = super().utcfromtimestamp(t)
        if isinstance(dt, cls):
            return dt
        return cls.from_datetime(dt)  # We want to return EWSDateTime objects

    @classmethod
    def now(cls, tz=None):
        t = super().now(tz=tz)
        if isinstance(t, cls):
            return t
        return cls.from_datetime(t)  # We want to return EWSDateTime objects

    @classmethod
    def utcnow(cls):
        t = super().utcnow()
        if isinstance(t, cls):
            return t
        return cls.from_datetime(t)  # We want to return EWSDateTime objects

    def date(self):
        d = super().date()
        if isinstance(d, EWSDate):
            return d
        return EWSDate.from_date(d)  # We want to return EWSDate objects


class EWSTimeZone(zoneinfo.ZoneInfo):
    """Represents a time zone as expected by the EWS TimezoneContext / TimezoneDefinition XML element, and returned by
    services.GetServerTimeZones.
    """

    IANA_TO_MS_MAP = IANA_TO_MS_TIMEZONE_MAP
    MS_TO_IANA_MAP = MS_TIMEZONE_TO_IANA_MAP

    def __new__(cls, *args, **kwargs):
        try:
            instance = super().__new__(cls, *args, **kwargs)
        except zoneinfo.ZoneInfoNotFoundError as e:
            raise UnknownTimeZone(e.args[0])
        try:
            instance.ms_id = cls.IANA_TO_MS_MAP[instance.key][0]
        except KeyError:
            raise UnknownTimeZone(f"No Windows timezone name found for timezone {instance.key!r}")

        # We don't need the Windows long-format time zone name in long format. It's used in time zone XML elements, but
        # EWS happily accepts empty strings. For a full list of time zones supported by the target server, including
        # long-format names, see output of services.GetServerTimeZones(account.protocol).call()
        instance.ms_name = ""
        return instance

    def __hash__(self):
        return hash(self.key)

    def __eq__(self, other):
        # Microsoft time zones are less granular than IANA, so an EWSTimeZone created from 'Europe/Copenhagen' may
        # return from the server as 'Europe/Copenhagen'. We're catering for Microsoft here, so base equality on the
        # Microsoft time zone ID.
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.ms_id == other.ms_id

    @classmethod
    def from_ms_id(cls, ms_id):
        # Create a time zone instance from a Microsoft time zone ID. This is lossy because there is not a 1:1
        # translation from MS time zone ID to IANA time zone.
        try:
            return cls(cls.MS_TO_IANA_MAP[ms_id])
        except KeyError:
            if "/" in ms_id:
                # EWS sometimes returns an ID that has a region/location format, e.g. 'Europe/Copenhagen'. Try the
                # string unaltered.
                return cls(ms_id)
            raise UnknownTimeZone(f"Windows timezone ID {ms_id!r} is unknown by CLDR")

    @classmethod
    def from_pytz(cls, tz):
        return cls(tz.zone)

    @classmethod
    def from_datetime(cls, tz):
        """Convert from a standard library `datetime.timezone` instance."""
        return cls(tz.tzname(None))

    @classmethod
    def from_dateutil(cls, tz):
        # Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
        # don't contain enough information to reliably match them with a CLDR time zone.
        if hasattr(tz, "_filename"):
            key = "/".join(tz._filename.split("/")[-2:])
            return cls(key)
        return cls(tz.tzname(datetime.datetime.now()))

    @classmethod
    def from_zoneinfo(cls, tz):
        return cls(tz.key)

    @classmethod
    def from_timezone(cls, tz):
        # Support multiple tzinfo implementations. We could use isinstance(), but then we'd have to have pytz
        # and dateutil as dependencies for this package.
        tz_module = tz.__class__.__module__.split(".")[0]
        try:
            return {
                cls.__module__.split(".")[0]: lambda z: z,
                "backports": cls.from_zoneinfo,
                "datetime": cls.from_datetime,
                "dateutil": cls.from_dateutil,
                "pytz": cls.from_pytz,
                "zoneinfo": cls.from_zoneinfo,
                "pytz_deprecation_shim": lambda z: cls.from_timezone(z.unwrap_shim()),
            }[tz_module](tz)
        except KeyError:
            raise TypeError(f"Unsupported tzinfo type: {tz!r}")

    @classmethod
    def localzone(cls):
        try:
            tz = tzlocal.get_localzone()
        except zoneinfo.ZoneInfoNotFoundError:
            # Older versions of tzlocal will raise a pytz exception. Let's not depend on pytz just for that.
            raise UnknownTimeZone("Failed to guess local timezone")
        # Handle both old and new versions of tzlocal that may return pytz or zoneinfo objects, respectively
        return cls.from_timezone(tz)

    def fromutc(self, dt):
        t = super().fromutc(dt)
        if isinstance(t, EWSDateTime):
            return t
        return EWSDateTime.from_datetime(t)  # We want to return EWSDateTime objects


UTC = EWSTimeZone("UTC")
UTC_NOW = lambda: EWSDateTime.now(tz=UTC)  # noqa: E731

Functions

def UTC_NOW()
Expand source code
UTC_NOW = lambda: EWSDateTime.now(tz=UTC)  # noqa: E731

Classes

class EWSDate (...)

Extends the normal date implementation to satisfy EWS.

Expand source code
class EWSDate(datetime.date):
    """Extends the normal date implementation to satisfy EWS."""

    __slots__ = "_year", "_month", "_day", "_hashcode"

    def ewsformat(self):
        """ISO 8601 format to satisfy xs:date as interpreted by EWS. Example: 2009-01-15."""
        return self.isoformat()

    def __add__(self, other):
        dt = super().__add__(other)
        if isinstance(dt, self.__class__):
            return dt
        return self.from_date(dt)  # We want to return EWSDate objects

    def __iadd__(self, other):
        return self + other

    def __sub__(self, other):
        dt = super().__sub__(other)
        if isinstance(dt, datetime.timedelta):
            return dt
        if isinstance(dt, self.__class__):
            return dt
        return self.from_date(dt)  # We want to return EWSDate objects

    def __isub__(self, other):
        return self - other

    @classmethod
    def fromordinal(cls, n):
        dt = super().fromordinal(n)
        if isinstance(dt, cls):
            return dt
        return cls.from_date(dt)  # We want to return EWSDate objects

    @classmethod
    def from_date(cls, d):
        if type(d) is not datetime.date:
            raise InvalidTypeError("d", d, datetime.date)
        return cls(d.year, d.month, d.day)

    @classmethod
    def from_string(cls, date_string):
        # Sometimes, we'll receive a date string with time zone information. Not very useful.
        if date_string.endswith("Z"):
            date_fmt = "%Y-%m-%dZ"
        elif ":" in date_string:
            if "+" in date_string:
                date_fmt = "%Y-%m-%d+%H:%M"
            else:
                date_fmt = "%Y-%m-%d-%H:%M"
        else:
            date_fmt = "%Y-%m-%d"
        d = datetime.datetime.strptime(date_string, date_fmt).date()
        if isinstance(d, cls):
            return d
        return cls.from_date(d)  # We want to return EWSDate objects

Ancestors

  • datetime.date

Static methods

def from_date(d)
Expand source code
@classmethod
def from_date(cls, d):
    if type(d) is not datetime.date:
        raise InvalidTypeError("d", d, datetime.date)
    return cls(d.year, d.month, d.day)
def from_string(date_string)
Expand source code
@classmethod
def from_string(cls, date_string):
    # Sometimes, we'll receive a date string with time zone information. Not very useful.
    if date_string.endswith("Z"):
        date_fmt = "%Y-%m-%dZ"
    elif ":" in date_string:
        if "+" in date_string:
            date_fmt = "%Y-%m-%d+%H:%M"
        else:
            date_fmt = "%Y-%m-%d-%H:%M"
    else:
        date_fmt = "%Y-%m-%d"
    d = datetime.datetime.strptime(date_string, date_fmt).date()
    if isinstance(d, cls):
        return d
    return cls.from_date(d)  # We want to return EWSDate objects
def fromordinal(n)

int -> date corresponding to a proleptic Gregorian ordinal.

Expand source code
@classmethod
def fromordinal(cls, n):
    dt = super().fromordinal(n)
    if isinstance(dt, cls):
        return dt
    return cls.from_date(dt)  # We want to return EWSDate objects

Methods

def ewsformat(self)

ISO 8601 format to satisfy xs:date as interpreted by EWS. Example: 2009-01-15.

Expand source code
def ewsformat(self):
    """ISO 8601 format to satisfy xs:date as interpreted by EWS. Example: 2009-01-15."""
    return self.isoformat()
class EWSDateTime (*args, **kwargs)

Extends the normal datetime implementation to satisfy EWS.

Expand source code
class EWSDateTime(datetime.datetime):
    """Extends the normal datetime implementation to satisfy EWS."""

    __slots__ = "_year", "_month", "_day", "_hour", "_minute", "_second", "_microsecond", "_tzinfo", "_hashcode"

    def __new__(cls, *args, **kwargs):
        # pylint: disable=arguments-differ

        if len(args) == 8:
            tzinfo = args[7]
        else:
            tzinfo = kwargs.get("tzinfo")
        if isinstance(tzinfo, zoneinfo.ZoneInfo):
            # Don't allow pytz or dateutil timezones here. They are not safe to use as direct input for datetime()
            tzinfo = EWSTimeZone.from_timezone(tzinfo)
        if not isinstance(tzinfo, (EWSTimeZone, type(None))):
            raise InvalidTypeError("tzinfo", tzinfo, EWSTimeZone)
        if len(args) == 8:
            args = args[:7] + (tzinfo,)
        else:
            kwargs["tzinfo"] = tzinfo
        return super().__new__(cls, *args, **kwargs)

    def ewsformat(self):
        """ISO 8601 format to satisfy xs:datetime as interpreted by EWS. Examples:
        * 2009-01-15T13:45:56Z
        * 2009-01-15T13:45:56+01:00
        """
        if not self.tzinfo:
            raise ValueError(f"{self!r} must be timezone-aware")
        if self.tzinfo.key == "UTC":
            if self.microsecond:
                return self.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
            return self.strftime("%Y-%m-%dT%H:%M:%SZ")
        return self.isoformat()

    @classmethod
    def from_datetime(cls, d):
        if type(d) is not datetime.datetime:
            raise InvalidTypeError("d", d, datetime.datetime)
        if d.tzinfo is None:
            tz = None
        elif isinstance(d.tzinfo, EWSTimeZone):
            tz = d.tzinfo
        else:
            tz = EWSTimeZone.from_timezone(d.tzinfo)
        return cls(d.year, d.month, d.day, d.hour, d.minute, d.second, d.microsecond, tzinfo=tz)

    def astimezone(self, tz=None):
        if tz is None:
            tz = EWSTimeZone.localzone()
        t = super().astimezone(tz=tz).replace(tzinfo=tz)
        if isinstance(t, self.__class__):
            return t
        return self.from_datetime(t)  # We want to return EWSDateTime objects

    @classmethod
    def fromisoformat(cls, date_string):
        return cls.from_string(date_string)

    def __add__(self, other):
        t = super().__add__(other)
        if isinstance(t, self.__class__):
            return t
        return self.from_datetime(t)  # We want to return EWSDateTime objects

    def __iadd__(self, other):
        return self + other

    def __sub__(self, other):
        t = super().__sub__(other)
        if isinstance(t, datetime.timedelta):
            return t
        if isinstance(t, self.__class__):
            return t
        return self.from_datetime(t)  # We want to return EWSDateTime objects

    def __isub__(self, other):
        return self - other

    @classmethod
    def from_string(cls, date_string):
        # Parses several common datetime formats and returns time zone aware EWSDateTime objects
        if date_string.endswith("Z"):
            # UTC datetime
            return super().strptime(date_string, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC)
        if len(date_string) == 19:
            # This is probably a naive datetime. Don't allow this, but signal caller with an appropriate error
            local_dt = super().strptime(date_string, "%Y-%m-%dT%H:%M:%S")
            raise NaiveDateTimeNotAllowed(local_dt)
        # This is probably a datetime value with time zone information. This comes in the form '+/-HH:MM'.
        aware_dt = datetime.datetime.fromisoformat(date_string).astimezone(UTC).replace(tzinfo=UTC)
        if isinstance(aware_dt, cls):
            return aware_dt
        return cls.from_datetime(aware_dt)

    @classmethod
    def fromtimestamp(cls, t, tz=None):
        dt = super().fromtimestamp(t, tz=tz)
        if isinstance(dt, cls):
            return dt
        return cls.from_datetime(dt)  # We want to return EWSDateTime objects

    @classmethod
    def utcfromtimestamp(cls, t):
        dt = super().utcfromtimestamp(t)
        if isinstance(dt, cls):
            return dt
        return cls.from_datetime(dt)  # We want to return EWSDateTime objects

    @classmethod
    def now(cls, tz=None):
        t = super().now(tz=tz)
        if isinstance(t, cls):
            return t
        return cls.from_datetime(t)  # We want to return EWSDateTime objects

    @classmethod
    def utcnow(cls):
        t = super().utcnow()
        if isinstance(t, cls):
            return t
        return cls.from_datetime(t)  # We want to return EWSDateTime objects

    def date(self):
        d = super().date()
        if isinstance(d, EWSDate):
            return d
        return EWSDate.from_date(d)  # We want to return EWSDate objects

Ancestors

  • datetime.datetime
  • datetime.date

Static methods

def from_datetime(d)
Expand source code
@classmethod
def from_datetime(cls, d):
    if type(d) is not datetime.datetime:
        raise InvalidTypeError("d", d, datetime.datetime)
    if d.tzinfo is None:
        tz = None
    elif isinstance(d.tzinfo, EWSTimeZone):
        tz = d.tzinfo
    else:
        tz = EWSTimeZone.from_timezone(d.tzinfo)
    return cls(d.year, d.month, d.day, d.hour, d.minute, d.second, d.microsecond, tzinfo=tz)
def from_string(date_string)
Expand source code
@classmethod
def from_string(cls, date_string):
    # Parses several common datetime formats and returns time zone aware EWSDateTime objects
    if date_string.endswith("Z"):
        # UTC datetime
        return super().strptime(date_string, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC)
    if len(date_string) == 19:
        # This is probably a naive datetime. Don't allow this, but signal caller with an appropriate error
        local_dt = super().strptime(date_string, "%Y-%m-%dT%H:%M:%S")
        raise NaiveDateTimeNotAllowed(local_dt)
    # This is probably a datetime value with time zone information. This comes in the form '+/-HH:MM'.
    aware_dt = datetime.datetime.fromisoformat(date_string).astimezone(UTC).replace(tzinfo=UTC)
    if isinstance(aware_dt, cls):
        return aware_dt
    return cls.from_datetime(aware_dt)
def fromisoformat(date_string)

string -> datetime from datetime.isoformat() output

Expand source code
@classmethod
def fromisoformat(cls, date_string):
    return cls.from_string(date_string)
def fromtimestamp(t, tz=None)

timestamp[, tz] -> tz's local time from POSIX timestamp.

Expand source code
@classmethod
def fromtimestamp(cls, t, tz=None):
    dt = super().fromtimestamp(t, tz=tz)
    if isinstance(dt, cls):
        return dt
    return cls.from_datetime(dt)  # We want to return EWSDateTime objects
def now(tz=None)

Returns new datetime object representing current time local to tz.

tz Timezone object.

If no tz is specified, uses local timezone.

Expand source code
@classmethod
def now(cls, tz=None):
    t = super().now(tz=tz)
    if isinstance(t, cls):
        return t
    return cls.from_datetime(t)  # We want to return EWSDateTime objects
def utcfromtimestamp(t)

Construct a naive UTC datetime from a POSIX timestamp.

Expand source code
@classmethod
def utcfromtimestamp(cls, t):
    dt = super().utcfromtimestamp(t)
    if isinstance(dt, cls):
        return dt
    return cls.from_datetime(dt)  # We want to return EWSDateTime objects
def utcnow()

Return a new datetime representing UTC day and time.

Expand source code
@classmethod
def utcnow(cls):
    t = super().utcnow()
    if isinstance(t, cls):
        return t
    return cls.from_datetime(t)  # We want to return EWSDateTime objects

Methods

def astimezone(self, tz=None)

tz -> convert to local time in new timezone tz

Expand source code
def astimezone(self, tz=None):
    if tz is None:
        tz = EWSTimeZone.localzone()
    t = super().astimezone(tz=tz).replace(tzinfo=tz)
    if isinstance(t, self.__class__):
        return t
    return self.from_datetime(t)  # We want to return EWSDateTime objects
def date(self)

Return date object with same year, month and day.

Expand source code
def date(self):
    d = super().date()
    if isinstance(d, EWSDate):
        return d
    return EWSDate.from_date(d)  # We want to return EWSDate objects
def ewsformat(self)

ISO 8601 format to satisfy xs:datetime as interpreted by EWS. Examples: * 2009-01-15T13:45:56Z * 2009-01-15T13:45:56+01:00

Expand source code
def ewsformat(self):
    """ISO 8601 format to satisfy xs:datetime as interpreted by EWS. Examples:
    * 2009-01-15T13:45:56Z
    * 2009-01-15T13:45:56+01:00
    """
    if not self.tzinfo:
        raise ValueError(f"{self!r} must be timezone-aware")
    if self.tzinfo.key == "UTC":
        if self.microsecond:
            return self.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
        return self.strftime("%Y-%m-%dT%H:%M:%SZ")
    return self.isoformat()
class EWSTimeZone (*args, **kwargs)

Represents a time zone as expected by the EWS TimezoneContext / TimezoneDefinition XML element, and returned by services.GetServerTimeZones.

Expand source code
class EWSTimeZone(zoneinfo.ZoneInfo):
    """Represents a time zone as expected by the EWS TimezoneContext / TimezoneDefinition XML element, and returned by
    services.GetServerTimeZones.
    """

    IANA_TO_MS_MAP = IANA_TO_MS_TIMEZONE_MAP
    MS_TO_IANA_MAP = MS_TIMEZONE_TO_IANA_MAP

    def __new__(cls, *args, **kwargs):
        try:
            instance = super().__new__(cls, *args, **kwargs)
        except zoneinfo.ZoneInfoNotFoundError as e:
            raise UnknownTimeZone(e.args[0])
        try:
            instance.ms_id = cls.IANA_TO_MS_MAP[instance.key][0]
        except KeyError:
            raise UnknownTimeZone(f"No Windows timezone name found for timezone {instance.key!r}")

        # We don't need the Windows long-format time zone name in long format. It's used in time zone XML elements, but
        # EWS happily accepts empty strings. For a full list of time zones supported by the target server, including
        # long-format names, see output of services.GetServerTimeZones(account.protocol).call()
        instance.ms_name = ""
        return instance

    def __hash__(self):
        return hash(self.key)

    def __eq__(self, other):
        # Microsoft time zones are less granular than IANA, so an EWSTimeZone created from 'Europe/Copenhagen' may
        # return from the server as 'Europe/Copenhagen'. We're catering for Microsoft here, so base equality on the
        # Microsoft time zone ID.
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.ms_id == other.ms_id

    @classmethod
    def from_ms_id(cls, ms_id):
        # Create a time zone instance from a Microsoft time zone ID. This is lossy because there is not a 1:1
        # translation from MS time zone ID to IANA time zone.
        try:
            return cls(cls.MS_TO_IANA_MAP[ms_id])
        except KeyError:
            if "/" in ms_id:
                # EWS sometimes returns an ID that has a region/location format, e.g. 'Europe/Copenhagen'. Try the
                # string unaltered.
                return cls(ms_id)
            raise UnknownTimeZone(f"Windows timezone ID {ms_id!r} is unknown by CLDR")

    @classmethod
    def from_pytz(cls, tz):
        return cls(tz.zone)

    @classmethod
    def from_datetime(cls, tz):
        """Convert from a standard library `datetime.timezone` instance."""
        return cls(tz.tzname(None))

    @classmethod
    def from_dateutil(cls, tz):
        # Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
        # don't contain enough information to reliably match them with a CLDR time zone.
        if hasattr(tz, "_filename"):
            key = "/".join(tz._filename.split("/")[-2:])
            return cls(key)
        return cls(tz.tzname(datetime.datetime.now()))

    @classmethod
    def from_zoneinfo(cls, tz):
        return cls(tz.key)

    @classmethod
    def from_timezone(cls, tz):
        # Support multiple tzinfo implementations. We could use isinstance(), but then we'd have to have pytz
        # and dateutil as dependencies for this package.
        tz_module = tz.__class__.__module__.split(".")[0]
        try:
            return {
                cls.__module__.split(".")[0]: lambda z: z,
                "backports": cls.from_zoneinfo,
                "datetime": cls.from_datetime,
                "dateutil": cls.from_dateutil,
                "pytz": cls.from_pytz,
                "zoneinfo": cls.from_zoneinfo,
                "pytz_deprecation_shim": lambda z: cls.from_timezone(z.unwrap_shim()),
            }[tz_module](tz)
        except KeyError:
            raise TypeError(f"Unsupported tzinfo type: {tz!r}")

    @classmethod
    def localzone(cls):
        try:
            tz = tzlocal.get_localzone()
        except zoneinfo.ZoneInfoNotFoundError:
            # Older versions of tzlocal will raise a pytz exception. Let's not depend on pytz just for that.
            raise UnknownTimeZone("Failed to guess local timezone")
        # Handle both old and new versions of tzlocal that may return pytz or zoneinfo objects, respectively
        return cls.from_timezone(tz)

    def fromutc(self, dt):
        t = super().fromutc(dt)
        if isinstance(t, EWSDateTime):
            return t
        return EWSDateTime.from_datetime(t)  # We want to return EWSDateTime objects

Ancestors

  • zoneinfo.ZoneInfo
  • datetime.tzinfo

Class variables

var IANA_TO_MS_MAP
var MS_TO_IANA_MAP

Static methods

def from_datetime(tz)

Convert from a standard library datetime.timezone instance.

Expand source code
@classmethod
def from_datetime(cls, tz):
    """Convert from a standard library `datetime.timezone` instance."""
    return cls(tz.tzname(None))
def from_dateutil(tz)
Expand source code
@classmethod
def from_dateutil(cls, tz):
    # Objects returned by dateutil.tz.tzlocal() and dateutil.tz.gettz() are not supported. They
    # don't contain enough information to reliably match them with a CLDR time zone.
    if hasattr(tz, "_filename"):
        key = "/".join(tz._filename.split("/")[-2:])
        return cls(key)
    return cls(tz.tzname(datetime.datetime.now()))
def from_ms_id(ms_id)
Expand source code
@classmethod
def from_ms_id(cls, ms_id):
    # Create a time zone instance from a Microsoft time zone ID. This is lossy because there is not a 1:1
    # translation from MS time zone ID to IANA time zone.
    try:
        return cls(cls.MS_TO_IANA_MAP[ms_id])
    except KeyError:
        if "/" in ms_id:
            # EWS sometimes returns an ID that has a region/location format, e.g. 'Europe/Copenhagen'. Try the
            # string unaltered.
            return cls(ms_id)
        raise UnknownTimeZone(f"Windows timezone ID {ms_id!r} is unknown by CLDR")
def from_pytz(tz)
Expand source code
@classmethod
def from_pytz(cls, tz):
    return cls(tz.zone)
def from_timezone(tz)
Expand source code
@classmethod
def from_timezone(cls, tz):
    # Support multiple tzinfo implementations. We could use isinstance(), but then we'd have to have pytz
    # and dateutil as dependencies for this package.
    tz_module = tz.__class__.__module__.split(".")[0]
    try:
        return {
            cls.__module__.split(".")[0]: lambda z: z,
            "backports": cls.from_zoneinfo,
            "datetime": cls.from_datetime,
            "dateutil": cls.from_dateutil,
            "pytz": cls.from_pytz,
            "zoneinfo": cls.from_zoneinfo,
            "pytz_deprecation_shim": lambda z: cls.from_timezone(z.unwrap_shim()),
        }[tz_module](tz)
    except KeyError:
        raise TypeError(f"Unsupported tzinfo type: {tz!r}")
def from_zoneinfo(tz)
Expand source code
@classmethod
def from_zoneinfo(cls, tz):
    return cls(tz.key)
def localzone()
Expand source code
@classmethod
def localzone(cls):
    try:
        tz = tzlocal.get_localzone()
    except zoneinfo.ZoneInfoNotFoundError:
        # Older versions of tzlocal will raise a pytz exception. Let's not depend on pytz just for that.
        raise UnknownTimeZone("Failed to guess local timezone")
    # Handle both old and new versions of tzlocal that may return pytz or zoneinfo objects, respectively
    return cls.from_timezone(tz)

Methods

def fromutc(self, dt)

Given a datetime with local time in UTC, retrieve an adjusted datetime in local time.

Expand source code
def fromutc(self, dt):
    t = super().fromutc(dt)
    if isinstance(t, EWSDateTime):
        return t
    return EWSDateTime.from_datetime(t)  # We want to return EWSDateTime objects