Skip to content

Commit b7a6cfe

Browse files
committed
Allow explicit UTC offset to be passed to bypass timezone lookup.
1 parent 8601392 commit b7a6cfe

3 files changed

Lines changed: 21 additions & 18 deletions

File tree

immanuel/charts.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@
3030
class Subject:
3131
""" Simple class to model a chart subject - essentially just
3232
a time and place. """
33-
def __init__(self, date_time: datetime | str, latitude: float | list | tuple | str, longitude: float | list | tuple | str, time_is_dst: bool = None) -> None:
33+
def __init__(self, date_time: str | float | datetime, latitude: float | list | tuple | str, longitude: float | list | tuple | str, timezone_offset: float = None, time_is_dst: bool = None) -> None:
3434
self.latitude, self.longitude = (convert.to_dec(v) for v in (latitude, longitude))
3535
self.time_is_dst = time_is_dst
3636
self.date_time = date.to_datetime(
3737
dt=date_time,
3838
lat=self.latitude,
3939
lon=self.longitude,
40-
is_dst=time_is_dst
40+
offset=timezone_offset,
41+
is_dst=time_is_dst,
4142
)
4243
self.date_time_ambiguous = date.ambiguous(self.date_time) and time_is_dst is None
4344
self.julian_date = date.to_jd(self.date_time)
@@ -449,7 +450,7 @@ class Transits(Chart):
449450
to the natal chart. Coordinates default to those specified in settings. """
450451
def __init__(self, latitude: float | list | tuple | str = settings.default_latitude, longitude: float | list | tuple | str = settings.default_longitude, aspects_to: Chart = None) -> None:
451452
lat, lon = (convert.to_dec(v) for v in (latitude, longitude))
452-
timezone = date.timezone(lat, lon)
453+
timezone = date.timezone_name(lat, lon)
453454
date_time = datetime.now(tz=ZoneInfo(timezone))
454455
self._native = Subject(date_time, lat, lon)
455456
super().__init__(chart.TRANSITS, aspects_to)

immanuel/tools/date.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
1515
"""
1616

17-
from datetime import datetime
17+
from datetime import datetime, timedelta, timezone
1818
from zoneinfo import ZoneInfo
1919

2020
import swisseph as swe
@@ -29,37 +29,39 @@ def ambiguous(dt: datetime) -> bool:
2929
return tz.datetime_ambiguous(dt)
3030

3131

32-
def timezone(lat: float, lon: float) -> str:
32+
def timezone_name(lat: float, lon: float) -> str:
3333
""" Returns a timezone string based on decimal lat/lon coordinates. """
3434
return TimezoneFinder().timezone_at(lat=lat, lng=lon)
3535

3636

37-
def localize(dt: datetime, lat: float, lon: float, is_dst: bool = None) -> datetime:
38-
""" Localizes a naive datetime based on decimal lat/lon coordinates. """
39-
return dt.replace(tzinfo=ZoneInfo(timezone(lat, lon)), fold=1 if is_dst is False else 0)
37+
def localize(dt: datetime, lat: float, lon: float, offset: float = None, is_dst: bool = None) -> datetime:
38+
""" Localizes a naive datetime based on either decimal lat/lon
39+
coordinates or an explicit UTC offset. """
40+
zone = ZoneInfo(timezone_name(lat, lon)) if offset is None else timezone(timedelta(hours=offset))
41+
return dt.replace(tzinfo=zone, fold=1 if is_dst is False else 0)
4042

4143

42-
def to_datetime(dt: str | float | datetime, lat: float = None, lon: float = None, is_dst: bool = None) -> datetime:
44+
def to_datetime(dt: str | float | datetime, lat: float = None, lon: float = None, offset: float = None, is_dst: bool = None) -> datetime:
4345
""" Convert an unknown into a datetime. Unknowns can be either an
4446
ISO-formatted string, a Julian Date, or already a datetime. """
4547
no_coords = lat is None or lon is None
4648
if isinstance(dt, str):
4749
date_time = datetime.fromisoformat(dt)
48-
return date_time.replace(tzinfo=ZoneInfo('UTC')) if no_coords else localize(date_time, lat, lon, is_dst)
50+
return date_time.replace(tzinfo=ZoneInfo('UTC')) if no_coords else localize(date_time, lat, lon, offset, is_dst)
4951
if isinstance(dt, float):
5052
ut = swe.revjul(dt)
5153
time = convert.dec_to_dms(ut[3])[1:]
5254
date_time = datetime(*ut[:3], *time, tzinfo=ZoneInfo('UTC'))
53-
return date_time if no_coords else date_time.astimezone(ZoneInfo(timezone(lat, lon)))
55+
return date_time if no_coords else date_time.astimezone(ZoneInfo(timezone_name(lat, lon)))
5456
if isinstance(dt, datetime):
5557
if no_coords:
5658
return dt.replace(tzinfo=ZoneInfo('UTC')) if dt.tzinfo is None else dt
5759
else:
58-
return localize(dt, lat, lon, is_dst)
60+
return localize(dt, lat, lon, offset, is_dst)
5961
return None
6062

6163

62-
def to_jd(dt: str | float | datetime, lat: float = None, lon: float = None, is_dst: bool = None) -> float:
64+
def to_jd(dt: str | float | datetime, lat: float = None, lon: float = None, offset: float = None, is_dst: bool = None) -> float:
6365
""" Convert an unknown into a Julian date. Unknowns can be either an
6466
ISO-formatted string, already a Julian Date, or a datetime. """
6567
if isinstance(dt, float):
@@ -72,7 +74,7 @@ def to_jd(dt: str | float | datetime, lat: float = None, lon: float = None, is_d
7274
return None
7375

7476
if lat is not None and lon is not None:
75-
date_time = localize(date_time, lat, lon, is_dst)
77+
date_time = localize(date_time, lat, lon, offset, is_dst)
7678
elif date_time.tzinfo is None:
7779
date_time = date_time.replace(tzinfo=ZoneInfo('UTC'))
7880

tests/test_date.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ def jd():
5050

5151

5252
def test_timezone_gmt(gmt_coords):
53-
assert date.timezone(*gmt_coords) == 'Europe/London'
53+
assert date.timezone_name(*gmt_coords) == 'Europe/London'
5454

5555

5656
def test_timezone_pst(pst_coords):
57-
assert date.timezone(*pst_coords) == 'America/Los_Angeles'
57+
assert date.timezone_name(*pst_coords) == 'America/Los_Angeles'
5858

5959

6060
def test_localize(pst_coords):
@@ -64,8 +64,8 @@ def test_localize(pst_coords):
6464

6565

6666
def test_localize_dst(ambiguous_date, pst_coords):
67-
dt_no_dst = date.localize(ambiguous_date, *pst_coords, False)
68-
dt_dst = date.localize(ambiguous_date, *pst_coords, True)
67+
dt_no_dst = date.localize(ambiguous_date, *pst_coords, is_dst=False)
68+
dt_dst = date.localize(ambiguous_date, *pst_coords, is_dst=True)
6969
jd_no_dst = date.to_jd(dt_no_dst)
7070
jd_dst = date.to_jd(dt_dst)
7171
assert dt_no_dst.hour == dt_dst.hour

0 commit comments

Comments
 (0)