sffjunkie / astral

Python calculations for the position of the sun and moon.
Apache License 2.0
242 stars 47 forks source link

On only some days: ValueError: Unable to find a dusk time on the date specified #86

Open BetterAutomations opened 1 year ago

BetterAutomations commented 1 year ago

For a particular location within the southeastern United States on some days, requesting an astral.sun generates a ValueError, but on previous and following days in the same location, the correct values are returned. This location always experiences dusk.

I only need sunrise and sunset so I attempted to use astral.sun.sunset alone, but this causes the same issue to appears on a different day, but warning that it is "Unable to find a sunset time". I do not recall exactly which day but it was within April.

Tested another library (suntime) and it handled this location with all days in 2023 without any issues.

Sample code:

from datetime import datetime
from astral.sun import sun
from astral import Observer

sun(Observer(30, -82), datetime(2023, 3, 10))
sun(Observer(30, -82), datetime(2023, 3, 11))
sun(Observer(30, -82), datetime(2023, 3, 12))
sun(Observer(30, -82), datetime(2023, 3, 13))
sun(Observer(30, -82), datetime(2023, 3, 14))
sun(Observer(30, -82), datetime(2023, 3, 15))
sun(Observer(30, -82), datetime(2023, 3, 16))
sun(Observer(30, -82), datetime(2023, 3, 17))
sun(Observer(30, -82), datetime(2023, 3, 18))
sun(Observer(30, -82), datetime(2023, 3, 19))
sun(Observer(30, -82), datetime(2023, 3, 20))

Output:

(SecureCoop) root@api02:/SecureCoop# python
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> from astral.sun import sun
>>> from astral import Observer
>>> sun(Observer(30, -82), datetime(2023, 3, 10))
{'dawn': datetime.datetime(2023, 3, 10, 11, 19, 49, 114342, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 10, 11, 44, 9, 112599, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 10, 17, 38, 26, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 10, 23, 32, 47, 795392, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 10, 23, 57, 9, 354083, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 11))
{'dawn': datetime.datetime(2023, 3, 11, 11, 18, 39, 517805, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 11, 11, 42, 58, 996090, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 11, 17, 38, 10, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 11, 23, 33, 26, 601529, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 11, 23, 57, 47, 689061, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 12))
{'dawn': datetime.datetime(2023, 3, 12, 11, 17, 29, 461424, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 12, 11, 41, 48, 511143, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 12, 17, 37, 55, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 12, 23, 34, 5, 165001, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 12, 23, 58, 25, 873095, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 13))
{'dawn': datetime.datetime(2023, 3, 13, 11, 16, 18, 973837, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 13, 11, 40, 37, 686887, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 13, 17, 37, 39, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 13, 23, 34, 43, 497721, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 13, 23, 59, 3, 918562, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 14))
{'dawn': datetime.datetime(2023, 3, 14, 11, 15, 8, 83636, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 14, 11, 39, 26, 552340, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 14, 17, 37, 22, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 14, 23, 35, 21, 611771, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 14, 23, 59, 41, 837950, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 15))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/SecureCoop/lib/python3.10/site-packages/astral/sun.py", line 1267, in sun
    "dusk": dusk(observer, date, dawn_dusk_depression, tzinfo),
  File "/SecureCoop/lib/python3.10/site-packages/astral/sun.py", line 962, in dusk
    raise ValueError("Unable to find a dusk time on the date specified")
