In the porting to orekit 13 we encountered some stuff with the new AbsoluteDate which we do not understand.
We do get different results from constructor using string representation and from individual parameters representation. We are using python, but I do not think that matters.
The two cases are: AbsoluteDate(2024, 12, 19, 21, 42, 55.145, utc)
and AbsoluteDate("2024-12-19T21:42:55.145Z", utc)
I think I have encountered this type of behaviour too with Orekit 13. Typically when parsing back and forth TLEs. I’d be happy to get an explanation if not a workaround.
Most probably due to the chain: string (which is decimal) → primitive double (which is IEEE754 binary) →TimeOffset (which is multiple of decimal attoseconds).
As values like 55.145 (or simply 0.1) cannot be represented exactly as IEEE754 numbers, some accuracy is lost in the middle conversions. This is the reason why in upgrading instructions we recommend to use TimeOffset.parse(field) instead of new TimeOffset(Double.parseDouble(field)) .
We most probably will find such parsing/writing caveats for a long time, we ought to fix them one by one when we find them.
I will make an update to the orekit wrapper that now makes a float for the microseconds and use the TimeOffset(x, MICROSECONDS) instead for the decimal part. Python has a limitation in datetime that it is only to microsecond precision. We cannot do anything about that but it is something to take care of in some corner cases.
Can you avoid Python datetime completely and just rely on the TimeOffset.parse method?
A limitation to microseconds would typically completely prevent any use of one way measurements (like GNSS, SLR or Two Way Time Transfer) as one microsecond at the speed of light represent a 300m error, which is huge in most applications.
In most cases one can avoid datetime completely, the AbsoluteDate class is fully “integrated” so one can keep calculation within the orekit domain as much as possible and that is indeed strongly recommended.
For us datetime is mostly used for user input-output in API’s and more “interfacing” to the orekit domain and try to keep these two quite well separated. I think the MICROSECOND approach above will avoid some errors, for example where an OEM end-of-file date (parsed in orekit from string) is not the same as if entering the end date as datetime with current (previous) version of translation through float microseconds.
Could you elaborate what you mean on relying on the TimeOffset.parse method, do you mean when going from a string representation of date in python to a AbsoluteDate?
Yes, I think we should try to avoid as most as possible any intermediate that uses a double number. The string representation is mainly decimal, just as are TimeOffset and AbsoluteDate. When parsing “55.145”, TimeOffset does not use any double. It splits the string at the ‘.’ separator, parses the two sides as long integers (55L and 145L), and then take care of the number of decimals, So it will work with a string like “55.145” that has millisecond precision, but also “55.145123” that has microseconds precision or “55.124123456” that has nanoseconds precision.
Once you have the date with full accuracy, you can decide to round it later down to microseconds if you want using getRoundedTime(TimeUnit.MICROSECONDS) or to print it using toStringWithoutUtcOffset(timeScale, fractionsDigits) which will take care of rounding, including going up to the next day if for example you have a date that is just a few nanoseconds before midnight and you round it to microseconds, hence ending up at 0h next day.
As these are very central for the python wrapper, please have a look and this should be the best way to do it right now I think?
MICROSECOND_MULTIPLIER = 1000000
def absolutedate_to_datetime(orekit_absolutedate: AbsoluteDate) -> datetime:
""" Converts from orekit.AbsoluteDate objects
to python datetime objects (utc).
Args:
orekit_absolutedate (AbsoluteDate): orekit AbsoluteDate object to convert
Returns:
datetime: time in python datetime format (UTC)
"""
utc = TimeScalesFactory.getUTC()
or_comp = orekit_absolutedate.getComponents(utc)
or_date = or_comp.getDate()
or_time = or_comp.getTime()
us = or_time.getSplitSecond().getRoundedTime(TimeUnit.MICROSECONDS)
return datetime(or_date.getYear(),
or_date.getMonth(),
or_date.getDay(),
or_time.getHour(),
or_time.getMinute(),
us // MICROSECOND_MULTIPLIER,
us % MICROSECOND_MULTIPLIER)
def datetime_to_absolutedate(dt_date: datetime) -> AbsoluteDate:
""" Converts from python datetime objects to orekit.AbsoluteDate objects.
Args:
dt_date (datetime): datetime object to convert
Returns:
AbsoluteDate: time in orekit AbsoluteDate format
"""
if dt_date.tzinfo is not None and dt_date.tzinfo.utcoffset(dt_date) is not None:
# If the datetime is timezone-aware, convert it to UTC
dt_date = dt_date.astimezone(UTC)
utc = TimeScalesFactory.getUTC()
return AbsoluteDate(dt_date.year,
dt_date.month,
dt_date.day,
dt_date.hour,
dt_date.minute,
TimeOffset(dt_date.second*MICROSECOND_MULTIPLIER+dt_date.microsecond, TimeOffset.MICROSECOND),
utc)
The updated date conversions are now in the orekit JCC conda 13.0.1.1 release. Do not hesitate to reach out for any issues with this new conversion technique.