TLE epoch precision - ephem vs sgp4

So… this TLE has given me some problems lately… I’m making this post just to document the adventure and leave an open loop and data point for a TLE that was valid for some software but not all.

Brief background

I’ve spent the last couple weeks updating my “shadow archive” of observations metadata, used for e.g. Year Review 2022. I’ve recently finished adding the demoddata information and downloading (most of) the data from those decoded frames, and tagging each frame with the az / el / range vector at that time. Doing so makes generating the following polar histogram of decoded frames take only ~5 seconds to extract, collate, and render: (station #834)


Someone a few years ago generated similar plots (remind me who please!)

Back to this TLE of destiny.

TLE epoch time

The issue is the epoch time of 2023, day 175.2.

PyEphem does not have a problem with this TLE, as evidenced by the observations which used it (them?) and from directly using pyephem in a python REPL:

I used Skyfield for the calculations, mostly as an excuse to play with it and see if/how it would work for SatNOGS. It uses python-sgp4 for its EarthSatellite objects and parsing TLEs.

python-sgp4 chokes on this value, because the day turns out to be 2023-06-24 at 04:47 and 59.999999998137355 seconds. Rounding happens and we get a seconds value of 60 seconds on a date where there certainly was NOT a leap second. Skyfiled thus chokes on the exception and … we are at this post :slight_smile:

My version of sgp4 is apparently NOT using the C++ code, but the pure Python translation:

In [15]: import sgp4.api

In [16]: sgp4.api.accelerated
Out[16]: False

No conclusions

@fredy used @cgbsat strf to fit those elements to prior observations, so by implication strf had no problem with the epoch value. Or perhaps the internal number was OK, but a tiny round-the-unfortunate-direction happend in the printf()?

sgp4 handles the fractional day number with Python’s native divmod() and other non-fancy operations sgp4.functions.days2mdhms(year, days).

I suspect that this could be addressed by checking that the TLE’s values exported from the tool not only is the closest to the tool’s internal values, but also round-trips back from the lower-precision output format. Is this worth the effort? Depends on whether you use import sgp4 or import ephem :person_shrugging:.

When you try to parse and process 57M frames, you are bound to find interesting corner cases!

$ sqlite3 demoddata.db 'select count(*) from obs_demoddata;'

Hope you enjoyed the read.


This is an interesting bug that you’ve found. I’m a bit surprised that python-sgp4 is choking on this, as internally I would expect it to have no need to convert the epoch into hours, minutes and seconds, except for perhaps printing.

1 Like

Which TLE specifically, I tried the one in the linked post with python-sgp4 and it loads fine. (Can even print the epoch using sat_epoch_datetime from the sgp4.conveniences package.)

Also this could be related. sgp4.ext.days2mdhms returns seconds value of 60 causing datetime failure · Issue #82 · brandon-rhodes/python-sgp4 · GitHub

I have version 2.21 of python-sgp4 installed on Python 3.10.6


Thanks for the reminder to check versions @KD9KCK

The plot thickens, a little, then dissipates.

I saw the bug in the version that was picked up by conda environment, into which I recently added skyfield. But, that turned out to install python-sgp4 v2.20, which is before the issue cited and addressed in a later version.

My pip-installed version uses python-sgp4 v2.22, which is the latest release. It also is the version of the code I was looking at for playing around with how the divmod is done.

The issue and fix of sgp4.ext.days2mdhms returns seconds value of 60 causing datetime failure · Issue #82 · brandon-rhodes/python-sgp4 · GitHub indeed does fix the issue.

Confirmed this by testing every day+fraction to the TLE’s 8 digits of (base 10) precision. One screen of code and a VPS with nothing better to do for a few hours. Empirically satisfied. The 8 digits of input are still much fewer than the ~15 digits of precision allowed by the double-precision floats used in the calculations.

It looks like both the original Vallado C++ and thus python-sgp4 use days2mdhms() to split the day+fraction to (month, day, hours, minutes, seconds),

  • calls jday() to get the corresponding Julian Day
  • Subtracts a constant to get CNES JD, epoch of 0:00 January 1, 1950. Plus 1, I presume to compensate for JD counting from 1 instead of fractional day-of-year starting at 0 (?)
  • Calls sgp4init() which does its thing. Stopped digging here. So many different time epochs are happening, wow.

Tempted to look closer and avoid the conversion through MDHMS to arrive at (pick-your-own)JD. It bugs my numerical precision twitch, but who am I to mess with Vallado’s code when I’m still working slowly through Bate, Mueller, and White :sweat_smile:

Still haven’t figured out why conda really wants to install an old version, but I’ve moved away from using it for new projects anyway. So #wontfix