Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/dt/utc_offset.py: 72%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

76 statements  

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

2 

3import re 

4from datetime import timedelta 

5from typing import Any, ClassVar 

6 

7from icalendar.compatibility import Self 

8from icalendar.error import JCalParsingError 

9from icalendar.parser import Parameters 

10 

11UTC_OFFSET_JCAL_REGEX = re.compile( 

12 r"^(?P<sign>[+-])?(?P<hours>\d\d):(?P<minutes>\d\d)(?::(?P<seconds>\d\d))?$" 

13) 

14 

15 

16class vUTCOffset: 

17 """UTC Offset 

18 

19 Value Name: 

20 UTC-OFFSET 

21 

22 Purpose: 

23 This value type is used to identify properties that contain 

24 an offset from UTC to local time. 

25 

26 Format Definition: 

27 This value type is defined by the following notation: 

28 

29 .. code-block:: text 

30 

31 utc-offset = time-numzone 

32 

33 time-numzone = ("+" / "-") time-hour time-minute [time-second] 

34 

35 Description: 

36 The PLUS SIGN character MUST be specified for positive 

37 UTC offsets (i.e., ahead of UTC). The HYPHEN-MINUS character MUST 

38 be specified for negative UTC offsets (i.e., behind of UTC). The 

39 value of "-0000" and "-000000" are not allowed. The time-second, 

40 if present, MUST NOT be 60; if absent, it defaults to zero. 

41 

42 Example: 

43 The following UTC offsets are given for standard time for 

44 New York (five hours behind UTC) and Geneva (one hour ahead of 

45 UTC): 

46 

47 .. code-block:: text 

48 

49 -0500 

50 

51 +0100 

52 

53 .. code-block:: pycon 

54 

55 >>> from icalendar.prop import vUTCOffset 

56 >>> utc_offset = vUTCOffset.from_ical('-0500') 

57 >>> utc_offset 

58 datetime.timedelta(days=-1, seconds=68400) 

59 >>> utc_offset = vUTCOffset.from_ical('+0100') 

60 >>> utc_offset 

61 datetime.timedelta(seconds=3600) 

62 """ 

63 

64 default_value: ClassVar[str] = "UTC-OFFSET" 

65 params: Parameters 

66 

67 ignore_exceptions = False # if True, and we cannot parse this 

68 

69 # component, we will silently ignore 

70 # it, rather than let the exception 

71 # propagate upwards 

72 

73 def __init__(self, td: timedelta, /, params: dict[str, Any] | None = None): 

74 if not isinstance(td, timedelta): 

75 raise TypeError("Offset value MUST be a timedelta instance") 

76 self.td = td 

77 self.params = Parameters(params) 

78 

79 def to_ical(self) -> str: 

80 """Return the ical representation.""" 

81 return self.format("") 

82 

83 def format(self, divider: str = "") -> str: 

84 """Represent the value with a possible divider. 

85 

86 .. code-block:: pycon 

87 

88 >>> from icalendar import vUTCOffset 

89 >>> from datetime import timedelta 

90 >>> utc_offset = vUTCOffset(timedelta(hours=-5)) 

91 >>> utc_offset.format() 

92 '-0500' 

93 >>> utc_offset.format(divider=':') 

94 '-05:00' 

95 """ 

96 if self.td < timedelta(0): 

97 sign = "-%s" 

98 td = timedelta(0) - self.td # get timedelta relative to 0 

99 else: 

100 # Google Calendar rejects '0000' but accepts '+0000' 

101 sign = "+%s" 

102 td = self.td 

103 

104 days, seconds = td.days, td.seconds 

105 

106 hours = abs(days * 24 + seconds // 3600) 

107 minutes = abs((seconds % 3600) // 60) 

108 seconds = abs(seconds % 60) 

109 if seconds: 

110 duration = f"{hours:02}{divider}{minutes:02}{divider}{seconds:02}" 

111 else: 

112 duration = f"{hours:02}{divider}{minutes:02}" 

113 return sign % duration 

114 

115 @classmethod 

116 def from_ical(cls, ical): 

117 if isinstance(ical, cls): 

118 return ical.td 

119 try: 

120 sign, hours, minutes, seconds = ( 

121 ical[0:1], 

122 int(ical[1:3]), 

123 int(ical[3:5]), 

124 int(ical[5:7] or 0), 

125 ) 

126 offset = timedelta(hours=hours, minutes=minutes, seconds=seconds) 

127 except Exception as e: 

128 raise ValueError(f"Expected UTC offset, got: {ical}") from e 

129 if not cls.ignore_exceptions and offset >= timedelta(hours=24): 

130 raise ValueError(f"Offset must be less than 24 hours, was {ical}") 

131 if sign == "-": 

132 return -offset 

133 return offset 

134 

135 def __eq__(self, other): 

136 if not isinstance(other, vUTCOffset): 

137 return False 

138 return self.td == other.td 

139 

140 def __hash__(self): 

141 return hash(self.td) 

142 

143 def __repr__(self): 

144 return f"vUTCOffset({self.td!r})" 

145 

146 @classmethod 

147 def examples(cls) -> list[Self]: 

148 """Examples of vUTCOffset.""" 

149 return [ 

150 cls(timedelta(hours=3)), 

151 cls(timedelta(0)), 

152 ] 

153 

154 from icalendar.param import VALUE 

155 

156 def to_jcal(self, name: str) -> list: 

157 """The jCal representation of this property according to :rfc:`7265`.""" 

158 return [name, self.params.to_jcal(), self.VALUE.lower(), self.format(":")] 

159 

160 @classmethod 

161 def from_jcal(cls, jcal_property: list) -> Self: 

162 """Parse jCal from :rfc:`7265`. 

163 

164 Parameters: 

165 jcal_property: The jCal property to parse. 

166 

167 Raises: 

168 ~error.JCalParsingError: If the provided jCal is invalid. 

169 """ 

170 JCalParsingError.validate_property(jcal_property, cls) 

171 match = UTC_OFFSET_JCAL_REGEX.match(jcal_property[3]) 

172 if match is None: 

173 raise JCalParsingError(f"Cannot parse {jcal_property!r} as UTC-OFFSET.") 

174 negative = match.group("sign") == "-" 

175 hours = int(match.group("hours")) 

176 minutes = int(match.group("minutes")) 

177 seconds = int(match.group("seconds") or 0) 

178 t = timedelta(hours=hours, minutes=minutes, seconds=seconds) 

179 if negative: 

180 t = -t 

181 return cls(t, Parameters.from_jcal_property(jcal_property)) 

182 

183 

184__all__ = ["vUTCOffset"]