Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/timezone/tzp.py: 79%

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

78 statements  

1from __future__ import annotations 

2 

3from datetime import datetime, time 

4from typing import TYPE_CHECKING, overload 

5 

6from icalendar.tools import to_datetime 

7 

8from .windows_to_olson import WINDOWS_TO_OLSON 

9 

10if TYPE_CHECKING: 

11 from dateutil.rrule import rrule 

12 

13 from icalendar import prop 

14 from icalendar.cal import Timezone 

15 

16 from .provider import TZProvider 

17 

18DEFAULT_TIMEZONE_PROVIDER = "zoneinfo" 

19 

20 

21class TZP: 

22 """This is the timezone provider proxy. 

23 

24 If you would like to have another timezone implementation, 

25 you can create a new one and pass it to this proxy. 

26 All of icalendar will then use this timezone implementation. 

27 """ 

28 

29 def __init__(self, provider: str | TZProvider = DEFAULT_TIMEZONE_PROVIDER): 

30 """Create a new timezone implementation proxy.""" 

31 self.use(provider) 

32 

33 def use_pytz(self) -> None: 

34 """Use pytz as the timezone provider.""" 

35 from .pytz import PYTZ # noqa: PLC0415, RUF100 

36 

37 self._use(PYTZ()) 

38 

39 def use_zoneinfo(self) -> None: 

40 """Use zoneinfo as the timezone provider.""" 

41 from .zoneinfo import ZONEINFO # noqa: PLC0415, RUF100 

42 

43 self._use(ZONEINFO()) 

44 

45 def _use(self, provider: TZProvider) -> None: 

46 """Use a timezone implementation.""" 

47 self.__tz_cache = {} 

48 self.__provider = provider 

49 

50 def use(self, provider: str | TZProvider): 

51 """Switch to a different timezone provider.""" 

52 if isinstance(provider, str): 

53 use_provider = getattr(self, f"use_{provider}", None) 

54 if use_provider is None: 

55 raise ValueError( 

56 f"Unknown provider {provider}. Use 'pytz' or 'zoneinfo'." 

57 ) 

58 use_provider() 

59 else: 

60 self._use(provider) 

61 

62 def use_default(self): 

63 """Use the default timezone provider.""" 

64 self.use(DEFAULT_TIMEZONE_PROVIDER) 

65 

66 def localize_utc(self, dt: datetime.date) -> datetime.datetime: 

67 """Return the datetime in UTC. 

68 

69 If the datetime has no timezone, set UTC as its timezone. 

70 """ 

71 return self.__provider.localize_utc(to_datetime(dt)) 

72 

73 @overload 

74 def localize( 

75 self, dt: datetime.datetime, tz: datetime.tzinfo | str | None 

76 ) -> datetime.datetime: ... 

77 

78 @overload 

79 def localize( 

80 self, dt: datetime.time, tz: datetime.tzinfo | str | None 

81 ) -> datetime.time: ... 

82 

83 def localize( 

84 self, dt: datetime.date | datetime.time, tz: datetime.tzinfo | str | None 

85 ) -> datetime.datetime | datetime.time: 

86 """Localize a datetime or time to a timezone. 

87 

88 Returns: 

89 - A localized :class:`datetime.datetime` when a 

90 :class:`datetime.datetime` is given. 

91 - A localized :class:`datetime.time` when a 

92 :class:`datetime.time` is given. 

93 """ 

94 if isinstance(tz, str): 

95 tz = self.timezone(tz) 

96 if tz is None: 

97 return dt.replace(tzinfo=None) 

98 if isinstance(dt, time): 

99 dt_full = datetime.combine(datetime(2020, 1, 1), dt) # noqa: DTZ001 

100 localized = self.__provider.localize(dt_full, tz) 

101 return localized.timetz() 

102 return self.__provider.localize(to_datetime(dt), tz) 

103 

104 def cache_timezone_component(self, timezone_component: Timezone.Timezone) -> None: 

105 """Cache the timezone that is created from a timezone component 

106 if it is not already known. 

107 

108 This can influence the result from timezone(): Once cached, the 

109 custom timezone is returned from timezone(). 

110 """ 

111 _unclean_id = timezone_component["TZID"] 

112 _id = self.clean_timezone_id(_unclean_id) 

113 if ( 

114 not self.__provider.knows_timezone_id(_id) 

115 and not self.__provider.knows_timezone_id(_unclean_id) 

116 and _id not in self.__tz_cache 

117 ): 

118 self.__tz_cache[_id] = timezone_component.to_tz(self, lookup_tzid=False) 

119 

120 def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None: 

121 """Make sure the until value works.""" 

122 self.__provider.fix_rrule_until(rrule, ical_rrule) 

123 

124 def create_timezone(self, timezone_component: Timezone.Timezone) -> datetime.tzinfo: 

125 """Create a timezone from a timezone component. 

126 

127 This component will not be cached. 

128 """ 

129 return self.__provider.create_timezone(timezone_component) 

130 

131 def clean_timezone_id(self, tzid: str) -> str: 

132 """Return a clean version of the timezone id. 

133 

134 Timezone ids can be a bit unclean, starting with a / for example. 

135 Internally, we should use this to identify timezones. 

136 """ 

137 return tzid.strip("/") 

138 

139 def timezone(self, tz_id: str) -> datetime.tzinfo | None: 

140 """Return a timezone with an id or None if we cannot find it.""" 

141 _unclean_id = tz_id 

142 tz_id = self.clean_timezone_id(tz_id) 

143 tz = self.__provider.timezone(tz_id) 

144 if tz is not None: 

145 return tz 

146 if tz_id in WINDOWS_TO_OLSON: 

147 tz = self.__provider.timezone(WINDOWS_TO_OLSON[tz_id]) 

148 return tz or self.__provider.timezone(_unclean_id) or self.__tz_cache.get(tz_id) 

149 

150 def uses_pytz(self) -> bool: 

151 """Whether we use pytz at all.""" 

152 return self.__provider.uses_pytz() 

153 

154 def uses_zoneinfo(self) -> bool: 

155 """Whether we use zoneinfo.""" 

156 return self.__provider.uses_zoneinfo() 

157 

158 @property 

159 def name(self) -> str: 

160 """The name of the timezone component used.""" 

161 return self.__provider.name 

162 

163 def __repr__(self) -> str: 

164 return f"{self.__class__.__name__}({self.name!r})" 

165 

166 

167__all__ = ["TZP"]