skyfielders / python-skyfield

Elegant astronomy for Python
MIT License
1.43k stars 213 forks source link

utc_strftime gives observers timezone #1006

Closed jake9wi closed 1 month ago

jake9wi commented 1 month ago

Skyfield function utc_strftime gives observers timezone.

Expected

2024-Sep-26 11:39:38 UTC
2024-Sep-26 11:11:55 UTC
2024-Sep-26 10:39:33 UTC
2024-Sep-26 10:06:41 UTC

Result

2024-Sep-26 11:39:38 Central Standard Time
2024-Sep-26 11:11:55 Central Standard Time
2024-Sep-26 10:39:33 Central Standard Time
2024-Sep-26 10:06:41 Central Standard Time

Version

Name Version Build Channel
skyfield 1.49 pyhff2d567_0 conda-forge

My Code

from skyfield.api import load, wgs84
from skyfield import almanac

# Load ephemeris data
eph = load('de440.bsp')
ts = load.timescale()

# Define location
latitude = 41.7093
longitude = -86.8639
elev = 186.927  # Elevation in meters 
loc = wgs84.latlon(latitude, longitude, elevation_m=elev)
observer = eph['Earth'] + loc

# Define the time for the date you want
t0 = ts.utc(2024, 9, 26)
t1 = ts.utc(2024, 9, 27)

def sun_rise(eph, observer, t0, t1):

    f = almanac.find_risings(
            target = eph['Sun'],
            observer = observer,
            start_time = t0,
            end_time = t1,
        )

    return f

def twilight_rise(eph, observer, t0, t1, hor):
    if hor == 'c':
        horizon = -6
    elif hor == 'n':
        horizon = -12
    elif hor == 'a':
        horizon = -18

    f = almanac.find_risings(
            target = eph['Sun'],
            observer = observer,
            start_time = t0,
            end_time = t1,
            horizon_degrees = horizon,
        )

    return f

ttr = sun_rise(eph, observer, t0, t1)
print(ttr[0].utc_strftime("%Y-%b-%d %H:%M:%S %Z")[0])

ttc = twilight_rise(eph, observer, t0, t1, 'c')
print(ttc[0].utc_strftime("%Y-%b-%d %H:%M:%S %Z")[0])

ttn = twilight_rise(eph, observer, t0, t1, 'n')
print(ttn[0].utc_strftime("%Y-%b-%d %H:%M:%S %Z")[0])

tta = twilight_rise(eph, observer, t0, t1, 'a')
print(tta[0].utc_strftime("%Y-%b-%d %H:%M:%S %Z")[0])
beluej123 commented 1 month ago

I believe, if you remove the printing format info from utc_strftime(), you will get what you r looking for: change from: utc_strftime("%Y-%b-%d %H:%M:%S %Z") to: utc_strftime()

In particular, the formatting you are asking for includes %Z which is time-zone info, but the tt (i.e. Julian) output from sun_rise() and twilight_rise() does not have time-zone info. I do not have a clue why the utc_strftime(... %Z) outputs Central Standard Time...

Below are a couple other notes: If you wish to see how I get local time-zone date/time, I made a few adjustments to your code, shown below (note my variable ttr_local). I added a few comments to your code I thought may be helpful...

In my opinion, I think skyfield's time-zone doc's could use updates to show how to convert between time-zones. For example: (1) converting tt-time (Julian) to UTC (for user display) and any of the other defined time-zones, and (2) converting a user entered time (with time-zone) to a tt-time and display UTC. I realize it is useful that skyfield's doc's are mindful of backward compatibility since some users may still use the 3rd party package pytz. However, I think this is especially germane now that the pytz module is not recommended by the python doc's, because python 3.9+ has its own time-zone methods...

If you wish to see how I get local time-zone date/time, I made a few adjustments to your code, shown below.

