|
8 | 8 |
|
9 | 9 | pytz >=2019.1 |
10 | 10 |
|
11 | | -
|
12 | | -.. warning:: This module has not been fully tested. Use with caution. |
13 | | -
|
14 | 11 | """ |
15 | 12 | # |
16 | 13 | # Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk> |
|
47 | 44 | import sys |
48 | 45 | import typing |
49 | 46 | from collections import OrderedDict |
| 47 | +from types import ModuleType |
50 | 48 | from typing import Optional, Union |
51 | 49 |
|
52 | 50 | __all__ = [ |
|
60 | 58 | "calc_easter", |
61 | 59 | ] |
62 | 60 |
|
63 | | -try: |
64 | | - |
65 | | - # 3rd party |
66 | | - import pytz |
67 | | - |
68 | | - def get_utc_offset( |
69 | | - tz: Union[datetime.tzinfo, str], |
70 | | - date: Optional[datetime.datetime] = None, |
71 | | - ) -> Optional[datetime.timedelta]: |
72 | | - """ |
73 | | - Returns the offset between UTC and the requested timezone on the given date. |
74 | | - If ``date`` is :py:obj:`None` then the current date is used. |
75 | | -
|
76 | | - :param tz: ``pytz.timezone`` or a string representing the timezone |
77 | | - :param date: The date to obtain the UTC offset for |
78 | | - """ |
79 | | - |
80 | | - if date is None: |
81 | | - date = datetime.datetime.utcnow() |
82 | | - |
83 | | - timezone: Optional[datetime.tzinfo] |
84 | | - |
85 | | - if isinstance(tz, str): |
86 | | - timezone = get_timezone(tz, date) |
87 | | - else: |
88 | | - timezone = tz # pragma: no cover (hard to test) |
89 | | - |
90 | | - return date.replace(tzinfo=pytz.utc).astimezone(timezone).utcoffset() |
91 | | - |
92 | | - def get_timezone(tz: str, date: Optional[datetime.datetime] = None) -> Optional[datetime.tzinfo]: |
93 | | - """ |
94 | | - Returns a localized ``pytz.timezone`` object for the given date. |
95 | | - If ``date`` is :py:obj:`None` then the current date is used. |
96 | | -
|
97 | | - :param tz: A string representing a pytz timezone |
98 | | - :param date: The date to obtain the timezone for |
99 | | - """ |
100 | | - |
101 | | - if date is None: |
102 | | - date = datetime.datetime.utcnow() # pragma: no cover (hard to test) |
103 | | - |
104 | | - d = date.replace(tzinfo=None) |
105 | | - |
106 | | - return pytz.timezone(tz).localize(d).tzinfo |
107 | | - |
108 | | - __all__.extend(["get_utc_offset", "get_timezone"]) |
109 | | - |
110 | | -except ImportError as e: # pragma: no cover |
111 | | - |
112 | | - # stdlib |
113 | | - import warnings |
114 | | - |
115 | | - warnings.warn( |
116 | | - f"""\ |
117 | | -Some functions in 'domdf_python_tools.dates' require pytz (https://pypi.org/project/pytz/), \ |
118 | | -but it could not be imported. |
119 | | -
|
120 | | -The error was: {e}. |
121 | | -""", |
122 | | - ) |
123 | | - |
124 | 61 |
|
125 | 62 | def current_tzinfo() -> Optional[datetime.tzinfo]: |
126 | 63 | """ |
@@ -192,9 +129,9 @@ def utc_timestamp_to_datetime( |
192 | 129 | return new_datetime.astimezone(output_tz) |
193 | 130 |
|
194 | 131 |
|
195 | | -if sys.version_info <= (3, 7, 2): |
| 132 | +if sys.version_info <= (3, 7, 2): # pragma: no cover (py37+) |
196 | 133 | MonthsType = OrderedDict |
197 | | -else: |
| 134 | +else: # pragma: no cover (<py37) |
198 | 135 | MonthsType = typing.OrderedDict[str, str] # type: ignore # noqa: TYP006 |
199 | 136 |
|
200 | 137 | #: Mapping of 3-character shortcodes to full month names. |
@@ -302,3 +239,82 @@ def calc_easter(year: int) -> datetime.date: |
302 | 239 | day = f % 31 + 1 |
303 | 240 |
|
304 | 241 | return datetime.date(year, month, day) |
| 242 | + |
| 243 | + |
| 244 | +def get_utc_offset( |
| 245 | + tz: Union[datetime.tzinfo, str], |
| 246 | + date: Optional[datetime.datetime] = None, |
| 247 | + ) -> Optional[datetime.timedelta]: |
| 248 | + """ |
| 249 | + Returns the offset between UTC and the requested timezone on the given date. |
| 250 | + If ``date`` is :py:obj:`None` then the current date is used. |
| 251 | +
|
| 252 | + :param tz: ``pytz.timezone`` or a string representing the timezone |
| 253 | + :param date: The date to obtain the UTC offset for |
| 254 | + """ |
| 255 | + |
| 256 | + if date is None: |
| 257 | + date = datetime.datetime.utcnow() |
| 258 | + |
| 259 | + timezone: Optional[datetime.tzinfo] |
| 260 | + |
| 261 | + if isinstance(tz, str): |
| 262 | + timezone = get_timezone(tz, date) |
| 263 | + else: |
| 264 | + timezone = tz # pragma: no cover (hard to test) |
| 265 | + |
| 266 | + return date.replace(tzinfo=pytz.utc).astimezone(timezone).utcoffset() |
| 267 | + |
| 268 | + |
| 269 | +def get_timezone(tz: str, date: Optional[datetime.datetime] = None) -> Optional[datetime.tzinfo]: |
| 270 | + """ |
| 271 | + Returns a localized ``pytz.timezone`` object for the given date. |
| 272 | + If ``date`` is :py:obj:`None` then the current date is used. |
| 273 | +
|
| 274 | + :param tz: A string representing a pytz timezone |
| 275 | + :param date: The date to obtain the timezone for |
| 276 | + """ |
| 277 | + |
| 278 | + if date is None: |
| 279 | + date = datetime.datetime.utcnow() # pragma: no cover (hard to test) |
| 280 | + |
| 281 | + d = date.replace(tzinfo=None) |
| 282 | + |
| 283 | + return pytz.timezone(tz).localize(d).tzinfo |
| 284 | + |
| 285 | + |
| 286 | +_pytz_functions = ["get_utc_offset", "get_timezone"] |
| 287 | + |
| 288 | +try: |
| 289 | + |
| 290 | + # 3rd party |
| 291 | + import pytz |
| 292 | + |
| 293 | + __all__.extend(_pytz_functions) |
| 294 | + |
| 295 | +except ImportError as e: |
| 296 | + |
| 297 | + if __name__ == "__main__": |
| 298 | + |
| 299 | + import warnings |
| 300 | + from domdf_python_tools.words import word_join |
| 301 | + |
| 302 | + warnings.warn(f"""\ |
| 303 | + '{word_join(_pytz_functions)}' require pytz (https://pypi.org/project/pytz/), but it could not be imported. |
| 304 | +
|
| 305 | + The error was: {e}. |
| 306 | + """) |
| 307 | + |
| 308 | + else: |
| 309 | + _actual_module = sys.modules[__name__] |
| 310 | + |
| 311 | + class SelfWrapper(ModuleType): |
| 312 | + def __getattr__(self, name): |
| 313 | + if name in _pytz_functions: |
| 314 | + raise ImportError( |
| 315 | + f"{name!r} requires pytz (https://pypi.org/project/pytz/), but it could not be imported." |
| 316 | + ) |
| 317 | + else: |
| 318 | + return getattr(_actual_module, name) |
| 319 | + |
| 320 | + sys.modules[__name__] = SelfWrapper(__name__) |
0 commit comments