Time conversion issue with Julian Date

It is sad that there are so many purported authorities on time, but rarely do they agree. There certainly is a great deal of confusion. Which sources, exactly, are correct??

Here is a specific apparent discrepancy between Orekit and NASA:

double gpsSec = 1.312567218e9;
AbsoluteDate ad11 = AbsoluteDate.GPS_EPOCH.shiftedBy(gpsSec);
/* insert code to print ad11 as UTC date with nanosecond precision */
System.out.println(“Julian day=” + formatter.format(ad11.offsetFrom(AbsoluteDate.JULIAN_EPOCH, TimeScalesFactory.getTT())/86400));

results in:

UTC = 2021-08-09 18:00:00.000000000 UTC
Julian day=2459436.2508007404

but NASA, in the form of this online calculator:

https://heasarc.gsfc.nasa.gov/cgi-bin/Tools/xTime/xTime.pl?time_in_i=&time_in_c=&time_in_d=&time_in_j=+ 2459436.25&time_in_m=&time_in_sf=&time_in_wf=&time_in_ii=&time_in_sl=&time_in_sni=&time_in_snu=&time_in_s=&time_in_h=&time_in_sz=&time_in_ss=&time_in_sn=&previous_input=2459436.2508007404&previous_input_ts=u&previous_input_tf=j&previous_input_gps_secs=1312567287.184&timesys_in=u&timesys_out=u&apply_clock_offset=yes

says that the Julian Date is 2459436.25 exactly.

The NASA gsfc website says that JD=2459436.2508007404 corresponds to

2021-08-09 18:01:09.184 UTC

or 69.184 seconds difference. That is also the current difference between TT and TAI.

Changing the Java code to:

System.out.println(“Julian day=” + formatter.format(ad11.offsetFrom(AbsoluteDate.JULIAN_EPOCH, TimeScalesFactory.getTAI())/86400));

gives the same result as above.

Also, if I use

AbsoluteDate julianEpoch = new AbsoluteDate(DateComponents.JULIAN_EPOCH, TimeComponents.H12, TimeScalesFactory.getTAI());

(which differs from the value in Orekit by using TAI instead of TT)


System.out.println(“Julian day=” + formatter.format(ad11.offsetFrom(julianEpoch, TimeScalesFactory.getTT())/86400));


System.out.println(“Julian day=” + formatter.format(ad11.offsetFrom(julianEpoch, TimeScalesFactory.getTAI())/86400))

I get a third value:
Julian day=2,459,436.2504282407

Can anyone help de-confuse this??


According to wikipedia:

"The instant that the gravitational correction started to be applied serves as the epoch for [Barycentric Coordinate Time] (Barycentric_Coordinate_Time) (TCB), [Geocentric Coordinate Time] (TCG), and [Terrestrial Time] (TT), which represent three fundamental time scales in the solar system.[[14]]. All three of these time scales were defined to read JD 2443144.5003725 (1 January 1977 00:00:32.184) exactly at that instant.

Testing this:

AbsoluteDate ad11 = new AbsoluteDate(new DateComponents(1977, 1, 1), new TimeComponents(0, 0, 32.184), TimeScalesFactory.getTT());
System.out.println(“Julian day=” + formatter.format(ad11.offsetFrom(AbsoluteDate.JULIAN_EPOCH, TimeScalesFactory.getTT())/86400));

results in

Julian day=2,443,144.5003724997

which may be as accurate as one can expect using IEEE 754 math.

So in this case Orekit agrees with Wikipedia.

There are some hidden pitfalls here. The offsetFrom method is really tricky and should be avoided in most cases, except if you know exactly how it behaves and what it is intended for. In most cases, you should rather use durationFrom which is easier to understand.

The offsetFrom method was created to work around badly designed interfaces in legacy ground control software. In many older interface specification, dates were represented as a continuous scalar value from a reference start time. One example I have seen already too many times uses “fractional number of day since 1st January 1950”, and was very often implemented as follows:

  1. compute the number of days using the calendar date, call this d
  2. compute the number of seconds in day using UTC time, call this s
  3. combine the two previous results and return d + s / 86400

This however fails to take into account UTC leap seconds and therefore does not work on very long time spans, as it makes two implicit assumptions: days are considered to all be 86400s long, time is considered to be in UTC. However these assumptions cannot be both true at the same time. Either days are 86400s long and you don’t use UTC or you use UTC and some days are longer than other ones when a leap second is introduced. When you have to exchange data with a legacy system that uses these wrong implicit assumptions, you have to implement the date conversion using the same assumptions, so the data is timestamped correctly. This is the only reason why offsetFrom exists: it explicitly removes the irregularities of a time scale, computing the offset just as if all days were 86400s long. There is also a dual constructor AbsoluteDate(AbsoluteDate reference, double apparentOffset, TimeScale timeScale) that does the reverse.

So in your example, you wipe out all irregularities that occurred between AbsoluteDate.JULIAN_EPOCH which is very far in the past and current time. As different times scales as TAI, UTC, TT, and all other were not defined this far in the past, Orekit assumes some time scales were equivalent in the far past, and only documented irregularities were introduced later, starting on 1st January 1961 for the the first linear drift between UTC and TAI, later replaced by whole leap seconds in 1972. It is therefore normal that offsetFrom is 69.184s away from the date you expect.

You should probably try something like:

System.out.println(“Julian day=” + formatter.format(ad11.durationFrom(AbsoluteDate.JULIAN_EPOCH)/86400));

I suggest you avoid both offsetFrom(AbsoluteDate instant, TimeScale timeScale) and AbsoluteDate(AbsoluteDate reference, double apparentOffset, TimeScale timeScale). They are only intended to interface with badly designed legacy interfaces, and are extremely confusing.