ValueError: Unable to find a dusk time on the date specified
>>> sun(Observer(30, -82), datetime(2023, 3, 16))
{'dawn': datetime.datetime(2023, 3, 16, 11, 12, 45, 209520, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 16, 11, 37, 3, 467906, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 16, 17, 36, 49, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 16, 23, 36, 37, 232881, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 16, 0, 0, 19, 643824, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 17))
{'dawn': datetime.datetime(2023, 3, 17, 11, 11, 33, 282549, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 17, 11, 35, 51, 575511, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 17, 17, 36, 32, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 17, 23, 37, 14, 764700, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 17, 0, 0, 57, 348801, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 18))
{'dawn': datetime.datetime(2023, 3, 18, 11, 10, 21, 66848, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 18, 11, 34, 39, 487807, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 18, 17, 36, 14, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 18, 23, 37, 52, 127314, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 18, 0, 1, 34, 965517, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 19))
{'dawn': datetime.datetime(2023, 3, 19, 11, 9, 8, 590765, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 19, 11, 33, 27, 233264, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 19, 17, 35, 57, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 19, 23, 38, 29, 333225, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 19, 0, 2, 12, 506606, tzinfo=datetime.timezone.utc)}
>>> sun(Observer(30, -82), datetime(2023, 3, 20))
{'dawn': datetime.datetime(2023, 3, 20, 11, 7, 55, 882599, tzinfo=datetime.timezone.utc), 'sunrise': datetime.datetime(2023, 3, 20, 11, 32, 14, 840246, tzinfo=datetime.timezone.utc), 'noon': datetime.datetime(2023, 3, 20, 17, 35, 39, tzinfo=datetime.timezone.utc), 'sunset': datetime.datetime(2023, 3, 20, 23, 39, 6, 394940, tzinfo=datetime.timezone.utc), 'dusk': datetime.datetime(2023, 3, 20, 0, 2, 49, 984667, tzinfo=datetime.timezone.utc)}
>>>
lwchkg commented 1 year ago

Looks like you should have Observer(-82, 30) instead of Observer(30, -82).

Anyway, still think that the library should not throw if the dusk time does not exist, because the input is valid. You should probably return None instead, or some custom values if you want to distinguish "the Sun is always up" and "the Sun is always down".

BetterAutomations commented 1 year ago

No, I had it correctly. Observer() is latitude/longitude and the location is 30 latitude and -82 longitude.

lwchkg commented 1 year ago

Oh... sorry for my mistake. Looks like I've written about another thing instead.

BTW, looks like the 'dusk' time is not on that day you enter acts funny. Either it return the dusk of the previous/next sun rise (on the specified day) or return an error if that of the previous/next day is also not on the day you specify.

To add to the injury the calculations appear to be in UTC instead of local time, so the library crashes even in normal conditions.

mrherman commented 1 year ago

Did anything ever get figured out on this issue. I recently started having the same issue, for a location that definitely experiences dusk. Seems to be an issue in >=3.0 versions (for me). Python 3.10

BetterAutomations commented 1 year ago

Never found a fix. I switched to the suntime library instead.

chipguyhere commented 1 year ago

I got caught up on this as well. Proof of concept code that won't run:

from astral.sun import sun from astral.geocoder import database, lookup from astral import LocationInfo from datetime import datetime, timezone

location = lookup("London", database()) # Use any city as a temporary placeholder location.latitude = 31.1442757 location.longitude = -88.9690628 s=sun(location.observer, date=datetime(2023,3,10,4,5,2,tzinfo=timezone.utc)) print(s["dawn"]) print(s["dusk"])

lwchkg commented 1 year ago

I've found a peculiarity in the code anyway: when it tries to find the sunrise/sunset time (or other times), it tries to find the sunrise/sunset at that exactly 24-hr window (in case of DST events, 23 or 25 hours).

Unfortunately, due to a range of factors (e.g. the longitude is not a multiple of 15°), the sunrise/sunset time can actually be in the previous day or the next day. I believe that we should simply use the out-of-day time right away, but astral tries to find the time of the previous or next sunrise/sunset to fit in that 24-hr window. If there is no such sunrise/sunset in the previous/next day, astral throws a ValueError.

The 24-hr window is calculated in the timezone you supplied, so unsurprisingly @chipguyhere 's code does not return the right result - his time zone supplied has almost 90° difference from the longitude, which translate to almost 6 hours.

elierpf commented 10 months ago

I had the same issue with version 3.2 of astral, definitively it raised the "no dusk error" when the civilian dusk's (the default one) time is 00:00 UTC or midnight in UTC timezone, doesn't matter the seconds after the midnight. I tested with version 2.2 and it works