|
2 | 2 |
|
3 | 3 | # source: https://github.com/RhetTbull/datetime-utils
|
4 | 4 |
|
5 |
| -__version__ = "2022.04.30" |
| 5 | +__version__ = "2024.10.07" |
6 | 6 |
|
7 | 7 | import datetime
|
| 8 | +from zoneinfo import ZoneInfo |
8 | 9 |
|
9 | 10 | # TODO: probably shouldn't use replace here, see this:
|
10 | 11 | # https://stackoverflow.com/questions/13994594/how-to-add-timezone-into-a-naive-datetime-instance-in-python/13994611#13994611
|
11 | 12 |
|
12 | 13 | __all__ = [
|
| 14 | + "datetime_add_tz", |
13 | 15 | "datetime_has_tz",
|
14 | 16 | "datetime_naive_to_local",
|
15 | 17 | "datetime_naive_to_utc",
|
@@ -209,39 +211,40 @@ def utc_offset_seconds(dt: datetime.datetime) -> int:
|
209 | 211 | else:
|
210 | 212 | raise ValueError("dt does not have timezone info")
|
211 | 213 |
|
212 |
| -def datetime_add_tz(dt: datetime.datetime, |
213 |
| - tzoffset: int | None = None, |
214 |
| - tzname: str | None = None, |
215 |
| - ) -> datetime.datetime: |
216 |
| - """Add a timezone, either as an offset or named timezone, to a naive datetime. |
217 | 214 |
|
218 |
| - Args: |
219 |
| - dt: naive datetime |
220 |
| - tzoffset: offset from UTC for timezone in seconds |
221 |
| - tzname: name of timezone, for example, "America/Los_Angeles" |
| 215 | +def datetime_add_tz( |
| 216 | + dt: datetime.datetime, |
| 217 | + tzoffset: int | None = None, |
| 218 | + tzname: str | None = None, |
| 219 | +) -> datetime.datetime: |
| 220 | + """Add a timezone, either as an offset or named timezone, to a naive datetime. |
222 | 221 |
|
223 |
| - Returns: timezone-aware datetime with new timezone |
| 222 | + Args: |
| 223 | + dt: naive datetime |
| 224 | + tzoffset: offset from UTC for timezone in seconds |
| 225 | + tzname: name of timezone, for example, "America/Los_Angeles" |
| 226 | +
|
| 227 | + Returns: timezone-aware datetime with new timezone |
| 228 | +
|
| 229 | + Raises: |
| 230 | + ValueError if both tzoffset and tzname are None |
| 231 | + ValueError if dt is not naive |
| 232 | + """ |
| 233 | + if datetime_has_tz(dt): |
| 234 | + raise ValueError(f"dt must be naive datetime: {dt}") |
224 | 235 |
|
225 |
| - Note: you may pass tzoffset, tzname or both. If both are passed, the offset will be tried |
226 |
| - first |
227 |
| - """ |
228 |
| - if timestamp is None: |
229 |
| - return DEFAULT_DATETIME if default else None |
| 236 | + if tzoffset is None and tzname is None: |
| 237 | + raise ValueError("Both tzoffset and tzname cannot be None") |
230 | 238 |
|
231 |
| - tzoffset = tzoffset or 0 |
| 239 | + tzoffset = tzoffset or 0 |
| 240 | + if tzname: |
232 | 241 | try:
|
233 |
| - dt = datetime.datetime.fromtimestamp(timestamp + TIME_DELTA) |
234 |
| - # Try to use tzname if provided |
235 |
| - if tzname: |
236 |
| - try: |
237 |
| - tz = ZoneInfo(tzname) |
238 |
| - return dt.astimezone(tz) |
239 |
| - except Exception: |
240 |
| - # If tzname fails, fall back to tzoffset |
241 |
| - pass |
242 |
| - |
243 |
| - # Use tzoffset if tzname wasn't provided or failed |
244 |
| - tz = datetime.timezone(datetime.timedelta(seconds=tzoffset)) |
| 242 | + tz = ZoneInfo(tzname) |
245 | 243 | return dt.astimezone(tz)
|
246 |
| - except (ValueError, TypeError): |
247 |
| - return DEFAULT_DATETIME if default else None |
| 244 | + except Exception: |
| 245 | + # If tzname fails, fall back to tzoffset |
| 246 | + pass |
| 247 | + |
| 248 | + # Use tzoffset if tzname wasn't provided or failed |
| 249 | + tz = datetime.timezone(datetime.timedelta(seconds=tzoffset)) |
| 250 | + return dt.astimezone(tz) |
0 commit comments