O.k. good to know. I will refrain from using the indicated methods. However, I started off with durationFrom, which I have used for years. When it didn’t give me the answer I was expecting, I tried offsetFrom, which I thought might be more definitive - thanks for correcting me that I had my evaluations reversed.

In this case, durationFrom() gives exactly the same answer as offsetFrom(, TimeScalesFactor.get(TT)), namely 2459436.2508007404.

The answer still differs from NASA’s, which is 2459436.25 exactly.

I am hoping that someone can confirm authoritatively that one of these results is correct.

Sadly, I tried a number of other websites including one apparently associated with ESA and got several other (wrong) values.

The initial duration 1312567218 = 15191 * 86400 + 18 * 3600 + 18 corresponds to 15191 days
in GPS scale (because GPS time has no leap seconds), 18 hours and 18 seconds. GPS scale was aligned to UTC at its initial epoch (1980-01-06), so it was 19 seconds ahead of TAI. Since that date, GPS time scale did not experience any leap second by design, so it is still 19 second ahead of TAI. UTC on the other hand experienced 18 leaps seconds and is now 37 second ahead of TAI. So the duration of 1312567218 seconds since GPS epoch translates to 2021-08-09T18:00:00Z, the 18 seconds in 15191 * 86400 + 18 * 3600 + 18 correspond exactly to the 18 leap seconds between 1980 and 2021. We all agree here.

So it appears this web site uses those same weird assumptions :scream:
It appears to consider a fractional Julian day is a mix of a whole number of day plus a fraction that is computed from noon this day in UTC, it is not a duration from something stable, but rather a composite value.

One way to check this would be to use two dates before and after a leap second (at least two days apart in order to avoid the additional complexity that leap seconds occur at 00h00 UTC but Julian days change at 12h00 UTC). For example 2016-12-31T06:00:00Z and 2017-01-02T06:00:00Z. The duration elapsed between these dates is 2 * 86400 + 1 = 172801 because a leap second was introduced on 2017-01-01T00:00:00Z. I guess converting these dates to Julian day on the NASA site would give fractional parts of exactly 0.75 (06:00:00Z is exactly 18 hours after the previous 12:00:00Z, and 0.75 = 18/24). If my guess is correct, it means the Julian day output is not a physical duration, it is an hybrid.

Beware that I don’t say it is wrong, I just say it is based on assumptions that should be documented somewhere.

As far as I am concerned, as time and time scales are complex and confusing, I tend to stick to only two principles to write time in a portable way:

  • either use calendar elements like ISO-8601 (or CCSDS ASCII calendar segmented time code, which is a subset of ISO-8601) in UTC time scale
  • or use a physical duration since an epoch defined unambiguously in a regular time scale without leaps (i.e. not UTC or GLONASS, but probably TAI, or GPS, or Galileo)

The representation 2459436.25 is really a segmented time code. The part before the decimal separator specifies the day, the part after the decimal separator specifies the hour in the day, but they are packed together in way that lead people to think it is continuous, which is probably not true when leap seconds are introduced (at least it is my guess).

Time conversion is fast and needs to be done only on input/output, so the first option (ISO-8601 and UTC) is probably more easily understood by anyone.

It is documented here: RESOLUTION B1 ON THE USE OF JULIAN DATES The XXIIIrd International Astronomical Union General Assembly

The important part is

The Julian Date (JD) of any instant is the Julian day number for the
preceding noon plus the fraction of the day since that instant. A Julian
Date begins at 12h 0m 0s and is composed of 86400 seconds. To
determine time intervals in a uniform time system it is necessary to
express the JD in a uniform time scale. For that purpose it is
recommended that JD be specified as SI seconds in Terrestrial
Time (TT) where the length of day is 86,400 SI seconds.

So we really have a day count with regular length, plus a fraction. The referenced NASA website does not comply with the recommendation to use TT (otherwise we would see the 32.184 s offset somewhere).

O.k. I am willing to believe that IAU is a plausible authority. I will send a note to the NASA website RE suggesting that they double-check their JD calculation.

Hurrah for Orekit!

Could you check the guess with the two dates before and after leap second?

NASA’s calculator has some trouble with leap seconds, as shown below. Dates are mismatched depending on the format. JD and MJD appear to decrease after the leap second. The interval computation is certainly incorrect, it should be +0.5s.

ISO 8601 date (yyyy-MM-dd hh:mm:ss)  2016-12-31T23:59:60.5
Calendar date (yyyyMondd at hh:mm:ss)  2017Jan01 at 00:00:00.500 UTC
Year and day number (yyyy:ddd:hh:mm:ss)  2017:001:00:00:00.500 UTC
Julian Date (ddddddd.ddd…)  2457754.50000579
Modified Julian Date (ddddd.ddd…)  57754.00000579

ISO 8601 date (yyyy-MM-dd hh:mm:ss)  2017-01-01T00:00:00
Calendar date (yyyyMondd at hh:mm:ss)  2017Jan01 at 00:00:00.000 UTC
Year and day number (yyyy:ddd:hh:mm:ss)  2017:001:00:00:00.000 UTC
Julian Date (ddddddd.ddd…)  2457754.50000000
Modified Julian Date (ddddd.ddd…)  57754.00000000

Previous Input ISO 8601 Date [UTC]: 2016-12-31T23:59:60.5
Current Input ISO 8601 Date [UTC]: 2017-01-01T00:00:00
Interval from Previous to Current: -0.500 seconds / -5.78703703703704e-06 days

Thanks very much to Luc and Evan. It is truly distressing that there are so many erroneous time calculators out there and bad “reference software”. Even NASA can’t seem to get it right…