"""
    [skyfielders/python-skyfield] utc_strftime gives observers timezone (Issue #1006)
    Explore skyfield functions:
        (1) sunrise sunset
        (2) in-progress, equinox; astronomical & local (approx. equilux)
        (3) timezone conversions;
            I think skyfield's timezone doc's could use updates

    Notes:
    ----------
        Sunrise/sunset angles (https://aa.usno.navy.mil/faq/RST_defs):
            astronomical = +- 18deg
            nautical twilight = +-12deg
            civil twilight = +-6deg
            sunrise sunset = 0deg

        https://rhodesmill.org/skyfield/examples.html#dark-twilight-day-example
        https://rhodesmill.org/skyfield/api-almanac.html
        https://rhodesmill.org/skyfield/time.html
        https://rhodesmill.org/skyfield/searches.html
        https://www.timeanddate.com/astronomy/equilux.html

    Returns
    -------
        None
    """
    from zoneinfo import ZoneInfo

    from skyfield import almanac
    from skyfield.api import load, wgs84

    def sun_rise(eph, observer, t0, t1):

        f = almanac.find_risings(
            target=eph["Sun"],
            observer=observer,
            start_time=t0,
            end_time=t1,
        )

        return f

    def twilight_rise(eph, observer, t0, t1, hor):
        # c=civil, n=nautical, a=astronical
        if hor == "c":
            horizon = -6
        elif hor == "n":
            horizon = -12
        elif hor == "a":
            horizon = -18

        f = almanac.find_risings(
            target=eph["Sun"],
            observer=observer,
            start_time=t0,
            end_time=t1,
            horizon_degrees=horizon,
        )

        return f

    # Load ephemeris data
    eph = load("de440.bsp")
    ts = load.timescale()

    # Define location, US/central timezone
    latitude = 41.7093
    longitude = -86.8639
    elev = 186.927  # Elevation in meters
    loc = wgs84.latlon(latitude, longitude, elevation_m=elev)
    observer = eph["Earth"] + loc

    # Define the time for the date you want
    t0 = ts.utc(2024, 9, 26)
    t1 = ts.utc(2024, 9, 27)

    # print date/time format
    prt_t_fmt = "%Y-%b-%d %H:%M:%S" # %b = a 3 letter month
    print(f"\nSunRise & timezone - github's KD9LWR issue #1006 (2024-09-26):")
    print(f"Location: {loc}")

    ttr = sun_rise(eph, observer, t0, t1) # returns tt time; no timezone info
    print(f"sunrise      : {ttr[0].utc_strftime()[0]}")
    # convert sunrise to local time, requires either:
    #   python-zoneinfo import, or pytz module (3rd party)
    ttr_local = ttr[0].astimezone(ZoneInfo('US/Central'))  # cast to central time zone
    print(f"sunrise, zone: {ttr_local[0].strftime(prt_t_fmt)} {ttr_local[0].tzname()}")

    ttc = twilight_rise(eph, observer, t0, t1, "c")
    print(f"civil        : {ttc[0].utc_strftime()[0]}")

    ttn = twilight_rise(eph, observer, t0, t1, "n")
    print(f"nautical     : {ttn[0].utc_strftime()[0]}")

    tta = twilight_rise(eph, observer, t0, t1, "a")
    print(f"astronomical : {tta[0].utc_strftime()[0]}")
brandon-rhodes commented 1 month ago

Skyfield function utc_strftime gives observers timezone.

Well, drat — I had hoped that by listing the supported %-escapes in the utc_strftime() docstring, I would successfully steer programmers away from trying unsupported escape sequences.

https://github.com/skyfielders/python-skyfield/blob/04d6a6341daada705578cf637147ce2f809300cf/skyfield/timelib.py#L618

The underlying call that I'm making here to time.strftime() does not, alas, let me customize the return value for %Z. (I use that Standard Library routine because not only am I not interested in re-implementing it, but because a Python version would be very slow compared to the C-powered version in the Standard Library.)

https://docs.python.org/3/library/time.html#time.strftime

So there are three possibilities here.

  1. Add a warning to the Skyfield documentation listing the %-codes that are not supported.
  2. Raise an error if %Z is attempted.
  3. Do a simple .replace() call on the input string to replace %Z with UTC before strftime() has a chance to see it.

I'm leaning toward the third option. Though it bothers me that it will slow up all users of .utc_strftime() forever, even though all of them to this point have simply avoided using %Z. Hmm. So maybe I'll go with option 1? We'll see how I feel tomorrow!

brandon-rhodes commented 1 month ago

I have updated the method's documentation!

https://github.com/skyfielders/python-skyfield/commit/f9249d5bd2d2df0d71781fb4006523432ad0566d

Hopefully this prevents confusion in the future.