Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sunset times returning for previous day #83

Open
erichorne opened this issue Dec 11, 2022 · 4 comments
Open

Sunset times returning for previous day #83

erichorne opened this issue Dec 11, 2022 · 4 comments

Comments

@erichorne
Copy link

Since astral 2.2, computing the sunset time appears to be return the time for the previous day. More accurately, if the sunset time ends up being UTC the next day, the day component of the date isn't changed. For example... Jul 10,2022 for my location sunset returns as Jul 10, 2022 03:10 UTC. I'm in the -7:00 timezone, so that would mean sunset for July 10 was computed as July 9 20;10. Clearly sunset on July 10 couldn't start the day before. I think the day component of the time should have been 11, not 10.

This code replicates the issue.
On astral 3.0, 3.1 and 3,2 it returns datetime.datetime(2022, 7, 10, 3, 10, 4, 552844, tzinfo=datetime.timezone.utc)
On astral 2.2 it returns datetime.datetime(2022, 7, 11, 3, 9, 47, 57594, tzinfo=<UTC>)

I believe the return value from astral 2.2 is the correct value.

import datetime
import astral
from astral.sun import sunset

ob = astral.Observer(latitude=34.175301, longitude=-118.983334, elevation=0.0)
sunset(ob, date=datetime.date(2022, 7, 10))

I'm open to the possibility I'm doing it wrong, but I didn't see anything in release notes that said I needed to change anything. Noticed this when upgrading from Astral 2.2 to 3.2 -- previously working code began to fail.

Thanks for any help / insights!

@mgourlay64
Copy link

Hi,

Writing this message just to confirm you that there is an issue and you're not doing it wrong.
I encounter the same problem since I update Astral to version greater than 3.
Here is my code to test it :

  from astral.sun import sun
  import datetime
  from dateutil import tz
  city = lookup("Adelaide", database())
  timezone = "Australia/Adelaide"
  from_zone = tz.gettz('UTC')
  to_zone = tz.gettz(timezone)
  
  s = sun(city.observer, date=datetime.datetime(2023, 3, 9))
  print('UTC')
  print((
      f'Dawn:    {s["dawn"]}\n'
      f'Sunrise: {s["sunrise"]}\n'
      f'Noon:    {s["noon"]}\n'
      f'Sunset:  {s["sunset"]}\n'
      f'Dusk:    {s["dusk"]}\n'
  ))
  print('Format in local')
  print((
      f'Dawn:    {s["dawn"].astimezone(to_zone)}\n'
      f'Sunrise: {s["sunrise"].astimezone(to_zone)}\n'
      f'Noon:    {s["noon"].astimezone(to_zone)}\n'
      f'Sunset:  {s["sunset"].astimezone(to_zone)}\n'
      f'Dusk:    {s["dusk"].astimezone(to_zone)}\n'
  )) 

My example is for Australia/Adelaide:
Astral == 2.2

Dawn:    2023-03-08 20:13:11.749752+00:00
Sunrise: 2023-03-08 20:39:10.681138+00:00
Noon:    2023-03-09 02:56:17+00:00
Sunset:  2023-03-09 09:12:46.501212+00:00
Dusk:    2023-03-09 09:38:41.579296+00:00

Format in local
Dawn:    2023-03-09 06:43:11.749752+10:30
Sunrise: 2023-03-09 07:09:10.681138+10:30
Noon:    2023-03-09 13:26:17+10:30
Sunset:  2023-03-09 19:42:46.501212+10:30
Dusk:    2023-03-09 20:08:41.579296+10:30

Which is correct

Astral == 3.2

Dawn:    2023-03-09 20:14:04.742910+00:00
Sunrise: 2023-03-09 20:40:01.671164+00:00
Noon:    2023-03-09 02:56:17+00:00
Sunset:  2023-03-09 09:12:46.211661+00:00
Dusk:    2023-03-09 09:38:41.547868+00:00

