solarmoonpy 1.0.5__py3-none-any.whl → 1.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solarmoonpy/location.py +67 -2
- solarmoonpy/sun.py +112 -1
- solarmoonpy/version.py +1 -1
- {solarmoonpy-1.0.5.dist-info → solarmoonpy-1.0.6.dist-info}/METADATA +1 -1
- solarmoonpy-1.0.6.dist-info/RECORD +10 -0
- solarmoonpy-1.0.5.dist-info/RECORD +0 -10
- {solarmoonpy-1.0.5.dist-info → solarmoonpy-1.0.6.dist-info}/WHEEL +0 -0
- {solarmoonpy-1.0.5.dist-info → solarmoonpy-1.0.6.dist-info}/licenses/LICENSE +0 -0
- {solarmoonpy-1.0.5.dist-info → solarmoonpy-1.0.6.dist-info}/top_level.txt +0 -0
solarmoonpy/location.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import date, datetime, timezone
|
|
3
3
|
from zoneinfo import ZoneInfo
|
|
4
|
-
from typing import Optional, Union
|
|
4
|
+
from typing import Optional, Union, Literal
|
|
5
5
|
|
|
6
|
-
from .sun import sunrise_sunset, noon # Importar funciones de sun.py
|
|
6
|
+
from .sun import sunrise_sunset, noon, dawn, dusk, midnight # Importar funciones de sun.py
|
|
7
7
|
|
|
8
8
|
@dataclass
|
|
9
9
|
class LocationInfo:
|
|
@@ -308,3 +308,68 @@ class Location:
|
|
|
308
308
|
"noon": noon_time,
|
|
309
309
|
"sunset": sunset,
|
|
310
310
|
}
|
|
311
|
+
|
|
312
|
+
def dawn(
|
|
313
|
+
self,
|
|
314
|
+
date: Optional[date] = None,
|
|
315
|
+
local: bool = True,
|
|
316
|
+
twilight_type: Literal["civil", "nautical", "astronomical"] = "civil",
|
|
317
|
+
elevation: Optional[float] = None
|
|
318
|
+
) -> Optional[datetime]:
|
|
319
|
+
"""🌅 Calcula amanecer (inicio crepúsculo)."""
|
|
320
|
+
if local and self.timezone is None:
|
|
321
|
+
raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
|
|
322
|
+
|
|
323
|
+
date = date or datetime.now(self.tzinfo if local else timezone.utc).date()
|
|
324
|
+
elevation = elevation if elevation is not None else self.elevation
|
|
325
|
+
|
|
326
|
+
return dawn(
|
|
327
|
+
latitude=self.latitude,
|
|
328
|
+
longitude=self.longitude,
|
|
329
|
+
date=date,
|
|
330
|
+
twilight_type=twilight_type,
|
|
331
|
+
elevation=elevation,
|
|
332
|
+
timezone=self.tzinfo if local else timezone.utc,
|
|
333
|
+
with_refraction=True
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def dusk(
|
|
337
|
+
self,
|
|
338
|
+
date: Optional[date] = None,
|
|
339
|
+
local: bool = True,
|
|
340
|
+
twilight_type: Literal["civil", "nautical", "astronomical"] = "civil",
|
|
341
|
+
elevation: Optional[float] = None
|
|
342
|
+
) -> Optional[datetime]:
|
|
343
|
+
"""🌙 Calcula anochecer (fin crepúsculo)."""
|
|
344
|
+
if local and self.timezone is None:
|
|
345
|
+
raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
|
|
346
|
+
|
|
347
|
+
date = date or datetime.now(self.tzinfo if local else timezone.utc).date()
|
|
348
|
+
elevation = elevation if elevation is not None else self.elevation
|
|
349
|
+
|
|
350
|
+
return dusk(
|
|
351
|
+
latitude=self.latitude,
|
|
352
|
+
longitude=self.longitude,
|
|
353
|
+
date=date,
|
|
354
|
+
twilight_type=twilight_type,
|
|
355
|
+
elevation=elevation,
|
|
356
|
+
timezone=self.tzinfo if local else timezone.utc,
|
|
357
|
+
with_refraction=True
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def midnight(
|
|
361
|
+
self,
|
|
362
|
+
date: Optional[date] = None,
|
|
363
|
+
local: bool = True
|
|
364
|
+
) -> datetime:
|
|
365
|
+
"""🌑 Calcula medianoche solar."""
|
|
366
|
+
if local and self.timezone is None:
|
|
367
|
+
raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
|
|
368
|
+
|
|
369
|
+
date = date or datetime.now(self.tzinfo if local else timezone.utc).date()
|
|
370
|
+
|
|
371
|
+
return midnight(
|
|
372
|
+
longitude=self.longitude,
|
|
373
|
+
date=date,
|
|
374
|
+
timezone=self.tzinfo if local else timezone.utc
|
|
375
|
+
)
|
solarmoonpy/sun.py
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from math import acos, asin, atan2, cos, degrees, radians, sin, tan, sqrt
|
|
3
|
+
from typing import Optional, Tuple, Literal
|
|
4
|
+
|
|
5
|
+
# Constantes estándar para diferentes eventos solares
|
|
6
|
+
SUN_APPARENT_RADIUS = 959.63 / 3600.0 # ≈ 0.2666° (radio aparente del Sol)
|
|
7
|
+
|
|
8
|
+
# Zenith angles estándar (90° = horizonte)
|
|
9
|
+
ZENITH_SUNRISE_SUNSET = 90.0 + SUN_APPARENT_RADIUS
|
|
10
|
+
ZENITH_CIVIL_DAWN_DUSK = 96.0
|
|
11
|
+
ZENITH_NAUTICAL_DAWN_DUSK = 102.0
|
|
12
|
+
ZENITH_ASTRONOMICAL_DAWN_DUSK = 108.0
|
|
3
13
|
|
|
4
14
|
def julianday(date_or_dt: datetime.date | datetime.datetime) -> float:
|
|
5
15
|
"""
|
|
@@ -239,4 +249,105 @@ def noon(
|
|
|
239
249
|
time_utc = (720.0 - (4 * longitude) - eqtime) # minutos
|
|
240
250
|
noon_utc = datetime.datetime(date.year, date.month, date.day, tzinfo=datetime.timezone.utc) \
|
|
241
251
|
+ datetime.timedelta(minutes=time_utc)
|
|
242
|
-
return noon_utc.astimezone(timezone)
|
|
252
|
+
return noon_utc.astimezone(timezone)
|
|
253
|
+
|
|
254
|
+
def dawn(
|
|
255
|
+
latitude: float, longitude: float, date: datetime.date,
|
|
256
|
+
twilight_type: Literal["civil", "nautical", "astronomical"] = "civil",
|
|
257
|
+
elevation: float = 0.0,
|
|
258
|
+
timezone: datetime.timezone = datetime.timezone.utc,
|
|
259
|
+
with_refraction: bool = True
|
|
260
|
+
) -> Optional[datetime.datetime]:
|
|
261
|
+
"""🌅 Calcula amanecer (inicio crepúsculo)."""
|
|
262
|
+
zeniths = {"civil": 96.0, "nautical": 102.0, "astronomical": 108.0}
|
|
263
|
+
zenith = zeniths[twilight_type]
|
|
264
|
+
|
|
265
|
+
# Reutiliza la misma lógica que sunrise_sunset pero para un zenith específico
|
|
266
|
+
adjustment_for_elevation = adjust_to_horizon(elevation)
|
|
267
|
+
adjusted_zenith = zenith + adjustment_for_elevation
|
|
268
|
+
if with_refraction:
|
|
269
|
+
adjusted_zenith += refraction_at_zenith(adjusted_zenith)
|
|
270
|
+
|
|
271
|
+
latitude = max(min(latitude, 89.8), -89.8)
|
|
272
|
+
jd = julianday(date)
|
|
273
|
+
jc = julianday_to_juliancentury(jd)
|
|
274
|
+
declination = sun_declination(jc)
|
|
275
|
+
eqtime = eq_of_time(jc)
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
ha_rising = hour_angle(latitude, declination, adjusted_zenith, "rising")
|
|
279
|
+
except ValueError:
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
delta = -longitude - degrees(ha_rising)
|
|
283
|
+
time_utc_minutes = 720.0 + (delta * 4.0) - eqtime
|
|
284
|
+
if time_utc_minutes < -720.0:
|
|
285
|
+
time_utc_minutes += 1440
|
|
286
|
+
|
|
287
|
+
base_dt = datetime.datetime(date.year, date.month, date.day, tzinfo=datetime.timezone.utc)
|
|
288
|
+
result_utc = base_dt + datetime.timedelta(minutes=time_utc_minutes)
|
|
289
|
+
return result_utc.astimezone(timezone)
|
|
290
|
+
|
|
291
|
+
def dusk(
|
|
292
|
+
latitude: float, longitude: float, date: datetime.date,
|
|
293
|
+
twilight_type: Literal["civil", "nautical", "astronomical"] = "civil",
|
|
294
|
+
elevation: float = 0.0,
|
|
295
|
+
timezone: datetime.timezone = datetime.timezone.utc,
|
|
296
|
+
with_refraction: bool = True
|
|
297
|
+
) -> Optional[datetime.datetime]:
|
|
298
|
+
"""🌙 Calcula anochecer (fin crepúsculo)."""
|
|
299
|
+
zeniths = {"civil": 96.0, "nautical": 102.0, "astronomical": 108.0}
|
|
300
|
+
zenith = zeniths[twilight_type]
|
|
301
|
+
|
|
302
|
+
adjustment_for_elevation = adjust_to_horizon(elevation)
|
|
303
|
+
adjusted_zenith = zenith + adjustment_for_elevation
|
|
304
|
+
if with_refraction:
|
|
305
|
+
adjusted_zenith += refraction_at_zenith(adjusted_zenith)
|
|
306
|
+
|
|
307
|
+
latitude = max(min(latitude, 89.8), -89.8)
|
|
308
|
+
jd = julianday(date)
|
|
309
|
+
jc = julianday_to_juliancentury(jd)
|
|
310
|
+
declination = sun_declination(jc)
|
|
311
|
+
eqtime = eq_of_time(jc)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
ha_setting = hour_angle(latitude, declination, adjusted_zenith, "setting")
|
|
315
|
+
except ValueError:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
delta = -longitude - degrees(ha_setting)
|
|
319
|
+
time_utc_minutes = 720.0 + (delta * 4.0) - eqtime
|
|
320
|
+
if time_utc_minutes < -720.0:
|
|
321
|
+
time_utc_minutes += 1440
|
|
322
|
+
|
|
323
|
+
base_dt = datetime.datetime(date.year, date.month, date.day, tzinfo=datetime.timezone.utc)
|
|
324
|
+
result_utc = base_dt + datetime.timedelta(minutes=time_utc_minutes)
|
|
325
|
+
return result_utc.astimezone(timezone)
|
|
326
|
+
|
|
327
|
+
def midnight(
|
|
328
|
+
longitude: float,
|
|
329
|
+
date: datetime.date,
|
|
330
|
+
timezone: datetime.timezone = datetime.timezone.utc
|
|
331
|
+
) -> datetime.datetime:
|
|
332
|
+
"""🌑 Medianoche solar (12h después del mediodía solar)."""
|
|
333
|
+
noon_time = noon(longitude, date, timezone)
|
|
334
|
+
return noon_time + datetime.timedelta(hours=12)
|
|
335
|
+
|
|
336
|
+
# 🎁 FUNCIÓN BONUS: Todo junto (opcional)
|
|
337
|
+
def all_sun_events(
|
|
338
|
+
latitude: float, longitude: float, date: datetime.date,
|
|
339
|
+
twilight_type: Literal["civil", "nautical", "astronomical"] = "civil",
|
|
340
|
+
elevation: float = 0.0,
|
|
341
|
+
timezone: datetime.timezone = datetime.timezone.utc,
|
|
342
|
+
with_refraction: bool = True
|
|
343
|
+
) -> dict:
|
|
344
|
+
"""🔥 Calcula TODOS los eventos solares del día."""
|
|
345
|
+
sr, ss = sunrise_sunset(latitude, longitude, date, elevation, timezone, with_refraction)
|
|
346
|
+
return {
|
|
347
|
+
"dawn": dawn(latitude, longitude, date, twilight_type, elevation, timezone, with_refraction),
|
|
348
|
+
"sunrise": sr,
|
|
349
|
+
"noon": noon(longitude, date, timezone),
|
|
350
|
+
"sunset": ss,
|
|
351
|
+
"dusk": dusk(latitude, longitude, date, twilight_type, elevation, timezone, with_refraction),
|
|
352
|
+
"midnight": midnight(longitude, date, timezone)
|
|
353
|
+
}
|
solarmoonpy/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "1.0.
|
|
2
|
+
__version__ = "1.0.6"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
solarmoonpy/__init__.py,sha256=cS0ZjePNX75usK8mgvbk_uguVpXRixqZU4zjrEtg42c,722
|
|
2
|
+
solarmoonpy/location.py,sha256=XC-d0g2kjLdTJyYDokz8m3C5bKZ7h9xy6rsRf40uQ84,12764
|
|
3
|
+
solarmoonpy/moon.py,sha256=yhJSsED7AWK3nF79fjqFlCeqYjiXXFhqGE36Nyfp0qk,19699
|
|
4
|
+
solarmoonpy/sun.py,sha256=K31_btucloC5WYnltFb_X_3hF1Xk0_5aQ1hRSrYP7C8,13396
|
|
5
|
+
solarmoonpy/version.py,sha256=C1AoWYSu92cpPGAfSGtTeKE9EGde-yUoSv0cT_BVTJM,35
|
|
6
|
+
solarmoonpy-1.0.6.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
7
|
+
solarmoonpy-1.0.6.dist-info/METADATA,sha256=hBdLEmwc3QWazEVQKaWF63k6pMVrtUUmiLoshnV6nD4,15023
|
|
8
|
+
solarmoonpy-1.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
solarmoonpy-1.0.6.dist-info/top_level.txt,sha256=egsoDe9E0QEu5GfAA5jSvy_3qTCWMLMe_Kh3gnM6-dY,12
|
|
10
|
+
solarmoonpy-1.0.6.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
solarmoonpy/__init__.py,sha256=cS0ZjePNX75usK8mgvbk_uguVpXRixqZU4zjrEtg42c,722
|
|
2
|
-
solarmoonpy/location.py,sha256=CcIAlghZ6QQKiQXygfaAs4aBN_7ANfoszabZycIPY30,10312
|
|
3
|
-
solarmoonpy/moon.py,sha256=yhJSsED7AWK3nF79fjqFlCeqYjiXXFhqGE36Nyfp0qk,19699
|
|
4
|
-
solarmoonpy/sun.py,sha256=dleHQWXzjnqtPAZr9ONdXiZvTVWvdW6gW775A2ASsNk,8937
|
|
5
|
-
solarmoonpy/version.py,sha256=l3Zi-Xuhc76_pomaTnxWfoGjGfeI-KyhHlKy8KJdeOA,35
|
|
6
|
-
solarmoonpy-1.0.5.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
7
|
-
solarmoonpy-1.0.5.dist-info/METADATA,sha256=aXlHhlDVG5xxhH0DwMP-1oojRDsjtZgI2he9rIOA9xM,15023
|
|
8
|
-
solarmoonpy-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
solarmoonpy-1.0.5.dist-info/top_level.txt,sha256=egsoDe9E0QEu5GfAA5jSvy_3qTCWMLMe_Kh3gnM6-dY,12
|
|
10
|
-
solarmoonpy-1.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|