Source code for icalendar.prop.dt.utc_offset

"""UTC-Offset property type from :rfc:`5545`."""

import re
from datetime import timedelta
from typing import Any, ClassVar
from xml.etree.ElementTree import Element, SubElement

from icalendar.compatibility import Self
from icalendar.error import JCalParsingError
from icalendar.parser import Parameters
from icalendar.parser.xcal.base import XCalParser
from icalendar.parser.xcal.match import XCalRegexMatcher
from icalendar.parser.xcal.wrapper import from_xcal_wrapper

UTC_OFFSET_JCAL_REGEX = re.compile(
    r"^(?P<sign>[+-])?(?P<hours>\d\d):(?P<minutes>\d\d)(?::(?P<seconds>\d\d))?\Z"
)

XCAL_UTC_OFFSET_REGEX = XCalRegexMatcher(
    UTC_OFFSET_JCAL_REGEX,
    "Expected utc-offset format https://datatracker.ietf.org/doc/html/rfc6321#section-3.6.14",
)


[docs] class vUTCOffset: """UTC Offset Value Name: UTC-OFFSET Purpose: This value type is used to identify properties that contain an offset from UTC to local time. Format Definition: This value type is defined by the following notation: .. code-block:: text utc-offset = time-numzone time-numzone = ("+" / "-") time-hour time-minute [time-second] Description: The PLUS SIGN character MUST be specified for positive UTC offsets (i.e., ahead of UTC). The HYPHEN-MINUS character MUST be specified for negative UTC offsets (i.e., behind of UTC). The value of "-0000" and "-000000" are not allowed. The time-second, if present, MUST NOT be 60; if absent, it defaults to zero. Example: The following UTC offsets are given for standard time for New York (five hours behind UTC) and Geneva (one hour ahead of UTC): .. code-block:: ics -0500 +0100 .. code-block:: pycon >>> from icalendar.prop import vUTCOffset >>> utc_offset = vUTCOffset.from_ical('-0500') >>> utc_offset datetime.timedelta(days=-1, seconds=68400) >>> utc_offset = vUTCOffset.from_ical('+0100') >>> utc_offset datetime.timedelta(seconds=3600) """ default_value: ClassVar[str] = "UTC-OFFSET" params: Parameters ignore_exceptions = False # if True, and we cannot parse this # component, we will silently ignore # it, rather than let the exception # propagate upwards def __init__(self, td: timedelta, /, params: dict[str, Any] | None = None) -> None: if not isinstance(td, timedelta): raise TypeError("Offset value MUST be a timedelta instance") self.td = td self.params = Parameters(params)
[docs] def to_ical(self) -> str: """Return the ical representation.""" return self.format("")
[docs] def format(self, divider: str = "") -> str: """Represent the value with a possible divider. .. code-block:: pycon >>> from icalendar import vUTCOffset >>> from datetime import timedelta >>> utc_offset = vUTCOffset(timedelta(hours=-5)) >>> utc_offset.format() '-0500' >>> utc_offset.format(divider=':') '-05:00' """ if self.td < timedelta(0): sign = "-%s" td = timedelta(0) - self.td # get timedelta relative to 0 else: # Google Calendar rejects '0000' but accepts '+0000' sign = "+%s" td = self.td days, seconds = td.days, td.seconds hours = abs(days * 24 + seconds // 3600) minutes = abs((seconds % 3600) // 60) seconds = abs(seconds % 60) if seconds: duration = f"{hours:02}{divider}{minutes:02}{divider}{seconds:02}" else: duration = f"{hours:02}{divider}{minutes:02}" return sign % duration
[docs] @classmethod def from_ical(cls, ical): if isinstance(ical, cls): return ical.td try: sign, hours, minutes, seconds = ( ical[0:1], int(ical[1:3]), int(ical[3:5]), int(ical[5:7] or 0), ) offset = timedelta(hours=hours, minutes=minutes, seconds=seconds) except Exception as e: raise ValueError(f"Expected UTC offset, got: {ical}") from e if not cls.ignore_exceptions and offset >= timedelta(hours=24): raise ValueError(f"Offset must be less than 24 hours, was {ical}") if sign == "-": return -offset return offset
def __eq__(self, other): if not isinstance(other, vUTCOffset): return False return self.td == other.td def __hash__(self): return hash(self.td) def __repr__(self): return f"vUTCOffset({self.td!r})"
[docs] @classmethod def examples(cls) -> list[Self]: """Examples of vUTCOffset.""" return [ cls(timedelta(hours=3)), cls(timedelta(0)), ]
from icalendar.param import VALUE
[docs] def to_jcal(self, name: str) -> list: """The jCal representation of this property according to :rfc:`7265`.""" return [name, self.params.to_jcal(), self.VALUE.lower(), self.format(":")]
[docs] @classmethod def from_jcal(cls, jcal_property: list) -> Self: """Parse jCal from :rfc:`7265`. Parameters: jcal_property: The jCal property to parse. Raises: ~icalendar.error.JCalParsingError: If the provided jCal is invalid. """ JCalParsingError.validate_property(jcal_property, cls) match = UTC_OFFSET_JCAL_REGEX.match(jcal_property[3]) if match is None: raise JCalParsingError(f"Cannot parse {jcal_property!r} as UTC-OFFSET.") negative = match.group("sign") == "-" hours = int(match.group("hours")) minutes = int(match.group("minutes")) seconds = int(match.group("seconds") or 0) t = timedelta(hours=hours, minutes=minutes, seconds=seconds) if negative: t = -t return cls(t, Parameters.from_jcal_property(jcal_property))
[docs] def to_xcal(self, element: Element) -> None: """The xCal representation of this property according to :rfc:`6321`.""" self.params.to_xcal(element) element = SubElement(element, self.default_value.lower()) element.text = self.format(":")
[docs] @classmethod @from_xcal_wrapper def from_xcal(cls, parser: XCalParser, params: Parameters) -> Self: """Parse xCal from :rfc:`6321`. Parameters: xml: The XML to parse or a parser. Raises: ~error.XCalParsingError: If the provided xCal is invalid. """ element = parser.parse_tag(cls.default_value) sign, hour, minute, second = XCAL_UTC_OFFSET_REGEX.groups(element) dt = timedelta(hours=int(hour), minutes=int(minute), seconds=int(second or "0")) if sign == "-": dt = -dt return cls(dt, params=params)
__all__ = ["vUTCOffset"]