solarmoonpy 1.0.5__tar.gz → 1.0.7__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solarmoonpy
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Precise solar and lunar calculations for astronomical applications
5
5
  Author-email: figorr <jdcuartero@yahoo.es>
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "solarmoonpy"
7
- version = "1.0.5"
7
+ version = "1.0.7"
8
8
  description = "Precise solar and lunar calculations for astronomical applications"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "figorr", email = "jdcuartero@yahoo.es" }]
@@ -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, Dict
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:
@@ -257,15 +257,80 @@ class Location:
257
257
  date=date,
258
258
  timezone=self.tzinfo if local else timezone.utc
259
259
  )
260
+
261
+ def dawn(
262
+ self,
263
+ date: Optional[date] = None,
264
+ local: bool = True,
265
+ twilight_type: Literal["civil", "nautical", "astronomical"] = "civil",
266
+ elevation: Optional[float] = None
267
+ ) -> Optional[datetime]:
268
+ """🌅 Calcula amanecer (inicio crepúsculo)."""
269
+ if local and self.timezone is None:
270
+ raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
260
271
 
261
- def sun_events(
272
+ date = date or datetime.now(self.tzinfo if local else timezone.utc).date()
273
+ elevation = elevation if elevation is not None else self.elevation
274
+
275
+ return dawn(
276
+ latitude=self.latitude,
277
+ longitude=self.longitude,
278
+ date=date,
279
+ twilight_type=twilight_type,
280
+ elevation=elevation,
281
+ timezone=self.tzinfo if local else timezone.utc,
282
+ with_refraction=True
283
+ )
284
+
285
+ def dusk(
262
286
  self,
263
287
  date: Optional[date] = None,
264
288
  local: bool = True,
289
+ twilight_type: Literal["civil", "nautical", "astronomical"] = "civil",
265
290
  elevation: Optional[float] = None
266
- ) -> dict:
291
+ ) -> Optional[datetime]:
292
+ """🌙 Calcula anochecer (fin crepúsculo)."""
293
+ if local and self.timezone is None:
294
+ raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
295
+
296
+ date = date or datetime.now(self.tzinfo if local else timezone.utc).date()
297
+ elevation = elevation if elevation is not None else self.elevation
298
+
299
+ return dusk(
300
+ latitude=self.latitude,
301
+ longitude=self.longitude,
302
+ date=date,
303
+ twilight_type=twilight_type,
304
+ elevation=elevation,
305
+ timezone=self.tzinfo if local else timezone.utc,
306
+ with_refraction=True
307
+ )
308
+
309
+ def midnight(
310
+ self,
311
+ date: Optional[date] = None,
312
+ local: bool = True
313
+ ) -> datetime:
314
+ """🌑 Calcula medianoche solar."""
315
+ if local and self.timezone is None:
316
+ raise ValueError("Se solicitó hora local pero no se definió una zona horaria.")
317
+
318
+ date = date or datetime.now(self.tzinfo if local else timezone.utc).date()
319
+
320
+ return midnight(
321
+ longitude=self.longitude,
322
+ date=date,
323
+ timezone=self.tzinfo if local else timezone.utc
324
+ )
325
+
326
+ def sun_events(
327
+ self,
328
+ date: Optional[date] = None,
329
+ local: bool = True,
330
+ elevation: Optional[float] = None,
331
+ ) -> Dict:
267
332
  """
268
- Devuelve un diccionario con sunrise, noon y sunset para una fecha dada.
333
+ Devuelve un diccionario con los eventos solares para una fecha dada, incluyendo todos los tipos de crepúsculo.
269
334
 
270
335
  Args:
271
336
  date: Fecha para la cual calcular los eventos solares. Si es None, usa la fecha actual.
@@ -274,9 +339,17 @@ class Location:
274
339
 
275
340
  Returns:
276
341
  dict: {
342
+ "dawn_civil": datetime | None,
343
+ "dawn_nautical": datetime | None,
344
+ "dawn_astronomical": datetime | None,
277
345
  "sunrise": datetime,
278
346
  "noon": datetime,
279
- "sunset": datetime
347
+ "sunset": datetime,
348
+ "dusk_civil": datetime | None,
349
+ "dusk_nautical": datetime | None,
350
+ "dusk_astronomical": datetime | None,
351
+ "midnight": datetime,
352
+ "daylight_duration": float | None
280
353
  }
281
354
  """
282
355
  if local and self.timezone is None:
@@ -303,8 +376,84 @@ class Location:
303
376
  timezone=tz,
304
377
  )
305
378
 
379
+ # Calcular medianoche solar
380
+ midnight_time = midnight(
381
+ longitude=self.longitude,
382
+ date=date,
383
+ timezone=tz,
384
+ )
385
+
386
+ # Calcular duración del día (en horas)
387
+ daylight_duration = None
388
+ if sunrise and sunset:
389
+ daylight_duration = (sunset - sunrise).total_seconds() / 3600
390
+
391
+ # Calcular dawn y dusk para cada tipo de crepúsculo
392
+ dawn_civil = dawn(
393
+ latitude=self.latitude,
394
+ longitude=self.longitude,
395
+ date=date,
396
+ twilight_type="civil",
397
+ elevation=elevation,
398
+ timezone=tz,
399
+ with_refraction=True,
400
+ )
401
+ dusk_civil = dusk(
402
+ latitude=self.latitude,
403
+ longitude=self.longitude,
404
+ date=date,
405
+ twilight_type="civil",
406
+ elevation=elevation,
407
+ timezone=tz,
408
+ with_refraction=True,
409
+ )
410
+ dawn_nautical = dawn(
411
+ latitude=self.latitude,
412
+ longitude=self.longitude,
413
+ date=date,
414
+ twilight_type="nautical",
415
+ elevation=elevation,
416
+ timezone=tz,
417
+ with_refraction=True,
418
+ )
419
+ dusk_nautical = dusk(
420
+ latitude=self.latitude,
421
+ longitude=self.longitude,
422
+ date=date,
423
+ twilight_type="nautical",
424
+ elevation=elevation,
425
+ timezone=tz,
426
+ with_refraction=True,
427
+ )
428
+ dawn_astronomical = dawn(
429
+ latitude=self.latitude,
430
+ longitude=self.longitude,
431
+ date=date,
432
+ twilight_type="astronomical",
433
+ elevation=elevation,
434
+ timezone=tz,
435
+ with_refraction=True,
436
+ )
437
+ dusk_astronomical = dusk(
438
+ latitude=self.latitude,
439
+ longitude=self.longitude,
440
+ date=date,
441
+ twilight_type="astronomical",
442
+ elevation=elevation,
443
+ timezone=tz,
444
+ with_refraction=True,
445
+ )
446
+
306
447
  return {
448
+ "dawn_civil": dawn_civil,
449
+ "dawn_nautical": dawn_nautical,
450
+ "dawn_astronomical": dawn_astronomical,
307
451
  "sunrise": sunrise,
308
452
  "noon": noon_time,
309
453
  "sunset": sunset,
310
- }
454
+ "dusk_civil": dusk_civil,
455
+ "dusk_nautical": dusk_nautical,
456
+ "dusk_astronomical": dusk_astronomical,
457
+ "midnight": midnight_time,
458
+ "daylight_duration": daylight_duration,
459
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ # version.py
2
+ __version__ = "1.0.7"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solarmoonpy
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Precise solar and lunar calculations for astronomical applications
5
5
  Author-email: figorr <jdcuartero@yahoo.es>
6
6
  License: Apache License
@@ -1,2 +0,0 @@
1
- # version.py
2
- __version__ = "1.0.5"
File without changes
File without changes
File without changes