Format in local
Dawn:    2023-03-10 06:44:04.742910+10:30
Sunrise: 2023-03-10 07:10:01.671164+10:30
Noon:    2023-03-09 13:26:17+10:30
Sunset:  2023-03-09 19:42:46.211661+10:30
Dusk:    2023-03-09 20:08:41.547868+10:30

We can clearly see that for Dawn and sunrise, the date is in correct (the offset is about one day)

@nigelsim
Copy link

#81 and #82 are duplicates of this.

I started looking into this, but am not familiar enough with the calculates to make quick progress. My hunch is that this refactor of time_of_transit is where the behaviour change is introduced. It seems like it is returning the sunrise that occurs on that UTC day, rather than the relative to the actual location.

The best answer (or interim workaround) may be to calculate sunrise and sunset times for date-1, date, and date+1, and then calculate the timezone at the location, find the UTC time at midday, and take the closes sunrise and sunset times around that timestamp.

These are the unit tests for test_sun_utc.py that I wrote to verify the error in 3.2. Note, the calculated values are from 2.2, so may be wrong too, but, at least the sunrise < sunset.

@pytest.mark.parametrize(
    "day,sunrise",
    [
        (datetime.date(2015, 1, 1), datetime.datetime(2014, 12, 31, 16, 51)),
        (datetime.date(2015, 12, 1), datetime.datetime(2015, 11, 30, 16, 42)),
        (datetime.date(2015, 12, 2), datetime.datetime(2015, 12, 1, 16, 42)),
        (datetime.date(2015, 12, 3), datetime.datetime(2015, 12, 2, 16, 42)),
        (datetime.date(2015, 12, 12), datetime.datetime(2015, 12, 11, 16, 41)),
        (datetime.date(2015, 12, 25), datetime.datetime(2015, 12, 24, 16, 45)),
    ],
)
def test_Sunrise_west(day: datetime.date, sunrise: datetime.datetime, wellington: LocationInfo):
    sunrise = sunrise.replace(tzinfo=datetime.timezone.utc)
    sunrise_utc = sun.sunrise(wellington.observer, day)
    assert datetime_almost_equal(sunrise, sunrise_utc)


@pytest.mark.parametrize(
    "day,sunset",
    [
        (datetime.date(2015, 1, 1), datetime.datetime(2015, 1, 1, 7, 56)),
        (datetime.date(2015, 12, 1), datetime.datetime(2015, 12, 1, 7, 36)),
        (datetime.date(2015, 12, 2), datetime.datetime(2015, 12, 2, 7, 37)),
        (datetime.date(2015, 12, 3), datetime.datetime(2015, 12, 3, 7, 38)),
        (datetime.date(2015, 12, 12), datetime.datetime(2015, 12, 12, 7, 47)),
        (datetime.date(2015, 12, 25), datetime.datetime(2015, 12, 25, 7, 55)),
    ],
)
def test_Sunset_west(day: datetime.date, sunset: datetime.datetime, wellington: LocationInfo):
    sunset = sunset.replace(tzinfo=datetime.timezone.utc)
    sunset_utc = sun.sunset(wellington.observer, day)
    assert datetime_almost_equal(sunset, sunset_utc)

@shanedoolane
Copy link

shanedoolane commented May 7, 2023

Reproducible with 3.2 with these lon/lat/date combos:

start_date = datetime.date(2023, 1, 1)
latitude = 37.4
longitude = -122.1
observer = astral.sun.Observer(latitude, longitude)

def daylight_hours(current_date):
    sunrise = astral.sun.sunrise(observer, date=current_date)  # returns UTC
    sunset = astral.sun.sunset(observer, date=current_date) # returns UTC

    daylight_hours = (sunset - sunrise).total_seconds() / 3600
    return daylight_hours

daylight_hours ends up being negative which is not expected per above.

@shanedoolane
Copy link

@nigelsim want to check your unit tests with this fork? I think i fixed it. I think you found the right refactor.
@mgourlay64 / @erichorne can you see if this fork fixes your problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants