Skip to content

Commit e5f04d4

Browse files
committed
Allow importing of functions in dates that don't depend on pytz when it isn't installed.
1 parent 6a180b1 commit e5f04d4

File tree

1 file changed

+155
-143
lines changed

1 file changed

+155
-143
lines changed

domdf_python_tools/dates.py

Lines changed: 155 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -87,173 +87,185 @@ def get_timezone(tz: str, date: Optional[datetime.datetime] = None) -> Optional[
8787

8888
return pytz.timezone(tz).localize(d).tzinfo
8989

90-
def current_tzinfo() -> Optional[datetime.tzinfo]:
91-
"""
92-
Returns a tzinfo object for the current timezone
90+
except ImportError as e:
9391

94-
:rtype: :class:`python:datetime.tzinfo`
95-
"""
92+
# stdlib
93+
import warnings
9694

97-
return datetime.datetime.now().astimezone().tzinfo # pragma: no cover (hard to test)
98-
99-
#
100-
# def datetime_to_utc_timestamp(datetime, current_tzinfo=None):
101-
# """
102-
# Convert a :class:`datetime.datetime` object to seconds since UNIX epoch, in UTC time
103-
#
104-
# :param datetime:
105-
# :type datetime: :class:`datetime.datetime`
106-
# :param current_tzinfo: A tzinfo object representing the current timezone.
107-
# If None it will be inferred.
108-
# :type current_tzinfo: :class:`~python:datetime.tzinfo`
109-
#
110-
# :return: Timestamp in UTC timezone
111-
# :rtype: float
112-
# """
113-
#
114-
# return datetime.astimezone(current_tzinfo).timestamp()
115-
#
116-
117-
def set_timezone(obj: datetime.datetime, tzinfo: datetime.tzinfo) -> datetime.datetime:
118-
"""
119-
Sets the timezone / tzinfo of the given :class:`datetime.datetime` object.
120-
This will not convert the time (i.e. the hours will stay the same).
121-
Use :meth:`python:datetime.datetime.astimezone` to accomplish that.
95+
warnings.warn(
96+
f"""\
97+
Some functions in 'domdf_python_tools.dates' require pytz (https://pypi.org/project/pytz/), but it could not be imported.
98+
The error was {e}.
99+
""",
100+
)
122101

123-
:param obj:
124-
:param tzinfo:
125102

126-
:return:
127-
"""
103+
def current_tzinfo() -> Optional[datetime.tzinfo]:
104+
"""
105+
Returns a tzinfo object for the current timezone
128106
129-
return obj.replace(tzinfo=tzinfo)
107+
:rtype: :class:`python:datetime.tzinfo`
108+
"""
130109

131-
def utc_timestamp_to_datetime(
132-
utc_timestamp: Union[float, int],
133-
output_tz: Optional[datetime.tzinfo] = None,
134-
) -> datetime.datetime:
135-
"""
136-
Convert UTC timestamp (seconds from UNIX epoch) to a :class:`datetime.datetime` object.
110+
return datetime.datetime.now().astimezone().tzinfo # pragma: no cover (hard to test)
137111

138-
If ``output_tz`` is None the timestamp is converted to the platform’s local date and time,
139-
and the local timezone is inferred and set for the object.
140112

141-
If ``output_tz`` is not None, it must be an instance of a :class:`~python:datetime.tzinfo` subclass,
142-
and the timestamp is converted to ``output_tz``’s time zone.
113+
#
114+
# def datetime_to_utc_timestamp(datetime, current_tzinfo=None):
115+
# """
116+
# Convert a :class:`datetime.datetime` object to seconds since UNIX epoch, in UTC time
117+
#
118+
# :param datetime:
119+
# :type datetime: :class:`datetime.datetime`
120+
# :param current_tzinfo: A tzinfo object representing the current timezone.
121+
# If None it will be inferred.
122+
# :type current_tzinfo: :class:`~python:datetime.tzinfo`
123+
#
124+
# :return: Timestamp in UTC timezone
125+
# :rtype: float
126+
# """
127+
#
128+
# return datetime.astimezone(current_tzinfo).timestamp()
129+
#
143130

144131

145-
:param utc_timestamp: The timestamp to convert to a datetime object
146-
:type utc_timestamp: float, int
147-
:param output_tz: The timezone to output the datetime object for.
148-
If None it will be inferred.
149-
:type output_tz: datetime.tzinfo, optional
132+
def set_timezone(obj: datetime.datetime, tzinfo: datetime.tzinfo) -> datetime.datetime:
133+
"""
134+
Sets the timezone / tzinfo of the given :class:`datetime.datetime` object.
135+
This will not convert the time (i.e. the hours will stay the same).
136+
Use :meth:`python:datetime.datetime.astimezone` to accomplish that.
150137
151-
:return: The timestamp as a datetime object.
152-
:rtype: datetime.datetime
138+
:param obj:
139+
:param tzinfo:
153140
154-
:raises: :class:`~python:OverflowError` if the timestamp is out of the range
155-
of values supported by the platform C localtime() or gmtime() functions,
156-
and OSError on localtime() or gmtime() failure. It’s common for this to
157-
be restricted to years in 1970 through 2038.
158-
"""
141+
:return:
142+
"""
159143

160-
new_datetime = datetime.datetime.fromtimestamp(utc_timestamp, output_tz)
161-
return new_datetime.astimezone(output_tz)
162-
163-
# List of months and their 3-character shortcodes.
164-
months = OrderedDict(
165-
Jan="January",
166-
Feb="February",
167-
Mar="March",
168-
Apr="April",
169-
May="May",
170-
Jun="June",
171-
Jul="July",
172-
Aug="August",
173-
Sep="September",
174-
Oct="October",
175-
Nov="November",
176-
Dec="December",
177-
)
144+
return obj.replace(tzinfo=tzinfo)
178145

179-
def parse_month(month: Union[str, int]) -> str:
180-
"""
181-
Converts an integer or shorthand month into the full month name.
182146

183-
:param month: The month number or shorthand name
184-
:type month: str or int
147+
def utc_timestamp_to_datetime(
148+
utc_timestamp: Union[float, int],
149+
output_tz: Optional[datetime.tzinfo] = None,
150+
) -> datetime.datetime:
151+
"""
152+
Convert UTC timestamp (seconds from UNIX epoch) to a :class:`datetime.datetime` object.
185153
186-
:return: The full name of the month
187-
:rtype: str
188-
"""
154+
If ``output_tz`` is None the timestamp is converted to the platform’s local date and time,
155+
and the local timezone is inferred and set for the object.
189156
190-
try:
191-
month = int(month)
192-
except ValueError:
193-
try:
194-
return months[month.capitalize()[:3]] # type: ignore
195-
except KeyError:
196-
raise ValueError("Unrecognised month value")
197-
198-
# Only get here if first try succeeded
199-
if 0 < month <= 12:
200-
return list(months.values())[month - 1]
201-
else:
202-
raise ValueError("Unrecognised month value")
157+
If ``output_tz`` is not None, it must be an instance of a :class:`~python:datetime.tzinfo` subclass,
158+
and the timestamp is converted to ``output_tz``’s time zone.
203159
204-
def get_month_number(month: Union[str, int]) -> int:
205-
"""
206-
Returns the number of the given month.
207-
If ``month`` is already a number between 1 and 12 it will be returned immediately.
208160
209-
:param month: The month to convert to a number
210-
:type month: str or int
161+
:param utc_timestamp: The timestamp to convert to a datetime object
162+
:type utc_timestamp: float, int
163+
:param output_tz: The timezone to output the datetime object for.
164+
If None it will be inferred.
165+
:type output_tz: datetime.tzinfo, optional
211166
212-
:return: The number of the month
213-
:rtype: int
214-
"""
167+
:return: The timestamp as a datetime object.
168+
:rtype: datetime.datetime
215169
216-
if isinstance(month, int):
217-
if 0 < month <= 12:
218-
return month
219-
else:
220-
raise ValueError("The given month is not recognised.")
221-
else:
222-
month = parse_month(month)
223-
return list(months.values()).index(month) + 1
170+
:raises: :class:`~python:OverflowError` if the timestamp is out of the range
171+
of values supported by the platform C localtime() or gmtime() functions,
172+
and OSError on localtime() or gmtime() failure. It’s common for this to
173+
be restricted to years in 1970 through 2038.
174+
"""
224175

225-
def check_date(month: Union[str, int], day: int, leap_year: bool = True) -> bool:
226-
"""
227-
Returns :py:obj:`True` if the day number is valid for the given month.
228-
Note that this will return :py:obj:`True` for the 29th Feb. If you don't
229-
want this behaviour set ``leap_year`` to :py:obj:`False`.
230-
231-
:param month: The month to test.
232-
:type month: str, int
233-
:param day: The day number to test.
234-
:type day: int
235-
:param leap_year: Whether to return :py:obj:`True` for 29th Feb. Default :py:obj:`True`.
236-
:type leap_year: bool, optional
237-
238-
:return: :py:obj:`True` if the date is valid.
239-
:rtype: bool
240-
"""
176+
new_datetime = datetime.datetime.fromtimestamp(utc_timestamp, output_tz)
177+
return new_datetime.astimezone(output_tz)
178+
179+
180+
# List of months and their 3-character shortcodes.
181+
months = OrderedDict(
182+
Jan="January",
183+
Feb="February",
184+
Mar="March",
185+
Apr="April",
186+
May="May",
187+
Jun="June",
188+
Jul="July",
189+
Aug="August",
190+
Sep="September",
191+
Oct="October",
192+
Nov="November",
193+
Dec="December",
194+
)
195+
196+
197+
def parse_month(month: Union[str, int]) -> str:
198+
"""
199+
Converts an integer or shorthand month into the full month name.
200+
201+
:param month: The month number or shorthand name
202+
:type month: str or int
241203
242-
# Ensure day is an integer
243-
day = int(day)
244-
month = get_month_number(month)
245-
year = 2020 if leap_year else 2019
204+
:return: The full name of the month
205+
:rtype: str
206+
"""
246207

208+
try:
209+
month = int(month)
210+
except ValueError:
247211
try:
248-
datetime.date(year, month, day)
249-
return True
250-
except ValueError:
251-
return False
212+
return months[month.capitalize()[:3]] # type: ignore
213+
except KeyError:
214+
raise ValueError("Unrecognised month value")
252215

253-
except ImportError:
216+
# Only get here if first try succeeded
217+
if 0 < month <= 12:
218+
return list(months.values())[month - 1]
219+
else:
220+
raise ValueError("Unrecognised month value")
254221

255-
# stdlib
256-
import warnings
257-
warnings.warn(
258-
"'domdf_python_tools.dates' requires pytz (https://pypi.org/project/pytz/), but it is not installed.",
259-
)
222+
223+
def get_month_number(month: Union[str, int]) -> int:
224+
"""
225+
Returns the number of the given month.
226+
If ``month`` is already a number between 1 and 12 it will be returned immediately.
227+
228+
:param month: The month to convert to a number
229+
:type month: str or int
230+
231+
:return: The number of the month
232+
:rtype: int
233+
"""
234+
235+
if isinstance(month, int):
236+
if 0 < month <= 12:
237+
return month
238+
else:
239+
raise ValueError("The given month is not recognised.")
240+
else:
241+
month = parse_month(month)
242+
return list(months.values()).index(month) + 1
243+
244+
245+
def check_date(month: Union[str, int], day: int, leap_year: bool = True) -> bool:
246+
"""
247+
Returns :py:obj:`True` if the day number is valid for the given month.
248+
Note that this will return :py:obj:`True` for the 29th Feb. If you don't
249+
want this behaviour set ``leap_year`` to :py:obj:`False`.
250+
251+
:param month: The month to test.
252+
:type month: str, int
253+
:param day: The day number to test.
254+
:type day: int
255+
:param leap_year: Whether to return :py:obj:`True` for 29th Feb. Default :py:obj:`True`.
256+
:type leap_year: bool, optional
257+
258+
:return: :py:obj:`True` if the date is valid.
259+
:rtype: bool
260+
"""
261+
262+
# Ensure day is an integer
263+
day = int(day)
264+
month = get_month_number(month)
265+
year = 2020 if leap_year else 2019
266+
267+
try:
268+
datetime.date(year, month, day)
269+
return True
270+
except ValueError:
271+
return False

0 commit comments

Comments
 (0)