1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.time;
18  
19  import java.time.Instant;
20  import java.time.OffsetDateTime;
21  import java.time.format.DateTimeFormatter;
22  import java.util.Date;
23  import java.util.TimeZone;
24  
25  import java.util.concurrent.TimeUnit;
26  import org.hamcrest.CoreMatchers;
27  import org.hamcrest.MatcherAssert;
28  import org.hipparchus.CalculusFieldElement;
29  import org.hipparchus.Field;
30  import org.hipparchus.analysis.differentiation.*;
31  import org.hipparchus.complex.Complex;
32  import org.hipparchus.complex.ComplexField;
33  import org.hipparchus.complex.FieldComplex;
34  import org.hipparchus.complex.FieldComplexField;
35  import org.hipparchus.dfp.Dfp;
36  import org.hipparchus.dfp.DfpField;
37  import org.hipparchus.util.Binary64;
38  import org.hipparchus.util.Binary64Field;
39  import org.hipparchus.util.FastMath;
40  import org.hipparchus.util.FieldTuple;
41  import org.hipparchus.util.Precision;
42  import org.hipparchus.util.Tuple;
43  import org.junit.jupiter.api.Assertions;
44  import org.junit.jupiter.api.BeforeEach;
45  import org.junit.jupiter.api.Test;
46  import org.orekit.OrekitMatchers;
47  import org.orekit.Utils;
48  import org.orekit.errors.OrekitException;
49  import org.orekit.errors.OrekitIllegalArgumentException;
50  import org.orekit.errors.OrekitMessages;
51  import org.orekit.utils.Constants;
52  
53  class FieldAbsoluteDateTest {
54  
55      private TimeScale utc;
56  
57      @BeforeEach
58      public void setUp() {
59          Utils.setDataRoot("regular-data");
60          utc = TimeScalesFactory.getUTC();
61      }
62  
63      @Test
64      void testStandardEpoch() {
65          doTestStandardEpoch(Binary64Field.getInstance());
66      }
67  
68      @Test
69      void testStandardEpochStrings() {
70          doTestStandardEpochStrings(Binary64Field.getInstance());
71      }
72  
73      @Test
74      void testJulianEpochRate() {
75          doTestJulianEpochRate(Binary64Field.getInstance());
76      }
77  
78      @Test
79      void testBesselianEpochRate() {
80          doTestBesselianEpochRate(Binary64Field.getInstance());
81      }
82  
83      @Test
84      void testLieske() {
85          doTestLieske(Binary64Field.getInstance());
86      }
87  
88      @Test
89      void testParse() {
90          doTestParse(Binary64Field.getInstance());
91      }
92  
93      @Test
94      void testLocalTimeParsing() {
95          doTestLocalTimeParsing(Binary64Field.getInstance());
96      }
97  
98      @Test
99      void testTimeZoneDisplay() {
100         doTestTimeZoneDisplay(Binary64Field.getInstance());
101     }
102 
103     @Test
104     void testLocalTimeLeapSecond() {
105         doTestLocalTimeLeapSecond(Binary64Field.getInstance());
106     }
107 
108     @Test
109     void testTimeZoneLeapSecond() {
110         doTestTimeZoneLeapSecond(Binary64Field.getInstance());
111     }
112 
113     @Test
114     void testParseLeap() {
115         doTestParseLeap(Binary64Field.getInstance());
116     }
117 
118     @Test
119     void testOutput() {
120         doTestOutput(Binary64Field.getInstance());
121     }
122 
123     @Test
124     void testJ2000() {
125         doTestJ2000(Binary64Field.getInstance());
126     }
127 
128     @Test
129     void testFraction() {
130         doTestFraction(Binary64Field.getInstance());
131     }
132 
133     @Test
134     void testScalesOffset() {
135         doTestScalesOffset(Binary64Field.getInstance());
136     }
137 
138     @Test
139     void testUTC() {
140         doTestUTC(Binary64Field.getInstance());
141     }
142 
143     @Test
144     void test1970() {
145         doTest1970(Binary64Field.getInstance());
146     }
147 
148     @Test
149     void test1970Instant() {
150         doTest1970Instant(Binary64Field.getInstance());
151     }
152 
153     @Test
154     void testInstantAccuracy() {
155         doTestInstantAccuracy(Binary64Field.getInstance());
156     }
157 
158     @Test
159     void testToInstant() {
160         doTestToInstant(Binary64Field.getInstance());
161     }
162 
163 
164     @Test
165     void testUtcGpsOffset() {
166         doTestUtcGpsOffset(Binary64Field.getInstance());
167     }
168 
169     @Test
170     void testGpsDate() {
171         doTestGpsDate(Binary64Field.getInstance());
172     }
173 
174     @Test
175     void testMJDDate() {
176         doTestMJDDate(Binary64Field.getInstance());
177     }
178 
179     @Test
180     void testJDDate() {
181         doTestJDDate(Binary64Field.getInstance());
182     }
183 
184     /** Test issue 1310: get a date from a JD using a pivot timescale. */
185     @Test
186     void testIssue1310JDDateInTDB() {
187 
188         // Given
189         // -----
190         final Field<Binary64> field = Binary64Field.getInstance();
191 
192         final TDBScale TDBscale = TimeScalesFactory.getTDB();
193         final FieldAbsoluteDate<Binary64> refDate = new FieldAbsoluteDate<>(field,
194                 new AbsoluteDate("2023-08-01T00:00:00.000", TDBscale));
195 
196         // When
197         // ----
198         final FieldAbsoluteDate<Binary64> wrongDate  = FieldAbsoluteDate.createJDDate(2460157,
199                 field.getOne().multiply(Constants.JULIAN_DAY / 2.0d), TDBscale);
200         final FieldAbsoluteDate<Binary64> properDate = FieldAbsoluteDate.createJDDate(2460157,
201                 field.getOne().multiply(Constants.JULIAN_DAY / 2.0d), TDBscale, TimeScalesFactory.getTT());
202 
203         // Then
204         // ----
205 
206         // Wrong date is too far from reference date
207         Assertions.assertEquals(0.0, wrongDate.durationFrom(refDate).getReal(), 1.270e-05);
208 
209         // Proper date is close enough from reference date
210         Assertions.assertEquals(0.0, properDate.durationFrom(refDate).getReal(), 2.132e-13);
211     }
212 
213     @Test
214     void testOffsets() {
215         doTestOffsets(Binary64Field.getInstance());
216     }
217 
218     @Test
219     void testBeforeAndAfterLeap() {
220         doTestBeforeAndAfterLeap(Binary64Field.getInstance());
221     }
222 
223     @Test
224     void testSymmetry() {
225         doTestSymmetry(Binary64Field.getInstance());
226     }
227 
228     @Test
229     void testEquals() {
230         doTestEquals(Binary64Field.getInstance());
231     }
232 
233     @Test
234     void testEqualsAddendum() {
235         // GIVEN
236         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
237         final UnivariateDerivative1 derivative1 = new UnivariateDerivative1(0., 1);
238         final UnivariateDerivative1Field field = UnivariateDerivative1Field.getInstance();
239         final FieldAbsoluteDate<UnivariateDerivative1> fieldDate = new FieldAbsoluteDate<>(field,
240                 date).shiftedBy(derivative1);
241         final FieldAbsoluteDate<UnivariateDerivative1> sameFieldDate = fieldDate.shiftedBy(0);
242         // WHEN
243         final boolean isEqual = fieldDate.equals(new FieldAbsoluteDate<>(field, date));
244         // THEN
245         Assertions.assertFalse(isEqual);
246         Assertions.assertEquals(sameFieldDate.hashCode(), fieldDate.hashCode());
247         Assertions.assertEquals(sameFieldDate, fieldDate);
248     }
249 
250     @Test
251     void testIsEqualTo() { doTestIsEqualTo(Binary64Field.getInstance()); }
252 
253     @Test
254     void testIsCloseTo() { doTestIsCloseTo(Binary64Field.getInstance()); }
255 
256     @Test
257     void testIsBefore() { doTestIsBefore(Binary64Field.getInstance()); }
258 
259     @Test
260     void testIsAfter() { doTestIsAfter(Binary64Field.getInstance()); }
261 
262     @Test
263     void testIsBeforeOrEqualTo() { doTestIsBeforeOrEqualTo(Binary64Field.getInstance()); }
264 
265     @Test
266     void testIsAfterOrEqualTo() { doTestIsAfterOrEqualTo(Binary64Field.getInstance()); }
267 
268     @Test
269     void testIsBetween() { doTestIsBetween(Binary64Field.getInstance()); }
270 
271     @Test
272     void testIsBetweenOrEqualTo() { doTestIsBetweenOrEqualTo(Binary64Field.getInstance()); }
273 
274     @Test
275     void testComponents() {
276         doTestComponents(Binary64Field.getInstance());
277     }
278 
279     @Test
280     void testMonth() {
281         doTestMonth(Binary64Field.getInstance());
282     }
283 
284     @Test
285     void testCCSDSUnsegmentedNoExtension() {
286         doTestCCSDSUnsegmentedNoExtension(Binary64Field.getInstance());
287     }
288 
289     @Test
290     void testCCSDSUnsegmentedWithExtendedPreamble() {
291         doTestCCSDSUnsegmentedWithExtendedPreamble(Binary64Field.getInstance());
292     }
293 
294     @Test
295     void testCCSDSDaySegmented() {
296         doTestCCSDSDaySegmented(Binary64Field.getInstance());
297     }
298 
299     @Test
300     void testCCSDSCalendarSegmented() {
301         doTestCCSDSCalendarSegmented(Binary64Field.getInstance());
302     }
303 
304     @Test
305     void testExpandedConstructors() {
306         doTestExpandedConstructors(Binary64Field.getInstance());
307     }
308 
309     @Test
310     void testHashcode() {
311         doTestHashcode(Binary64Field.getInstance());
312     }
313 
314     @Test
315     void testInfinity() {
316         doTestInfinity(Binary64Field.getInstance());
317     }
318 
319     @Test
320     void testAccuracy() {
321         doTestAccuracy(Binary64Field.getInstance());
322     }
323 
324     @Test
325     void testAccuracyIssue348() {
326         doTestAccuracyIssue348(Binary64Field.getInstance());
327     }
328 
329     @Test
330     void testIterationAccuracy() {
331         doTestIterationAccuracy(Binary64Field.getInstance());
332     }
333 
334     @Test
335     void testIssue142() {
336         doTestIssue142(Binary64Field.getInstance());
337     }
338 
339     @Test
340     void testIssue148() {
341         doTestIssue148(Binary64Field.getInstance());
342     }
343 
344     @Test
345     void testIssue149() {
346         doTestIssue149(Binary64Field.getInstance());
347     }
348 
349     @Test
350     void testWrapAtMinuteEnd() {
351         doTestWrapAtMinuteEnd(Binary64Field.getInstance());
352     }
353 
354     @Test
355     void testIssue508() {
356         doTestIssue508(Binary64Field.getInstance());
357     }
358 
359     @Test
360     void testGetComponentsIssue681and676and694() {
361         doTestGetComponentsIssue681and676and694(Binary64Field.getInstance());
362     }
363 
364     @Test
365     void testNegativeOffsetConstructor() {
366         doTestNegativeOffsetConstructor(Binary64Field.getInstance());
367     }
368 
369     @Test
370     void testNegativeOffsetShift() {
371         doTestNegativeOffsetShift(Binary64Field.getInstance());
372     }
373 
374     @Test
375     void testGetDayOfYear() {
376         doTestGetDayOfYear(Binary64Field.getInstance());
377     }
378     /** Test for method {@link FieldAbsoluteDate#hasZeroField()}.*/
379     @Test
380     void testHasZeroField() {
381                        
382         // DerivativeStructure
383         // ----------
384         
385         final DSFactory dsFactory  = new DSFactory(3, 2);
386         final Field<DerivativeStructure> dsField = dsFactory.getDerivativeField();
387         
388         // Constant date returns true
389         final FieldAbsoluteDate<DerivativeStructure> dsConstantDate = new FieldAbsoluteDate<>(dsField);
390         Assertions.assertTrue(dsConstantDate.hasZeroField());
391         Assertions.assertTrue(dsConstantDate.shiftedBy(dsField.getOne()).hasZeroField());
392         
393         // Variable date returns false
394         final DerivativeStructure dsDt0 = dsFactory.variable(0, 10.);
395         final DerivativeStructure dsDt1 = dsFactory.variable(1, -100.);
396         final DerivativeStructure dsDt2 = dsFactory.variable(2, 100.);
397         Assertions.assertFalse(dsConstantDate.shiftedBy(dsDt0).hasZeroField());
398         Assertions.assertFalse(dsConstantDate.shiftedBy(dsDt1).hasZeroField());
399         Assertions.assertFalse(dsConstantDate.shiftedBy(dsDt2).hasZeroField());
400         Assertions.assertFalse(dsConstantDate.shiftedBy(dsDt0).shiftedBy(dsDt1).shiftedBy(dsDt2).hasZeroField());
401         
402         // UnivariateDerivative1
403         // ---------------------
404         
405         final Field<UnivariateDerivative1> u1Field = UnivariateDerivative1Field.getInstance();
406         
407         // Constant date returns true
408         final FieldAbsoluteDate<UnivariateDerivative1> u1ConstantDate = new FieldAbsoluteDate<>(u1Field);
409         Assertions.assertTrue(u1ConstantDate.hasZeroField());
410         Assertions.assertTrue(u1ConstantDate.shiftedBy(u1Field.getOne()).hasZeroField());
411         Assertions.assertTrue(u1ConstantDate.shiftedBy(new UnivariateDerivative1(10., 0.)).hasZeroField());
412         
413         // Variable date returns false
414         Assertions.assertFalse(u1ConstantDate.shiftedBy(new UnivariateDerivative1(10., 10.)).hasZeroField());
415         
416         // UnivariateDerivative1
417         // ---------------------
418         
419         final Field<UnivariateDerivative2> u2Field = UnivariateDerivative2Field.getInstance();
420         
421         // Constant date returns true
422         final FieldAbsoluteDate<UnivariateDerivative2> u2ConstantDate = new FieldAbsoluteDate<>(u2Field);
423         Assertions.assertTrue(u2ConstantDate.hasZeroField());
424         Assertions.assertTrue(u2ConstantDate.shiftedBy(u2Field.getOne()).hasZeroField());
425         Assertions.assertTrue(u2ConstantDate.shiftedBy(new UnivariateDerivative2(10., 0., 0.)).hasZeroField());
426         
427         // Variable date returns false
428         Assertions.assertFalse(u2ConstantDate.shiftedBy(new UnivariateDerivative2(10., 1., 2.)).hasZeroField());
429         
430         // Gradient
431         // --------
432         
433         final Field<Gradient> gdField = GradientField.getField(2);
434         
435         // Constant date returns true
436         final FieldAbsoluteDate<Gradient> gdConstantDate = new FieldAbsoluteDate<>(gdField);
437         Assertions.assertTrue(gdConstantDate.hasZeroField());
438         
439         // Variable date returns false
440         final Gradient gdDt0 = Gradient.variable(2, 0, 10.);
441         final Gradient gdDt1 = Gradient.variable(2, 1, -100.);
442         Assertions.assertFalse(gdConstantDate.shiftedBy(gdDt0).hasZeroField());
443         Assertions.assertFalse(gdConstantDate.shiftedBy(gdDt1).hasZeroField());
444         Assertions.assertFalse(gdConstantDate.shiftedBy(gdDt0).shiftedBy(gdDt1).hasZeroField());
445 
446         // SparseGradient
447         final FieldAbsoluteDate<SparseGradient> sgdDate = new FieldAbsoluteDate<>(SparseGradient.createConstant(10.).getField());
448         Assertions.assertTrue(sgdDate.hasZeroField());
449 
450         // Complex
451         // -------
452         
453         final Field<Complex> cxField = ComplexField.getInstance();
454         
455         // Complex with no imaginary part returns true
456         final FieldAbsoluteDate<Complex> cxConstantDate = new FieldAbsoluteDate<>(cxField);
457         Assertions.assertTrue(cxConstantDate.hasZeroField());
458 
459         Assertions.assertTrue(cxConstantDate.shiftedBy(new Complex(10., 0.)).hasZeroField());                
460         
461         // Complex with imaginary part returns false
462         Assertions.assertFalse(cxConstantDate.shiftedBy(new Complex(-100., 10.)).hasZeroField());
463 
464         // Binary64
465         final Binary64Field b64Field = Binary64Field.getInstance();
466         final FieldAbsoluteDate<Binary64> b64Date = new FieldAbsoluteDate<>(b64Field);
467         Assertions.assertTrue(b64Date.hasZeroField());
468         
469         // Dfp
470         final FieldAbsoluteDate<Dfp> dfpDate = new FieldAbsoluteDate<>(new DfpField(10));
471         Assertions.assertTrue(dfpDate.hasZeroField());
472         
473         // FieldComplex
474         final FieldAbsoluteDate<FieldComplex<Complex>> fcxDate = new FieldAbsoluteDate<>(FieldComplexField.getField(cxField));
475         Assertions.assertTrue(fcxDate.hasZeroField());
476         
477         // FieldTuple
478         final FieldAbsoluteDate<FieldTuple<DerivativeStructure>> ftpDate = new FieldAbsoluteDate<>(new FieldTuple<>(dsDt0, dsDt1).getField());
479         Assertions.assertTrue(ftpDate.hasZeroField());
480 
481         // Tuple
482         final FieldAbsoluteDate<Tuple> tpDate = new FieldAbsoluteDate<>(new Tuple(0., 1.).getField());
483         Assertions.assertTrue(tpDate.hasZeroField());
484         
485         // FieldDerivativeStructure
486         final FDSFactory<Binary64> fdsFactory = new FDSFactory<>(b64Field, 3, 1);
487         final FieldAbsoluteDate<FieldDerivativeStructure<Binary64>> fdsDate = new FieldAbsoluteDate<>(fdsFactory.constant(1.).getField());
488         Assertions.assertTrue(fdsDate.hasZeroField());
489         
490         // FieldGradient
491         final FieldAbsoluteDate<FieldGradient<Binary64>> fgdDate =
492                         new FieldAbsoluteDate<>(new FieldGradient<>(fdsFactory.constant(1.)).getField());
493         Assertions.assertTrue(fgdDate.hasZeroField());
494         
495         // FieldUnivariateDerivative1
496         final FieldAbsoluteDate<FieldUnivariateDerivative1<Binary64>> fu1Date = new FieldAbsoluteDate<>(
497                 FieldUnivariateDerivative1Field.getUnivariateDerivative1Field(Binary64Field.getInstance()));
498         Assertions.assertTrue(fu1Date.hasZeroField());
499         
500         // FieldUnivariateDerivative2
501         final FieldAbsoluteDate<FieldUnivariateDerivative2<Binary64>> fu2Date = new FieldAbsoluteDate<>(
502                 FieldUnivariateDerivative2Field.getUnivariateDerivative2Field(Binary64Field.getInstance()));
503         Assertions.assertTrue(fu2Date.hasZeroField());
504     }
505 
506     @Test
507     void testDurationFromWithTimeUnit() {
508         doTestDurationFromWithTimeUnit(Binary64Field.getInstance());
509     }
510 
511     @Test
512     void testConstructWithTimeUnitOffset() {
513         doTestConstructWithTimeUnitOffset(Binary64Field.getInstance());
514     }
515 
516     @Test
517     void testShiftedByWithTimeUnit() {
518         doTestShiftedByWithTimeUnit(Binary64Field.getInstance());
519     }
520 
521     @Test
522     void testToStringWithoutUtcOffset() {
523         doTestToStringWithoutUtcOffset(Binary64Field.getInstance());
524     }
525 
526 
527     private <T extends CalculusFieldElement<T>> void doTestStandardEpoch(final Field<T> field) {
528 
529         TimeScale tai = TimeScalesFactory.getTAI();
530         TimeScale tt  = TimeScalesFactory.getTT();
531 
532         FieldAbsoluteDate<T> JuEp  = FieldAbsoluteDate.getJulianEpoch(field);
533         FieldAbsoluteDate<T> MJuEp = FieldAbsoluteDate.getModifiedJulianEpoch(field);
534         FieldAbsoluteDate<T> FiEp  = FieldAbsoluteDate.getFiftiesEpoch(field);
535         FieldAbsoluteDate<T> CCSDS = FieldAbsoluteDate.getCCSDSEpoch(field);
536         FieldAbsoluteDate<T> GaEp  = FieldAbsoluteDate.getGalileoEpoch(field);
537         FieldAbsoluteDate<T> GPSEp = FieldAbsoluteDate.getGPSEpoch(field);
538         FieldAbsoluteDate<T> JTTEP = FieldAbsoluteDate.getJ2000Epoch(field);
539 
540         Assertions.assertEquals(-210866760000000L, JuEp.toDate(tt).getTime());
541         Assertions.assertEquals(-3506716800000L, MJuEp.toDate(tt).getTime());
542         Assertions.assertEquals(-631152000000L, FiEp.toDate(tt).getTime());
543         Assertions.assertEquals(-378691200000L, CCSDS.toDate(tai).getTime());
544         Assertions.assertEquals(935280019000L,  GaEp.toDate(tai).getTime());
545         Assertions.assertEquals(315964819000L,  GPSEp.toDate(tai).getTime());
546         Assertions.assertEquals(946728000000L,  JTTEP.toDate(tt).getTime());
547 
548     }
549 
550     private <T extends CalculusFieldElement<T>> void doTestStandardEpochStrings(final Field<T> field) {
551 
552         Assertions.assertEquals("-4712-01-01T12:00:00.000",
553                             FieldAbsoluteDate.getJulianEpoch(field).toString(TimeScalesFactory.getTT()));
554         Assertions.assertEquals("1858-11-17T00:00:00.000",
555                             FieldAbsoluteDate.getModifiedJulianEpoch(field).toString(TimeScalesFactory.getTT()));
556         Assertions.assertEquals("1950-01-01T00:00:00.000",
557                             FieldAbsoluteDate.getFiftiesEpoch(field).toString(TimeScalesFactory.getTT()));
558         Assertions.assertEquals("1958-01-01T00:00:00.000",
559                             FieldAbsoluteDate.getCCSDSEpoch(field).toString(TimeScalesFactory.getTAI()));
560         Assertions.assertEquals("1999-08-21T23:59:47.000",
561                             FieldAbsoluteDate.getGalileoEpoch(field).toString(TimeScalesFactory.getUTC()));
562         Assertions.assertEquals("1980-01-06T00:00:00.000",
563                             FieldAbsoluteDate.getGPSEpoch(field).toString(TimeScalesFactory.getUTC()));
564         Assertions.assertEquals("2000-01-01T12:00:00.000",
565                             FieldAbsoluteDate.getJ2000Epoch(field).toString(TimeScalesFactory.getTT()));
566         Assertions.assertEquals("1970-01-01T00:00:00.000",
567                             FieldAbsoluteDate.getJavaEpoch(field).toString(TimeScalesFactory.getUTC()));
568     }
569 
570     private <T extends CalculusFieldElement<T>> void doTestJulianEpochRate(final Field<T> field) {
571 
572         for (int i = 0; i < 10; ++i) {
573             FieldAbsoluteDate<T> j200i = FieldAbsoluteDate.createJulianEpoch(field.getZero().add(2000.0+i));
574             FieldAbsoluteDate<T> j2000 = FieldAbsoluteDate.getJ2000Epoch(field);
575             double expected    = i * Constants.JULIAN_YEAR;
576             Assertions.assertEquals(expected, j200i.durationFrom(j2000).getReal(), 4.0e-15 * expected);
577         }
578 
579     }
580 
581     private <T extends CalculusFieldElement<T>> void doTestBesselianEpochRate(final Field<T> field) {
582 
583         for (int i = 0; i < 10; ++i) {
584             FieldAbsoluteDate<T> b195i = FieldAbsoluteDate.createBesselianEpoch(field.getZero().add(1950.0 + i));
585             FieldAbsoluteDate<T> b1950 = FieldAbsoluteDate.createBesselianEpoch(field.getZero().add(1950.0));
586             double expected    = i * Constants.BESSELIAN_YEAR;
587             Assertions.assertEquals(expected, b195i.durationFrom(b1950).getReal(), 4.0e-15 * expected);
588         }
589 
590     }
591 
592     private <T extends CalculusFieldElement<T>> void doTestLieske(final Field<T> field) {
593 
594         // the following test values correspond to table 1 in the paper:
595         // Precession Matrix Based on IAU (1976) System of Astronomical Constants,
596         // Jay H. Lieske, Astronomy and Astrophysics, vol. 73, no. 3, Mar. 1979, p. 282-284
597         // http://articles.adsabs.harvard.edu/cgi-bin/nph-iarticle_query?1979A%26A....73..282L&defaultprint=YES&filetype=.pdf
598 
599         // published table, with limited accuracy
600 
601 
602         final double publishedEpsilon = 1.0e-6 * Constants.JULIAN_YEAR;
603         checkEpochs(field, 1899.999142, 1900.000000, publishedEpsilon);
604         checkEpochs(field, 1900.000000, 1900.000858, publishedEpsilon);
605         checkEpochs(field, 1950.000000, 1949.999790, publishedEpsilon);
606         checkEpochs(field, 1950.000210, 1950.000000, publishedEpsilon);
607         checkEpochs(field, 2000.000000, 1999.998722, publishedEpsilon);
608         checkEpochs(field, 2000.001278, 2000.000000, publishedEpsilon);
609 
610         // recomputed table, using directly Lieske formulas (i.e. *not* Orekit implementation) with high accuracy
611         final double accurateEpsilon = 1.2e-13 * Constants.JULIAN_YEAR;
612         checkEpochs(field, 1899.99914161068724704, 1900.00000000000000000, accurateEpsilon);
613         checkEpochs(field, 1900.00000000000000000, 1900.00085837097878165, accurateEpsilon);
614         checkEpochs(field, 1950.00000000000000000, 1949.99979044229979466, accurateEpsilon);
615         checkEpochs(field, 1950.00020956217615449, 1950.00000000000000000, accurateEpsilon);
616         checkEpochs(field, 2000.00000000000000000, 1999.99872251362080766, accurateEpsilon);
617         checkEpochs(field, 2000.00127751366506194, 2000.00000000000000000, accurateEpsilon);
618 
619     }
620 
621     private <T extends CalculusFieldElement<T>> void checkEpochs(final Field<T> field, final double besselianEpoch, final double julianEpoch, final double epsilon) {
622         final FieldAbsoluteDate<T> b = FieldAbsoluteDate.createBesselianEpoch(field.getZero().add(besselianEpoch));
623         final FieldAbsoluteDate<T> j = FieldAbsoluteDate.createJulianEpoch(field.getZero().add(julianEpoch));
624         Assertions.assertEquals(0.0, b.durationFrom(j).getReal(), epsilon);
625     }
626 
627     private <T extends CalculusFieldElement<T>> void doTestParse(final Field<T> field) {
628 
629         Assertions.assertEquals(FieldAbsoluteDate.getModifiedJulianEpoch(field),
630                             new FieldAbsoluteDate<>(field, "1858-W46-3", TimeScalesFactory.getTT()));
631         Assertions.assertEquals(FieldAbsoluteDate.getJulianEpoch(field),
632                             new FieldAbsoluteDate<>(field, "-4712-01-01T12:00:00.000", TimeScalesFactory.getTT()));
633         Assertions.assertEquals(FieldAbsoluteDate.getFiftiesEpoch(field),
634                             new FieldAbsoluteDate<>(field, "1950-01-01", TimeScalesFactory.getTT()));
635         Assertions.assertEquals(FieldAbsoluteDate.getCCSDSEpoch(field),
636                             new FieldAbsoluteDate<>(field, "1958-001", TimeScalesFactory.getTAI()));
637     }
638 
639     private <T extends CalculusFieldElement<T>> void doTestLocalTimeParsing(final Field<T> field) {
640         TimeScale utc = TimeScalesFactory.getUTC();
641         Assertions.assertEquals(new FieldAbsoluteDate<>(field, "2011-12-31T23:00:00",       utc),
642                             new FieldAbsoluteDate<>(field, "2012-01-01T03:30:00+04:30", utc));
643         Assertions.assertEquals(new FieldAbsoluteDate<>(field, "2011-12-31T23:00:00",       utc),
644                             new FieldAbsoluteDate<>(field, "2012-01-01T03:30:00+0430",  utc));
645         Assertions.assertEquals(new FieldAbsoluteDate<>(field, "2011-12-31T23:30:00",       utc),
646                             new FieldAbsoluteDate<>(field, "2012-01-01T03:30:00+04",    utc));
647         Assertions.assertEquals(new FieldAbsoluteDate<>(field, "2012-01-01T05:17:00",       utc),
648                             new FieldAbsoluteDate<>(field, "2011-12-31T22:17:00-07:00", utc));
649         Assertions.assertEquals(new FieldAbsoluteDate<>(field, "2012-01-01T05:17:00",       utc),
650                             new FieldAbsoluteDate<>(field, "2011-12-31T22:17:00-0700",  utc));
651         Assertions.assertEquals(new FieldAbsoluteDate<>(field, "2012-01-01T05:17:00",       utc),
652                             new FieldAbsoluteDate<>(field, "2011-12-31T22:17:00-07",    utc));
653     }
654 
655     private <T extends CalculusFieldElement<T>> void doTestTimeZoneDisplay(final Field<T> field) {
656         final TimeScale utc = TimeScalesFactory.getUTC();
657         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, "2000-01-01T01:01:01.000", utc);
658         Assertions.assertEquals("2000-01-01T01:01:01.000Z",      date.toString());
659         Assertions.assertEquals("2000-01-01T11:01:01.000+10:00", date.toString( 600));
660         Assertions.assertEquals("1999-12-31T23:01:01.000-02:00", date.toString(-120));
661 
662         // winter time, Europe is one hour ahead of UTC
663         final TimeZone tz = TimeZone.getTimeZone("Europe/Paris");
664         Assertions.assertEquals("2001-01-22T11:30:00.000+01:00",
665                             new FieldAbsoluteDate<>(field, "2001-01-22T10:30:00", utc).toString(tz));
666 
667         // summer time, Europe is two hours ahead of UTC
668         Assertions.assertEquals("2001-06-23T11:30:00.000+02:00",
669                             new FieldAbsoluteDate<>(field, "2001-06-23T09:30:00", utc).toString(tz));
670 
671     }
672 
673     private <T extends CalculusFieldElement<T>> void doTestLocalTimeLeapSecond(final Field<T> field) {
674 
675         TimeScale utc = TimeScalesFactory.getUTC();
676         FieldAbsoluteDate<T> beforeLeap = new FieldAbsoluteDate<>(field, "2012-06-30T23:59:59.8", utc);
677         FieldAbsoluteDate<T> inLeap     = new FieldAbsoluteDate<>(field, "2012-06-30T23:59:60.5", utc);
678         Assertions.assertEquals(0.7, inLeap.durationFrom(beforeLeap).getReal(), 1.0e-12);
679        for (int minutesFromUTC = -1500; minutesFromUTC < -1499; ++minutesFromUTC) {
680             DateTimeComponents dtcBeforeLeap = beforeLeap.getComponents(minutesFromUTC);
681             DateTimeComponents dtcInsideLeap = inLeap.getComponents(minutesFromUTC);
682 
683 
684             Assertions.assertEquals(dtcBeforeLeap.getDate(), dtcInsideLeap.getDate());
685 
686             Assertions.assertEquals(dtcBeforeLeap.getTime().getHour(), dtcInsideLeap.getTime().getHour());
687             Assertions.assertEquals(dtcBeforeLeap.getTime().getMinute(), dtcInsideLeap.getTime().getMinute());
688             Assertions.assertEquals(minutesFromUTC, dtcBeforeLeap.getTime().getMinutesFromUTC());
689             Assertions.assertEquals(minutesFromUTC, dtcInsideLeap.getTime().getMinutesFromUTC());
690             Assertions.assertEquals(59.8, dtcBeforeLeap.getTime().getSecond(), 1.0e-10);
691             Assertions.assertEquals(60.5, dtcInsideLeap.getTime().getSecond(), 1.0e-10);
692         }
693 
694     }
695 
696     private <T extends CalculusFieldElement<T>> void doTestTimeZoneLeapSecond(final Field<T> field) {
697 
698         TimeScale utc = TimeScalesFactory.getUTC();
699         final TimeZone tz = TimeZone.getTimeZone("Europe/Paris");
700         FieldAbsoluteDate<T> localBeforeMidnight = new FieldAbsoluteDate<>(field, "2012-06-30T21:59:59.800", utc);
701         Assertions.assertEquals("2012-06-30T23:59:59.800+02:00",
702                             localBeforeMidnight.toString(tz));
703         Assertions.assertEquals("2012-07-01T00:00:00.800+02:00",
704                             localBeforeMidnight.shiftedBy(1.0).toString(tz));
705 
706         FieldAbsoluteDate<T> beforeLeap = new FieldAbsoluteDate<>(field, "2012-06-30T23:59:59.8", utc);
707         FieldAbsoluteDate<T> inLeap     = new FieldAbsoluteDate<>(field, "2012-06-30T23:59:60.5", utc);
708         Assertions.assertEquals(0.7, inLeap.durationFrom(beforeLeap).getReal(), 1.0e-12);
709         Assertions.assertEquals("2012-07-01T01:59:59.800+02:00", beforeLeap.toString(tz));
710         Assertions.assertEquals("2012-07-01T01:59:60.500+02:00", inLeap.toString(tz));
711 
712     }
713 
714     private <T extends CalculusFieldElement<T>> void doTestParseLeap(final Field<T> field) {
715         TimeScale utc = TimeScalesFactory.getUTC();
716         FieldAbsoluteDate<T> beforeLeap = new FieldAbsoluteDate<>(field, "2012-06-30T23:59:59.8", utc);
717         FieldAbsoluteDate<T> inLeap     = new FieldAbsoluteDate<>(field, "2012-06-30T23:59:60.5", utc);
718         Assertions.assertEquals(0.7, inLeap.durationFrom(beforeLeap).getReal(), 1.0e-12);
719         Assertions.assertEquals("2012-06-30T23:59:60.500", inLeap.toString(utc));
720     }
721 
722     private <T extends CalculusFieldElement<T>> void doTestOutput(final Field<T> field) {
723         TimeScale tt = TimeScalesFactory.getTT();
724         Assertions.assertEquals("1950-01-01T01:01:01.000",
725                             FieldAbsoluteDate.getFiftiesEpoch(field).shiftedBy(3661.0).toString(tt));
726         Assertions.assertEquals("2000-01-01T13:01:01.000",
727                             FieldAbsoluteDate.getJ2000Epoch(field).shiftedBy(3661.0).toString(tt));
728     }
729 
730     private <T extends CalculusFieldElement<T>> void doTestJ2000(final Field<T> field) {
731         FieldAbsoluteDate<T> FAD = new FieldAbsoluteDate<>(field);
732         Assertions.assertEquals("2000-01-01T12:00:00.000",
733                             FAD.toString(TimeScalesFactory.getTT()));
734         Assertions.assertEquals("2000-01-01T11:59:27.816",
735                             FAD.toString(TimeScalesFactory.getTAI()));
736         Assertions.assertEquals("2000-01-01T11:58:55.816",
737                             FAD.toString(utc));
738         Assertions.assertEquals("2000-01-01T12:00:00.000",
739                             FieldAbsoluteDate.getJ2000Epoch(field).toString(TimeScalesFactory.getTT()));
740         Assertions.assertEquals("2000-01-01T11:59:27.816",
741                             FieldAbsoluteDate.getJ2000Epoch(field).toString(TimeScalesFactory.getTAI()));
742         Assertions.assertEquals("2000-01-01T11:58:55.816",
743                             FieldAbsoluteDate.getJ2000Epoch(field).toString(utc));
744     }
745 
746     private <T extends CalculusFieldElement<T>> void doTestFraction(final Field<T> field) {
747         FieldAbsoluteDate<T> d =
748             new FieldAbsoluteDate<>(field, new DateComponents(2000, 1, 1), new TimeComponents(11, 59, 27.816),
749                              TimeScalesFactory.getTAI());
750         Assertions.assertEquals(0, d.durationFrom(FieldAbsoluteDate.getJ2000Epoch(field)).getReal(), 1.0e-10);
751     }
752 
753     private <T extends CalculusFieldElement<T>> void doTestScalesOffset(final Field<T> field) {
754         FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, new DateComponents(2006, 2, 24),
755                                                             new TimeComponents(15, 38, 0),
756                                                             utc);
757         Assertions.assertEquals(33,
758                             date.timeScalesOffset(TimeScalesFactory.getTAI(), utc).getReal(),
759                             1.0e-10);
760     }
761 
762     private <T extends CalculusFieldElement<T>> void doTestUTC(final Field<T> field) {
763         FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, new DateComponents(2002, 1, 1),
764                                                             new TimeComponents(0, 0, 1),
765                                                             utc);
766         Assertions.assertEquals("2002-01-01T00:00:01.000Z", date.toString());
767     }
768 
769     private <T extends CalculusFieldElement<T>> void doTest1970(final Field<T> field) {
770         FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, new Date(0L), utc);
771         Assertions.assertEquals("1970-01-01T00:00:00.000Z", date.toString());
772     }
773 
774     private <T extends CalculusFieldElement<T>> void doTest1970Instant(final Field<T> field) {
775         Assertions.assertEquals("1970-01-01T00:00:00.000Z", new FieldAbsoluteDate<>(field, Instant.EPOCH, utc).toString());
776         Assertions.assertEquals("1970-01-01T00:00:00.000Z", new FieldAbsoluteDate<>(field, Instant.ofEpochMilli(0L), utc).toString());
777         Assertions.assertEquals("1970-01-01T00:00:00.000Z", new FieldAbsoluteDate<>(field, Instant.EPOCH, (UTCScale) utc).toString());
778         Assertions.assertEquals("1970-01-01T00:00:00.000Z", new FieldAbsoluteDate<>(field, Instant.ofEpochMilli(0L), (UTCScale) utc).toString());
779     }
780 
781     private <T extends CalculusFieldElement<T>> void doTestInstantAccuracy(final Field<T> field) {
782         Assertions.assertEquals("1970-01-02T00:16:40.123456789Z", new FieldAbsoluteDate<>(field, Instant.ofEpochSecond(87400, 123456789), utc).toString());
783         Assertions.assertEquals("1970-01-07T00:10:00.123456789Z", new FieldAbsoluteDate<>(field, Instant.ofEpochSecond(519000, 123456789), utc).toString());
784         Assertions.assertEquals("1970-01-02T00:16:40.123456789Z", new FieldAbsoluteDate<>(field, Instant.ofEpochSecond(87400, 123456789), (UTCScale) utc).toString());
785         Assertions.assertEquals("1970-01-07T00:10:00.123456789Z", new FieldAbsoluteDate<>(field, Instant.ofEpochSecond(519000, 123456789), (UTCScale) utc).toString());
786     }
787 
788     public <T extends CalculusFieldElement<T>> void doTestToInstant(final Field<T> field) {
789         Assertions.assertEquals(Instant.ofEpochSecond(0), new FieldAbsoluteDate<>(field, "1970-01-01T00:00:00.000Z", utc).toInstant());
790         Assertions.assertEquals(Instant.ofEpochSecond(0), new FieldAbsoluteDate<>(field, "1970-01-01T00:00:00.000Z", utc).toInstant(TimeScalesFactory.getTimeScales()));
791 
792         Instant expectedInstant = Instant.ofEpochSecond(519000, 123456789);
793         Assertions.assertEquals(expectedInstant, new FieldAbsoluteDate<>(field, "1970-01-07T00:10:00.123456789Z", utc).toInstant());
794         Assertions.assertEquals(expectedInstant, new FieldAbsoluteDate<>(field, "1970-01-07T00:10:00.123456789Z", utc).toInstant(TimeScalesFactory.getTimeScales()));
795 
796         Assertions.assertEquals(OffsetDateTime.parse("2024-05-15T09:32:36.123456789Z", DateTimeFormatter.ISO_DATE_TIME).toInstant(),
797             new FieldAbsoluteDate<>(field,"2024-05-15T09:32:36.123456789Z", utc).toInstant());
798         Assertions.assertEquals(OffsetDateTime.parse("2024-05-15T09:32:36.123456789Z", DateTimeFormatter.ISO_DATE_TIME).toInstant(),
799             new FieldAbsoluteDate<>(field, "2024-05-15T09:32:36.123456789Z", utc).toInstant(TimeScalesFactory.getTimeScales()));
800 
801     }
802 
803     private <T extends CalculusFieldElement<T>> void doTestUtcGpsOffset(final Field<T> field) {
804         FieldAbsoluteDate<T> date1   = new FieldAbsoluteDate<>(field, new DateComponents(2005, 8, 9),
805                                                                new TimeComponents(16, 31, 17),
806                                                                utc);
807         FieldAbsoluteDate<T> date2   = new FieldAbsoluteDate<>(field, new DateComponents(2006, 8, 9),
808                                                                new TimeComponents(16, 31, 17),
809                                                                utc);
810         FieldAbsoluteDate<T> dateRef = new FieldAbsoluteDate<>(field, new DateComponents(1980, 1, 6),
811                                                                TimeComponents.H00,
812                                                                utc);
813 
814         // 13 seconds offset between GPS time and UTC in 2005
815         long noLeapGap = ((9347 * 24 + 16) * 60 + 31) * 60 + 17;
816         long realGap   = (long) date1.durationFrom(dateRef).getReal();
817         Assertions.assertEquals(13L, realGap - noLeapGap);
818 
819         // 14 seconds offset between GPS time and UTC in 2006
820         noLeapGap = ((9712 * 24 + 16) * 60 + 31) * 60 + 17;
821         realGap   = (long) date2.durationFrom(dateRef).getReal();
822         Assertions.assertEquals(14L, realGap - noLeapGap);
823 
824     }
825 
826     private <T extends CalculusFieldElement<T>> void doTestGpsDate(final Field<T> field) {
827         FieldAbsoluteDate<T> date = FieldAbsoluteDate.createGPSDate(1387, field.getZero().add(318677000.0));
828         FieldAbsoluteDate<T> ref  = new FieldAbsoluteDate<>(field, new DateComponents(2006, 8, 9),
829                                                             new TimeComponents(16, 31, 3),
830                                                             utc);
831         Assertions.assertEquals(0, date.durationFrom(ref).getReal(), 1.0e-15);
832     }
833 
834     private <T extends CalculusFieldElement<T>> void doTestMJDDate(final Field<T> field) {
835         FieldAbsoluteDate<T> dateA = FieldAbsoluteDate.createMJDDate(51544, field.getZero().add(0.5 * Constants.JULIAN_DAY),
836                                                                      TimeScalesFactory.getTT());
837         Assertions.assertEquals(0.0, FieldAbsoluteDate.getJ2000Epoch(field).durationFrom(dateA).getReal(), 1.0e-15);
838         FieldAbsoluteDate<T> dateB = FieldAbsoluteDate.createMJDDate(53774, field.getZero(), TimeScalesFactory.getUTC());
839         FieldAbsoluteDate<T> dateC = new FieldAbsoluteDate<>(field, "2006-02-08T00:00:00", TimeScalesFactory.getUTC());
840         Assertions.assertEquals(0.0, dateC.durationFrom(dateB).getReal(), 1.0e-15);
841     }
842 
843     private <T extends CalculusFieldElement<T>> void doTestJDDate(final Field<T> field) {
844         final FieldAbsoluteDate<T> date = FieldAbsoluteDate.createJDDate(2400000, field.getZero().add(0.5 * Constants.JULIAN_DAY),
845                                                                          TimeScalesFactory.getTT());
846         Assertions.assertEquals(0.0, FieldAbsoluteDate.getModifiedJulianEpoch(field).durationFrom(date).getReal(), 1.0e-15);
847     }
848 
849     private <T extends CalculusFieldElement<T>> void doTestOffsets(final Field<T> field) {
850         final TimeScale tai = TimeScalesFactory.getTAI();
851         FieldAbsoluteDate<T> leapStartUTC = new FieldAbsoluteDate<>(field, 1976, 12, 31, 23, 59, 59, utc);
852         FieldAbsoluteDate<T> leapEndUTC   = new FieldAbsoluteDate<>(field, 1977,  1,  1,  0,  0,  0, utc);
853         FieldAbsoluteDate<T> leapStartTAI = new FieldAbsoluteDate<>(field, 1977,  1,  1,  0,  0, 14, tai);
854         FieldAbsoluteDate<T> leapEndTAI   = new FieldAbsoluteDate<>(field, 1977,  1,  1,  0,  0, 16, tai);
855         Assertions.assertEquals(leapStartUTC, leapStartTAI);
856         Assertions.assertEquals(leapEndUTC, leapEndTAI);
857         Assertions.assertEquals(1, leapEndUTC.offsetFrom(leapStartUTC, utc).getReal(), 1.0e-10);
858         Assertions.assertEquals(1, leapEndTAI.offsetFrom(leapStartTAI, utc).getReal(), 1.0e-10);
859         Assertions.assertEquals(2, leapEndUTC.offsetFrom(leapStartUTC, tai).getReal(), 1.0e-10);
860         Assertions.assertEquals(2, leapEndTAI.offsetFrom(leapStartTAI, tai).getReal(), 1.0e-10);
861         Assertions.assertEquals(2, leapEndUTC.durationFrom(leapStartUTC).getReal(),    1.0e-10);
862         Assertions.assertEquals(2, leapEndTAI.durationFrom(leapStartTAI).getReal(),    1.0e-10);
863     }
864 
865     private <T extends CalculusFieldElement<T>> void doTestBeforeAndAfterLeap(final Field<T> field) {
866         final TimeScale tai = TimeScalesFactory.getTAI();
867         FieldAbsoluteDate<T> leapStart = new FieldAbsoluteDate<>(field, 1977,  1,  1,  0,  0, 14, tai);
868         FieldAbsoluteDate<T> leapEnd   = new FieldAbsoluteDate<>(field, 1977,  1,  1,  0,  0, 16, tai);
869         for (int i = -10; i < 10; ++i) {
870             final double dt = 1.1 * (2 * i - 1);
871             FieldAbsoluteDate<T> d1 = leapStart.shiftedBy(dt);
872             FieldAbsoluteDate<T> d2 = new FieldAbsoluteDate<>(leapStart, dt, tai);
873             FieldAbsoluteDate<T> d3 = new FieldAbsoluteDate<>(leapStart, dt, utc);
874             FieldAbsoluteDate<T> d4 = new FieldAbsoluteDate<>(leapEnd,   dt, tai);
875             FieldAbsoluteDate<T> d5 = new FieldAbsoluteDate<>(leapEnd,   dt, utc);
876             Assertions.assertTrue(FastMath.abs(d1.durationFrom(d2).getReal()) < 1.0e-10);
877             if (dt < 0) {
878                 Assertions.assertTrue(FastMath.abs(d2.durationFrom(d3).getReal()) < 1.0e-10);
879                 Assertions.assertTrue(d4.durationFrom(d5).getReal() > (1.0 - 1.0e-10));
880             } else {
881                 Assertions.assertTrue(d2.durationFrom(d3).getReal() < (-1.0 + 1.0e-10));
882                 Assertions.assertTrue(FastMath.abs(d4.durationFrom(d5).getReal()) < 1.0e-10);
883             }
884         }
885     }
886 
887     private <T extends CalculusFieldElement<T>> void doTestSymmetry(final Field<T> field) {
888         final TimeScale tai = TimeScalesFactory.getTAI();
889         FieldAbsoluteDate<T> leapStart = new FieldAbsoluteDate<>(field, 1977,  1,  1,  0,  0, 14, tai);
890         for (int i = -10; i < 10; ++i) {
891             final double dt = 1.1 * (2 * i - 1);
892             Assertions.assertEquals(dt, new FieldAbsoluteDate<>(leapStart, dt, utc).offsetFrom(leapStart, utc).getReal(), 1.0e-10);
893             Assertions.assertEquals(dt, new FieldAbsoluteDate<>(leapStart, dt, tai).offsetFrom(leapStart, tai).getReal(), 1.0e-10);
894             Assertions.assertEquals(dt, leapStart.shiftedBy(dt).durationFrom(leapStart).getReal(), 1.0e-10);
895         }
896     }
897 
898     private <T extends CalculusFieldElement<T>> void doTestEquals(final Field<T> field) {
899         FieldAbsoluteDate<T> d1 =
900             new FieldAbsoluteDate<>(field, new DateComponents(2006, 2, 25),
901                                     new TimeComponents(17, 10, 34),
902                                     utc);
903         FieldAbsoluteDate<T> d2 = new FieldAbsoluteDate<>(field, new DateComponents(2006, 2, 25),
904                                                           new TimeComponents(17, 10, 0),
905                                                           utc).shiftedBy(34);
906         Assertions.assertEquals(d1, d2);
907         Assertions.assertNotEquals(d1, this);
908     }
909 
910     private <T extends CalculusFieldElement<T>> void doTestIsEqualTo(final Field<T> field) {
911         TestDates<T> dates = new TestDates<>(field);
912         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
913         Assertions.assertTrue(present.isEqualTo(dates.present));
914         Assertions.assertTrue(present.isEqualTo(dates.presentToo));
915         Assertions.assertFalse(present.isEqualTo(dates.past));
916         Assertions.assertFalse(present.isEqualTo(dates.future));
917     }
918 
919     private <T extends CalculusFieldElement<T>> void doTestIsCloseTo(final Field<T> field) {
920         double tolerance = 10;
921         TestDates<T> dates = new TestDates<>(field);
922         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
923         FieldTimeStamped<T> closeToPresent = new AnyFieldTimeStamped<>(field, dates.presentDate.shiftedBy(5));
924         Assertions.assertTrue(present.isCloseTo(present, tolerance));
925         Assertions.assertTrue(present.isCloseTo(dates.presentToo, tolerance));
926         Assertions.assertTrue(present.isCloseTo(closeToPresent, tolerance));
927         Assertions.assertFalse(present.isCloseTo(dates.past, tolerance));
928         Assertions.assertFalse(present.isCloseTo(dates.future, tolerance));
929     }
930 
931     private <T extends CalculusFieldElement<T>> void doTestIsBefore(final Field<T> field) {
932         TestDates<T> dates = new TestDates<>(field);
933         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
934         Assertions.assertFalse(present.isBefore(dates.past));
935         Assertions.assertFalse(present.isBefore(present));
936         Assertions.assertFalse(present.isBefore(dates.presentToo));
937         Assertions.assertTrue(present.isBefore(dates.future));
938     }
939 
940     private <T extends CalculusFieldElement<T>> void doTestIsAfter(final Field<T> field) {
941         TestDates<T> dates = new TestDates<>(field);
942         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
943         Assertions.assertTrue(present.isAfter(dates.past));
944         Assertions.assertFalse(present.isAfter(present));
945         Assertions.assertFalse(present.isAfter(dates.presentToo));
946         Assertions.assertFalse(present.isAfter(dates.future));
947     }
948 
949     private <T extends CalculusFieldElement<T>> void doTestIsBeforeOrEqualTo(final Field<T> field) {
950         TestDates<T> dates = new TestDates<>(field);
951         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
952         Assertions.assertFalse(present.isBeforeOrEqualTo(dates.past));
953         Assertions.assertTrue(present.isBeforeOrEqualTo(present));
954         Assertions.assertTrue(present.isBeforeOrEqualTo(dates.presentToo));
955         Assertions.assertTrue(present.isBeforeOrEqualTo(dates.future));
956     }
957 
958     private <T extends CalculusFieldElement<T>> void doTestIsAfterOrEqualTo(final Field<T> field) {
959         TestDates<T> dates = new TestDates<>(field);
960         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
961         Assertions.assertTrue(present.isAfterOrEqualTo(dates.past));
962         Assertions.assertTrue(present.isAfterOrEqualTo(present));
963         Assertions.assertTrue(present.isAfterOrEqualTo(dates.presentToo));
964         Assertions.assertFalse(present.isAfterOrEqualTo(dates.future));
965     }
966 
967     private <T extends CalculusFieldElement<T>> void doTestIsBetween(final Field<T> field) {
968         TestDates<T> dates = new TestDates<>(field);
969         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
970         Assertions.assertTrue(present.isBetween(dates.past, dates.future));
971         Assertions.assertTrue(present.isBetween(dates.future, dates.past));
972         Assertions.assertFalse(dates.past.getDate().isBetween(present, dates.future));
973         Assertions.assertFalse(dates.past.getDate().isBetween(dates.future, present));
974         Assertions.assertFalse(dates.future.getDate().isBetween(dates.past, present));
975         Assertions.assertFalse(dates.future.getDate().isBetween(present, dates.past));
976         Assertions.assertFalse(present.isBetween(present, dates.future));
977         Assertions.assertFalse(present.isBetween(dates.past, present));
978         Assertions.assertFalse(present.isBetween(dates.past, dates.past));
979         Assertions.assertFalse(present.isBetween(present, present));
980         Assertions.assertFalse(present.isBetween(present, dates.presentToo));
981     }
982 
983     private <T extends CalculusFieldElement<T>> void doTestIsBetweenOrEqualTo(final Field<T> field) {
984         TestDates<T> dates = new TestDates<>(field);
985         FieldAbsoluteDate<T> present = dates.getPresentFieldAbsoluteDate();
986         Assertions.assertTrue(present.isBetweenOrEqualTo(dates.past, dates.future));
987         Assertions.assertTrue(present.isBetweenOrEqualTo(dates.future, dates.past));
988         Assertions.assertFalse(dates.past.getDate().isBetweenOrEqualTo(present, dates.future));
989         Assertions.assertFalse(dates.past.getDate().isBetweenOrEqualTo(dates.future, present));
990         Assertions.assertFalse(dates.future.getDate().isBetweenOrEqualTo(dates.past, present));
991         Assertions.assertFalse(dates.future.getDate().isBetweenOrEqualTo(present, dates.past));
992         Assertions.assertTrue(present.isBetweenOrEqualTo(present, dates.future));
993         Assertions.assertTrue(present.isBetweenOrEqualTo(dates.past, present));
994         Assertions.assertFalse(present.isBetweenOrEqualTo(dates.past, dates.past));
995         Assertions.assertTrue(present.isBetweenOrEqualTo(present, present));
996         Assertions.assertTrue(present.isBetweenOrEqualTo(present, dates.presentToo));
997     }
998 
999     private <T extends CalculusFieldElement<T>> void doTestComponents(final Field<T> field) {
1000         // this is NOT J2000.0,
1001         // it is either a few seconds before or after depending on time scale
1002         DateComponents date = new DateComponents(2000, 1, 1);
1003         TimeComponents time = new TimeComponents(11, 59, 10);
1004         TimeScale[] scales = {
1005             TimeScalesFactory.getTAI(), TimeScalesFactory.getUTC(),
1006             TimeScalesFactory.getTT(), TimeScalesFactory.getTCG()
1007         };
1008         for (int i = 0; i < scales.length; ++i) {
1009             FieldAbsoluteDate<T> in = new FieldAbsoluteDate<>(field, date, time, scales[i]);
1010             for (int j = 0; j < scales.length; ++j) {
1011                 DateTimeComponents pair = in.getComponents(scales[j]);
1012                 if (i == j) {
1013                     Assertions.assertEquals(date, pair.getDate());
1014                     Assertions.assertEquals(time, pair.getTime());
1015                 } else {
1016                     Assertions.assertNotSame(date, pair.getDate());
1017                     Assertions.assertNotSame(time, pair.getTime());
1018                 }
1019             }
1020         }
1021     }
1022 
1023     private <T extends CalculusFieldElement<T>> void doTestMonth(final Field<T> field) {
1024         TimeScale utc = TimeScalesFactory.getUTC();
1025         Assertions.assertEquals(new FieldAbsoluteDate<>(field, 2011, 2, 23, utc),
1026                             new FieldAbsoluteDate<>(field, 2011, Month.FEBRUARY, 23, utc));
1027         Assertions.assertEquals(new FieldAbsoluteDate<>(field, 2011, 2, 23, 1, 2, 3.4, utc),
1028                             new FieldAbsoluteDate<>(field, 2011, Month.FEBRUARY, 23, 1, 2, 3.4, utc));
1029     }
1030 
1031     private <T extends CalculusFieldElement<T>> void doTestCCSDSUnsegmentedNoExtension(final Field<T> field) {
1032         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, "2002-05-23T12:34:56.789", utc);
1033         double lsb = FastMath.pow(2.0, -24);
1034 
1035         byte[] timeCCSDSEpoch = new byte[] { 0x53, 0x7F, 0x40, -0x70, -0x37, -0x05, -0x19 };
1036         for (int preamble = 0x00; preamble < 0x80; ++preamble) {
1037             if (preamble == 0x1F) {
1038                 // using CCSDS reference epoch
1039                 FieldAbsoluteDate<T> ccsds1 =
1040                                 FieldAbsoluteDate.parseCCSDSUnsegmentedTimeCode(field, (byte) preamble, (byte) 0x0, timeCCSDSEpoch, null);
1041                 Assertions.assertEquals(0, ccsds1.durationFrom(reference).getReal(), lsb / 2);
1042             } else {
1043                 try {
1044                     FieldAbsoluteDate.parseCCSDSUnsegmentedTimeCode(field, (byte) preamble, (byte) 0x0, timeCCSDSEpoch, null);
1045                     Assertions.fail("an exception should have been thrown");
1046                 } catch (OrekitException iae) {
1047                     // expected
1048                 }
1049 
1050             }
1051         }
1052 
1053         // missing epoch
1054         byte[] timeJ2000Epoch = new byte[] { 0x04, 0x7E, -0x0B, -0x10, -0x07, 0x16, -0x79 };
1055         try {
1056             FieldAbsoluteDate.parseCCSDSUnsegmentedTimeCode(field, (byte) 0x2F, (byte) 0x0, timeJ2000Epoch, null);
1057             Assertions.fail("an exception should have been thrown");
1058         } catch (OrekitException iae) {
1059             // expected
1060         }
1061 
1062         // using J2000.0 epoch
1063         FieldAbsoluteDate<T> ccsds3 =
1064                         FieldAbsoluteDate.parseCCSDSUnsegmentedTimeCode(field, (byte) 0x2F, (byte) 0x0, timeJ2000Epoch,
1065                                                                         FieldAbsoluteDate.getJ2000Epoch(field));
1066         Assertions.assertEquals(0, ccsds3.durationFrom(reference).getReal(), lsb / 2);
1067 
1068     }
1069 
1070     private <T extends CalculusFieldElement<T>> void doTestCCSDSUnsegmentedWithExtendedPreamble(final Field<T> field) {
1071 
1072         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, "2095-03-03T22:02:45.789012345678901", utc);
1073         int leap = (int) FastMath.rint(utc.offsetFromTAI(reference.toAbsoluteDate()).toDouble());
1074 
1075         byte extendedPreamble = (byte) -0x80;
1076         byte identification   = (byte)  0x10;
1077         byte coarseLength1    = (byte)  0x0C; // four (3 + 1) bytes
1078         byte fineLength1      = (byte)  0x03; // 3 bytes
1079         byte coarseLength2    = (byte)  0x20; // 1 additional byte for coarse time
1080         byte fineLength2      = (byte)  0x10; // 4 additional bytes for fine time
1081         byte[] timeCCSDSEpoch = new byte[] {
1082              0x01,  0x02,  0x03,  0x04,  (byte)(0x05 - leap), // 5 bytes for coarse time (seconds)
1083             -0x37, -0x04, -0x4A, -0x74, -0x2C, -0x3C, -0x48   // 7 bytes for fine time (sub-seconds)
1084         };
1085         byte preamble1 = (byte) (extendedPreamble | identification | coarseLength1 | fineLength1);
1086         byte preamble2 = (byte) (coarseLength2 | fineLength2);
1087         FieldAbsoluteDate<T> ccsds1 =
1088                         FieldAbsoluteDate.parseCCSDSUnsegmentedTimeCode(field, preamble1, preamble2, timeCCSDSEpoch, null);
1089 
1090         // The 8 attoseconds difference comes from the fact unsegmented time is
1091         // in powers of 1/256 s, so it is not a whole number of attoseconds
1092         Assertions.assertEquals(-8.0e-18, ccsds1.durationFrom(reference).getReal(), 1.0e-18);
1093 
1094     }
1095 
1096     private <T extends CalculusFieldElement<T>> void doTestCCSDSDaySegmented(final Field<T> field) {
1097         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, "2002-05-23T12:34:56.789012345678", TimeScalesFactory.getUTC());
1098         double lsb = 1.0e-13;
1099         byte[] timeCCSDSEpoch = new byte[] { 0x3F, 0x55, 0x02, -0x4D, 0x2C, -0x6B, 0x00, -0x44, 0x61, 0x4E };
1100 
1101         for (int preamble = 0x00; preamble < 0x100; ++preamble) {
1102             if (preamble == 0x42) {
1103                 // using CCSDS reference epoch
1104 
1105                 FieldAbsoluteDate<T> ccsds1 =
1106                                 FieldAbsoluteDate.parseCCSDSDaySegmentedTimeCode(field, (byte) preamble, timeCCSDSEpoch, null);
1107                 Assertions.assertEquals(0, ccsds1.durationFrom(reference).getReal(), lsb / 2);
1108             } else {
1109                 try {
1110                     FieldAbsoluteDate.parseCCSDSDaySegmentedTimeCode(field, (byte) preamble, timeCCSDSEpoch, null);
1111                     Assertions.fail("an exception should have been thrown");
1112                 } catch (OrekitException iae) {
1113                     // expected
1114                 }
1115 
1116             }
1117         }
1118 
1119         // missing epoch
1120         byte[] timeJ2000Epoch = new byte[] { 0x03, 0x69, 0x02, -0x4D, 0x2C, -0x6B, 0x00, -0x44, 0x61, 0x4E };
1121         try {
1122             FieldAbsoluteDate.parseCCSDSDaySegmentedTimeCode(field, (byte) 0x4A, timeJ2000Epoch, null);
1123             Assertions.fail("an exception should have been thrown");
1124         } catch (OrekitException iae) {
1125             // expected
1126         }
1127 
1128         // using J2000.0 epoch
1129         FieldAbsoluteDate<T> ccsds3 =
1130                         FieldAbsoluteDate.parseCCSDSDaySegmentedTimeCode(field, (byte) 0x4A, timeJ2000Epoch, DateComponents.J2000_EPOCH);
1131         Assertions.assertEquals(0, ccsds3.durationFrom(reference).getReal(), lsb / 2);
1132 
1133         // limit to microsecond
1134         byte[] timeMicrosecond = new byte[] { 0x03, 0x69, 0x02, -0x4D, 0x2C, -0x6B, 0x00, 0x0C };
1135         FieldAbsoluteDate<T> ccsds4 =
1136                         FieldAbsoluteDate.parseCCSDSDaySegmentedTimeCode(field, (byte) 0x49, timeMicrosecond, DateComponents.J2000_EPOCH);
1137         Assertions.assertEquals(-0.345678e-6, ccsds4.durationFrom(reference).getReal(), lsb / 2);
1138 
1139     }
1140 
1141     private <T extends CalculusFieldElement<T>> void doTestCCSDSCalendarSegmented(final Field<T> field) {
1142 
1143         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, "2002-05-23T12:34:56.789012345678", TimeScalesFactory.getUTC());
1144         double lsb = 1.0e-13;
1145         FieldAbsoluteDate<T> FAD = new FieldAbsoluteDate<>(field);
1146         // month of year / day of month variation
1147         byte[] timeMonthDay = new byte[] { 0x07, -0x2E, 0x05, 0x17, 0x0C, 0x22, 0x38, 0x4E, 0x5A, 0x0C, 0x22, 0x38, 0x4E };
1148         for (int preamble = 0x00; preamble < 0x100; ++preamble) {
1149             if (preamble == 0x56) {
1150                 FieldAbsoluteDate<T> ccsds1 =
1151                     FAD.parseCCSDSCalendarSegmentedTimeCode((byte) preamble, timeMonthDay);
1152                 Assertions.assertEquals(0, ccsds1.durationFrom(reference).getReal(), lsb / 2);
1153             } else {
1154                 try {
1155                     FAD.parseCCSDSCalendarSegmentedTimeCode((byte) preamble, timeMonthDay);
1156                     Assertions.fail("an exception should have been thrown");
1157                 } catch (OrekitException iae) {
1158                     // expected
1159                 } catch (IllegalArgumentException iae) {
1160                     // should happen when preamble specifies day of year variation
1161                     // since there is no day 1303 (= 5 * 256 + 23) in any year ...
1162                     Assertions.assertEquals(preamble & 0x08, 0x08);
1163                 }
1164 
1165             }
1166         }
1167 
1168         // day of year variation
1169         byte[] timeDay = new byte[] { 0x07, -0x2E, 0x00, -0x71, 0x0C, 0x22, 0x38, 0x4E, 0x5A, 0x0C, 0x22, 0x38, 0x4E };
1170         for (int preamble = 0x00; preamble < 0x100; ++preamble) {
1171             if (preamble == 0x5E) {
1172                 FieldAbsoluteDate<T> ccsds1 =
1173                     FAD.parseCCSDSCalendarSegmentedTimeCode((byte) preamble, timeDay);
1174                 Assertions.assertEquals(0, ccsds1.durationFrom(reference).getReal(), lsb / 2);
1175             } else {
1176                 try {
1177                     FAD.parseCCSDSCalendarSegmentedTimeCode((byte) preamble, timeDay);
1178                     Assertions.fail("an exception should have been thrown");
1179                 } catch (OrekitException iae) {
1180                     // expected
1181                 } catch (IllegalArgumentException iae) {
1182                     // should happen when preamble specifies month of year / day of month variation
1183                     // since there is no month 0 in any year ...
1184                     Assertions.assertEquals(preamble & 0x08, 0x00);
1185                 }
1186 
1187             }
1188         }
1189 
1190         // limit to microsecond
1191         byte[] timeMicrosecond = new byte[] { 0x07, -0x2E, 0x00, -0x71, 0x0C, 0x22, 0x38, 0x4E, 0x5A, 0x0C };
1192         FieldAbsoluteDate<T> ccsds4 =
1193             FAD.parseCCSDSCalendarSegmentedTimeCode((byte) 0x5B, timeMicrosecond);
1194         Assertions.assertEquals(-0.345678e-6, ccsds4.durationFrom(reference).getReal(), lsb / 2);
1195 
1196     }
1197 
1198     private <T extends CalculusFieldElement<T>> void doTestExpandedConstructors(final Field<T> field) {
1199         Assertions.assertEquals(new FieldAbsoluteDate<>(field, new DateComponents(2002, 5, 28),
1200                                                     new TimeComponents(15, 30, 0),
1201                                                     TimeScalesFactory.getUTC()),
1202                      new FieldAbsoluteDate<>(field, 2002, 5, 28, 15, 30, 0, TimeScalesFactory.getUTC()));
1203         Assertions.assertEquals(new FieldAbsoluteDate<>(field, new DateComponents(2002, 5, 28), TimeComponents.H00,
1204                                                     TimeScalesFactory.getUTC()),
1205                      new FieldAbsoluteDate<>(field, 2002, 5, 28, TimeScalesFactory.getUTC()));
1206         try {
1207             new FieldAbsoluteDate<>(field, 2002, 5, 28, 25, 30, 0, TimeScalesFactory.getUTC());
1208         } catch (OrekitIllegalArgumentException oiae) {
1209             Assertions.assertEquals(OrekitMessages.NON_EXISTENT_HMS_TIME, oiae.getSpecifier());
1210             Assertions.assertEquals(25, ((Integer) oiae.getParts()[0]).intValue());
1211             Assertions.assertEquals(30, ((Integer) oiae.getParts()[1]).intValue());
1212             Assertions.assertEquals( 0, ((Double) oiae.getParts()[2]), 1.0e-15);
1213         }
1214     }
1215 
1216     private <T extends CalculusFieldElement<T>> void doTestHashcode(final Field<T> field) {
1217         FieldAbsoluteDate<T> d1 =
1218             new FieldAbsoluteDate<>(field, new DateComponents(2006, 2, 25),
1219                                     new TimeComponents(17, 10, 34),
1220                                     utc);
1221         FieldAbsoluteDate<T> d2 = new FieldAbsoluteDate<>(field, new DateComponents(2006, 2, 25),
1222                                                           new TimeComponents(17, 10, 0),
1223                                                           utc).shiftedBy(34);
1224         Assertions.assertEquals(d1.hashCode(), d2.hashCode());
1225         Assertions.assertTrue(d1.hashCode() != d1.shiftedBy(1.0e-3).hashCode());
1226     }
1227 
1228     private <T extends CalculusFieldElement<T>> void doTestInfinity(final Field<T> field) {
1229         Assertions.assertTrue(FieldAbsoluteDate.getJulianEpoch(field).compareTo(FieldAbsoluteDate.getPastInfinity(field)) > 0);
1230         Assertions.assertTrue(FieldAbsoluteDate.getJulianEpoch(field).compareTo(FieldAbsoluteDate.getFutureInfinity(field)) < 0);
1231         Assertions.assertTrue(FieldAbsoluteDate.getJ2000Epoch(field).compareTo(FieldAbsoluteDate.getPastInfinity(field)) > 0);
1232         Assertions.assertTrue(FieldAbsoluteDate.getJ2000Epoch(field).compareTo(FieldAbsoluteDate.getFutureInfinity(field)) < 0);
1233         Assertions.assertTrue(FieldAbsoluteDate.getPastInfinity(field).compareTo(FieldAbsoluteDate.getJulianEpoch(field)) < 0);
1234         Assertions.assertTrue(FieldAbsoluteDate.getPastInfinity(field).compareTo(FieldAbsoluteDate.getJ2000Epoch(field)) < 0);
1235         Assertions.assertTrue(FieldAbsoluteDate.getPastInfinity(field).compareTo(FieldAbsoluteDate.getFutureInfinity(field)) < 0);
1236         Assertions.assertTrue(FieldAbsoluteDate.getFutureInfinity(field).compareTo(FieldAbsoluteDate.getJulianEpoch(field)) > 0);
1237         Assertions.assertTrue(FieldAbsoluteDate.getFutureInfinity(field).compareTo(FieldAbsoluteDate.getJ2000Epoch(field)) > 0);
1238         Assertions.assertTrue(FieldAbsoluteDate.getFutureInfinity(field).compareTo(FieldAbsoluteDate.getPastInfinity(field)) > 0);
1239         Assertions.assertTrue(Double.isInfinite(FieldAbsoluteDate.getFutureInfinity(field).durationFrom(FieldAbsoluteDate.getJ2000Epoch(field)).getReal()));
1240         Assertions.assertTrue(Double.isInfinite(FieldAbsoluteDate.getFutureInfinity(field).durationFrom(FieldAbsoluteDate.getPastInfinity(field)).getReal()));
1241         Assertions.assertTrue(Double.isInfinite(FieldAbsoluteDate.getPastInfinity(field).durationFrom(FieldAbsoluteDate.getJ2000Epoch(field)).getReal()));
1242         Assertions.assertEquals("5881610-07-11T23:59:59.999Z",  FieldAbsoluteDate.getFutureInfinity(field).toString());
1243         Assertions.assertEquals("-5877490-03-03T00:00:00.000Z", FieldAbsoluteDate.getPastInfinity(field).toString());
1244 
1245         final FieldAbsoluteDate<T> j2000     = FieldAbsoluteDate.getJ2000Epoch(field);
1246         final FieldAbsoluteDate<T> arbitrary = FieldAbsoluteDate.getArbitraryEpoch(field);
1247         Assertions.assertEquals(Double.POSITIVE_INFINITY,
1248                                 j2000.durationFrom(arbitrary.shiftedBy(Double.NEGATIVE_INFINITY)).getReal());
1249         Assertions.assertEquals(Double.NEGATIVE_INFINITY,
1250                                 j2000.durationFrom(arbitrary.shiftedBy(Double.POSITIVE_INFINITY)).getReal());
1251         Assertions.assertEquals(Double.POSITIVE_INFINITY, j2000.durationFrom(arbitrary.shiftedBy(field.getZero().add(Double.NEGATIVE_INFINITY))).getReal());
1252         Assertions.assertEquals(Double.NEGATIVE_INFINITY,
1253                                 j2000.durationFrom(arbitrary.shiftedBy(field.getZero().add(Double.POSITIVE_INFINITY))).getReal());
1254 
1255     }
1256 
1257     private <T extends CalculusFieldElement<T>> void doTestAccuracy(final Field<T> field) {
1258         TimeScale tai = TimeScalesFactory.getTAI();
1259         double sec = 0.281;
1260         FieldAbsoluteDate<T> t = new FieldAbsoluteDate<>(field, 2010, 6, 21, 18, 42, sec, tai);
1261         double recomputedSec = t.getComponents(tai).getTime().getSecond();
1262         Assertions.assertEquals(sec, recomputedSec, FastMath.ulp(sec));
1263     }
1264 
1265     private <T extends CalculusFieldElement<T>> void doTestAccuracyIssue348(final Field<T> field)
1266         {
1267         FieldAbsoluteDate<T> tF = new FieldAbsoluteDate<>(field,
1268                                                           new DateComponents(1970, 1, 1),
1269                                                           new TimeComponents(3, 25, 45.6789),
1270                                                           TimeScalesFactory.getUTC());
1271         AbsoluteDate tA = tF.toAbsoluteDate();
1272         double delta = -0.01;
1273         T recomputedDelta = tF.shiftedBy(delta).durationFrom(tA);
1274         Assertions.assertEquals(delta, recomputedDelta.getReal(), 1.0e-17);
1275     }
1276 
1277     private <T extends CalculusFieldElement<T>> void doTestIterationAccuracy(final Field<T> field) {
1278 
1279         final TimeScale tai = TimeScalesFactory.getTAI();
1280         final FieldAbsoluteDate<T> t0 = new FieldAbsoluteDate<>(field, 2010, 6, 21, 18, 42, 0.281, tai);
1281 
1282         // 0.1 is not representable exactly in double precision
1283         // we will accumulate error, between -0.5ULP and -3ULP at each iteration
1284         checkIteration(0.1, t0, 10000, 3.0, -0.3874, 1.0e-4);
1285 
1286         // 0.125 is representable exactly in double precision
1287         // error will be null
1288         checkIteration(0.125, t0, 10000, 1.0e-15, 0.0, 1.0e-15);
1289     }
1290 
1291     private <T extends CalculusFieldElement<T>> void checkIteration(final double step, final FieldAbsoluteDate<T> t0, final int nMax,
1292                                 final double maxErrorFactor,
1293                                 final double expectedMean, final double meanTolerance) {
1294         final double epsilon = FastMath.ulp(step);
1295         FieldAbsoluteDate<T> iteratedDate = t0;
1296         double mean = 0;
1297         for (int i = 1; i < nMax; ++i) {
1298             iteratedDate = iteratedDate.shiftedBy(step);
1299             FieldAbsoluteDate<T> directDate = t0.shiftedBy(i * step);
1300             final T error = iteratedDate.durationFrom(directDate);
1301             mean += error.getReal() / (i * epsilon);
1302             Assertions.assertEquals(0.0, iteratedDate.durationFrom(directDate).getReal(), maxErrorFactor * i * epsilon);
1303         }
1304         mean /= nMax;
1305         Assertions.assertEquals(expectedMean, mean, meanTolerance);
1306     }
1307 
1308     private <T extends CalculusFieldElement<T>> void doTestIssue142(final Field<T> field) {
1309         final FieldAbsoluteDate<T> epoch = FieldAbsoluteDate.getJavaEpoch(field);
1310         final TimeScale utc = TimeScalesFactory.getUTC();
1311 
1312         Assertions.assertEquals("1970-01-01T00:00:00.000", epoch.toString(utc));
1313         Assertions.assertEquals(0.0, epoch.durationFrom(new FieldAbsoluteDate<>(field, 1970, 1, 1, utc)).getReal(), 1.0e-15);
1314         Assertions.assertEquals(8.000082,
1315                             epoch.durationFrom(new FieldAbsoluteDate<>(field, DateComponents.JAVA_EPOCH, TimeScalesFactory.getTAI())).getReal(),
1316                             1.0e-15);
1317 
1318         //Milliseconds - April 1, 2006, in UTC
1319         long msOffset = 1143849600000L;
1320         final FieldAbsoluteDate<T> ad = new FieldAbsoluteDate<>(epoch, msOffset / 1000, TimeScalesFactory.getUTC());
1321         Assertions.assertEquals("2006-04-01T00:00:00.000", ad.toString(utc));
1322     }
1323 
1324     private <T extends CalculusFieldElement<T>> void doTestIssue148(final Field<T> field) {
1325         final TimeScale utc = TimeScalesFactory.getUTC();
1326         FieldAbsoluteDate<T> t0 = new FieldAbsoluteDate<>(field, 2012, 6, 30, 23, 59, 50.0, utc);
1327         DateTimeComponents components = t0.shiftedBy(11.0 - 200 * Precision.EPSILON).getComponents(utc);
1328         Assertions.assertEquals(2012, components.getDate().getYear());
1329         Assertions.assertEquals(   6, components.getDate().getMonth());
1330         Assertions.assertEquals(  30, components.getDate().getDay());
1331         Assertions.assertEquals(  23, components.getTime().getHour());
1332         Assertions.assertEquals(  59, components.getTime().getMinute());
1333         Assertions.assertEquals(  61 - 200 * Precision.EPSILON,
1334                             components.getTime().getSecond(), 1.0e-15);
1335     }
1336 
1337     private <T extends CalculusFieldElement<T>> void doTestIssue149(final Field<T> field) {
1338         final TimeScale utc = TimeScalesFactory.getUTC();
1339         FieldAbsoluteDate<T> t0 = new FieldAbsoluteDate<>(field, 2012, 6, 30, 23, 59, 59, utc);
1340         DateTimeComponents components = t0.shiftedBy(1.0 - Precision.EPSILON).getComponents(utc);
1341         Assertions.assertEquals(2012, components.getDate().getYear());
1342         Assertions.assertEquals(   6, components.getDate().getMonth());
1343         Assertions.assertEquals(  30, components.getDate().getDay());
1344         Assertions.assertEquals(  23, components.getTime().getHour());
1345         Assertions.assertEquals(  59, components.getTime().getMinute());
1346         Assertions.assertEquals(  60 - Precision.EPSILON,
1347                             components.getTime().getSecond(), 1.0e-15);
1348     }
1349 
1350     private <T extends CalculusFieldElement<T>> void doTestWrapAtMinuteEnd(final Field<T> field) {
1351         TimeScale tai = TimeScalesFactory.getTAI();
1352         TimeScale utc = TimeScalesFactory.getUTC();
1353         FieldAbsoluteDate<T> date0 = new FieldAbsoluteDate<>(field, DateComponents.J2000_EPOCH, TimeComponents.H12, tai);
1354         FieldAbsoluteDate<T> ref = date0.shiftedBy(new TimeOffset(496891466L, 732011406663332300L));
1355         FieldAbsoluteDate<T> date = ref.shiftedBy(new TimeOffset(597L, 900970042626200000L).negate().multiply(33));
1356         DateTimeComponents dtc = date.getComponents(utc);
1357         Assertions.assertEquals(                 2015, dtc.getDate().getYear());
1358         Assertions.assertEquals(                    9, dtc.getDate().getMonth());
1359         Assertions.assertEquals(                   30, dtc.getDate().getDay());
1360         Assertions.assertEquals(                    7, dtc.getTime().getHour());
1361         Assertions.assertEquals(                   54, dtc.getTime().getMinute());
1362         Assertions.assertEquals(                  59L, dtc.getTime().getSplitSecond().getSeconds());
1363         Assertions.assertEquals(  999999999998732300L, dtc.getTime().getSplitSecond().getAttoSeconds());
1364         Assertions.assertEquals("2015-09-30T07:54:59.9999999999987323",
1365                             date.toString(utc));
1366         FieldAbsoluteDate<T> beforeMidnight = new FieldAbsoluteDate<>(field, 2008, 2, 29, 23, 59,
1367                                                                       new TimeOffset(59L, 999400000000000000L), utc);
1368         FieldAbsoluteDate<T> stillBeforeMidnight = beforeMidnight.shiftedBy(2.0e-4);
1369         Assertions.assertEquals(59.9994, beforeMidnight.getComponents(utc).getTime().getSecond(), 1.0e-15);
1370         Assertions.assertEquals(59.9996, stillBeforeMidnight.getComponents(utc).getTime().getSecond(), 1.0e-15);
1371         Assertions.assertEquals("2008-02-29T23:59:59.9994", beforeMidnight.toString(utc));
1372         Assertions.assertEquals("2008-02-29T23:59:59.9996", stillBeforeMidnight.toString(utc));
1373     }
1374 
1375     private <T extends CalculusFieldElement<T>> void doTestIssue508(final Field<T> field) {
1376         AbsoluteDate date = new AbsoluteDate(2000, 2, 24, 17, 5, 30.047, TimeScalesFactory.getUTC());
1377         FieldAbsoluteDate<T> tA = new FieldAbsoluteDate<>(field, date);
1378         FieldAbsoluteDate<T> tB = new FieldAbsoluteDate<>(date, field.getZero());
1379         Assertions.assertEquals(0.0, tA.durationFrom(tB).getReal(), Precision.SAFE_MIN);
1380     }
1381 
1382     public <T extends CalculusFieldElement<T>> void doTestGetComponentsIssue681and676and694(
1383             final Field<T> field) {
1384 
1385         // setup
1386         FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, 2009, 1, 1, utc);
1387         double attoSecond = 1.0e-18;
1388         double zeroUlp = FastMath.nextUp(0.0);
1389         double oneUlp = FastMath.ulp(1.0);
1390         double sixtyUlp = FastMath.ulp(60.0);
1391         double one = FastMath.nextDown(1.0);
1392         double sixty = FastMath.nextDown(60.0);
1393         double sixtyOne = FastMath.nextDown(61.0);
1394 
1395         // actions + verify
1396         // translate back to AbsoluteDate has up to half an ULP of error,
1397         // except when truncated when the error can be up to 1 ULP.
1398         check(date, 2009, 1, 1, 0, 0, 0, 1, 0, 0);
1399         check(date.shiftedBy(attoSecond), 2009, 1, 1, 0, 0, attoSecond, 0.5, 0, 0);
1400         check(date.shiftedBy(one), 2009, 1, 1, 0, 0, one, 0.5, 0, 0);
1401         // I could also see rounding to a valid time as being reasonable here
1402         check(date.shiftedBy(59).shiftedBy(one), 2009, 1, 1, 0, 0, sixty, 1, 1, 0);
1403         check(date.shiftedBy(86399).shiftedBy(one), 2009, 1, 1, 23, 59, sixty, 1, 1, 0);
1404         check(date.shiftedBy(-zeroUlp), 2009, 1, 1, 0, 0, 0, 0.5, 0, 0);
1405         check(date.shiftedBy(-oneUlp), 2008, 12, 31, 23, 59, sixtyOne, 1, 1, 0);
1406         check(date.shiftedBy(-1).shiftedBy(zeroUlp), 2008, 12, 31, 23, 59, 60.0, 0.5, 0, 0);
1407         check(date.shiftedBy(-1).shiftedBy(-zeroUlp), 2008, 12, 31, 23, 59, 60.0, 0.5, 0, 0);
1408         check(date.shiftedBy(-1).shiftedBy(-oneUlp), 2008, 12, 31, 23, 59, 60.0, 0.5, 0, 0);
1409         check(date.shiftedBy(-1).shiftedBy(-sixtyUlp), 2008, 12, 31, 23, 59, sixty, 0.5, 0, 0);
1410         check(date.shiftedBy(-61).shiftedBy(attoSecond), 2008, 12, 31, 23, 59, attoSecond, 0.5, 0, 0);
1411 
1412         // check UTC weirdness.
1413         // These have more error because of additional multiplications and additions
1414         // up to 2 ULPs or ulp(60.0) of error.
1415         FieldAbsoluteDate<T> d = new FieldAbsoluteDate<>(field, 1966, 1, 1, utc);
1416         double ratePost = 0.0025920 / Constants.JULIAN_DAY;
1417         double factorPost = ratePost / (1 + ratePost);
1418         double ratePre = 0.0012960 / Constants.JULIAN_DAY;
1419         double factorPre = ratePre / (1 + ratePre);
1420         check(d, 1966, 1, 1, 0, 0, 0, 1, 0, 0);
1421         check(d.shiftedBy(zeroUlp), 1966, 1, 1, 0, 0, 0, 0.5, 0, 0);
1422         check(d.shiftedBy(attoSecond), 1966, 1, 1, 0, 0, attoSecond, 0.5, 0, 0);
1423         check(d.shiftedBy(one), 1966, 1, 1, 0, 0, one * (1 - factorPost), 1, 3, 0);
1424         check(d.shiftedBy(59).shiftedBy(one), 1966, 1, 1, 0, 0, sixty * (1 - factorPost), 1, 2, 0);
1425         check(d.shiftedBy(86399).shiftedBy(one), 1966, 1, 1, 23, 59, sixty - 86400 * factorPost, 1, 1, 0);
1426         check(d.shiftedBy(-zeroUlp), 1966, 1, 1, 0, 0, 0, 0.5, 0, 0);
1427         // actual leap is small ~1e-16, but during a leap rounding up to 60.0 is ok
1428         check(d.shiftedBy(-oneUlp), 1965, 12, 31, 23, 59, 60.0, 1, 0, 0);
1429         check(d.shiftedBy(-1).shiftedBy(zeroUlp), 1965, 12, 31, 23, 59, 59 + factorPre, 0.5, 0, 0);
1430         check(d.shiftedBy(-1).shiftedBy(-zeroUlp), 1965, 12, 31, 23, 59, 59 + factorPre, 0.5, 0, 0);
1431         check(d.shiftedBy(-1).shiftedBy(-oneUlp), 1965, 12, 31, 23, 59, 59 + factorPre, 0.5, 0, 0);
1432         check(d.shiftedBy(-1).shiftedBy(-sixtyUlp), 1965, 12, 31, 23, 59, 59 + (1 + sixtyUlp) * factorPre, 0.5, 1, 0);
1433         // since second ~= 0 there is significant cancellation
1434         check(d.shiftedBy(-60).shiftedBy(zeroUlp), 1965, 12, 31, 23, 59, 60 * factorPre, 0, 0, sixtyUlp);
1435         check(d.shiftedBy(-60).shiftedBy(oneUlp), 1965, 12, 31, 23, 59, (oneUlp - oneUlp * factorPre) + 60 * factorPre, 0.5, 0, sixtyUlp);
1436 
1437         // check first whole second leap
1438         FieldAbsoluteDate<T> d2 = new FieldAbsoluteDate<>(field, 1972, 7, 1, utc);
1439         check(d2, 1972, 7, 1, 0, 0, 0, 1, 0, 0);
1440         check(d2.shiftedBy(attoSecond), 1972, 7, 1, 0, 0, attoSecond, 0.5, 0, 0);
1441         check(d2.shiftedBy(one), 1972, 7, 1, 0, 0, one, 0.5, 0, 0);
1442         check(d2.shiftedBy(59).shiftedBy(one), 1972, 7, 1, 0, 0, sixty, 1, 1, 0);
1443         check(d2.shiftedBy(86399).shiftedBy(one), 1972, 7, 1, 23, 59, sixty, 1, 1, 0);
1444         check(d2.shiftedBy(-zeroUlp), 1972, 7, 1, 0, 0, 0, 0.5, 0, 0);
1445         check(d2.shiftedBy(-oneUlp), 1972, 6, 30, 23, 59, sixtyOne, 1, 1, 0);
1446         check(d2.shiftedBy(-1).shiftedBy(zeroUlp), 1972, 6, 30, 23, 59, 60.0, 0.5, 0, 0);
1447         check(d2.shiftedBy(-1).shiftedBy(-zeroUlp), 1972, 6, 30, 23, 59, 60.0, 0.5, 0, 0);
1448         check(d2.shiftedBy(-1).shiftedBy(-oneUlp), 1972, 6, 30, 23, 59, 60.0, 0.5, 0, 0);
1449         check(d2.shiftedBy(-1).shiftedBy(-sixtyUlp), 1972, 6, 30, 23, 59, sixty, 0.5, 0, 0);
1450         check(d2.shiftedBy(-61).shiftedBy(attoSecond), 1972, 6, 30, 23, 59, attoSecond, 0.5, 0, 0);
1451 
1452         // check first leap second, which was actually 1.422818 s.
1453         FieldAbsoluteDate<T> d3 = FieldAbsoluteDate.getArbitraryEpoch(field).shiftedBy(-1230724800);
1454         check(d3, 1960, 12, 31, 23, 59, 60, 0.5, 0, 0);
1455         FieldAbsoluteDate<T> d4 = new FieldAbsoluteDate<>(field, 1961, 1, 1, utc);
1456         check(d4, 1961, 1, 1, 0, 0, 0, 0.5, 0, 0);
1457         // FIXME something wrong because a date a smidgen before 1961-01-01 is not in a leap second
1458         //check(d4.shiftedBy(-oneUlp), 1960, 12, 31, 23, 59, 61.422818, 0.5, 0, 0);
1459 
1460         // check NaN, this is weird that NaNs have valid ymdhm, but not second.
1461         DateTimeComponents actual = date.shiftedBy(Double.NaN).getComponents(utc);
1462         DateComponents dc = actual.getDate();
1463         TimeComponents tc = actual.getTime();
1464         MatcherAssert.assertThat(dc.getYear(), CoreMatchers.is(2000));
1465         MatcherAssert.assertThat(dc.getMonth(), CoreMatchers.is(1));
1466         MatcherAssert.assertThat(dc.getDay(), CoreMatchers.is(1));
1467         MatcherAssert.assertThat(tc.getHour(), CoreMatchers.is(0));
1468         MatcherAssert.assertThat(tc.getMinute(), CoreMatchers.is(0));
1469         MatcherAssert.assertThat("second", tc.getSecond(), CoreMatchers.is(Double.NaN));
1470         MatcherAssert.assertThat(tc.getMinutesFromUTC(), CoreMatchers.is(0));
1471         final double difference = new FieldAbsoluteDate<>(field, actual, utc).durationFrom(date).getReal();
1472         MatcherAssert.assertThat(difference, CoreMatchers.is(Double.NaN));
1473     }
1474 
1475     private <T extends CalculusFieldElement<T>> void doTestNegativeOffsetConstructor(final Field<T> field) {
1476         FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field,
1477                                                             2019, 10, 11, 20, 40,
1478                                                             FastMath.scalb(6629298651489277.0, -55),
1479                                                             TimeScalesFactory.getTT());
1480         FieldAbsoluteDate<T> after = date.shiftedBy(Precision.EPSILON);
1481         Assertions.assertEquals(624098367L, date.toAbsoluteDate().getSeconds());
1482         Assertions.assertEquals(FastMath.nextAfter(1.0, Double.NEGATIVE_INFINITY),
1483                                 1.0e-18 * date.toAbsoluteDate().getAttoSeconds(), 2.4e-15);
1484         Assertions.assertEquals(Precision.EPSILON, after.durationFrom(date).getReal(), 1.0e-18);
1485     }
1486 
1487     private <T extends CalculusFieldElement<T>> void doTestNegativeOffsetShift(final Field<T> field) {
1488         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, 2019, 10, 11, 20, 40, 1.6667019180022178E-7,
1489                                                                  TimeScalesFactory.getTAI());
1490         T dt = field.getZero().newInstance(FastMath.scalb(6596520010750484.0, -39));
1491         FieldAbsoluteDate<T> shifted = reference.shiftedBy(dt);
1492         FieldAbsoluteDate<T> after = shifted.shiftedBy(Precision.EPSILON);
1493         Assertions.assertEquals(624110398L, shifted.toAbsoluteDate().getSeconds());
1494         Assertions.assertEquals((1.0 - 1.6922e-13) * 1.0e18, shifted.toAbsoluteDate().getAttoSeconds(), 1.0e-15);
1495         Assertions.assertEquals(Precision.EPSILON, after.durationFrom(shifted).getReal(), 1.0e-18);
1496     }
1497 
1498     private <T extends CalculusFieldElement<T>> void doTestDurationFromWithTimeUnit(final Field<T> field) {
1499         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, 2023, 1, 1, 12, 13, 59.12334567, utc);
1500         for (TimeUnit timeUnit : TimeUnit.values()) {
1501             Assertions.assertEquals(field.getZero(), reference.durationFrom(reference, timeUnit));
1502 
1503             long dayInTimeUnit = timeUnit.convert((long) Constants.JULIAN_DAY, TimeUnit.SECONDS);
1504             for (int i = 1; i <= 365; i++) {
1505                 FieldAbsoluteDate<T> minusDays = reference.shiftedBy(-i * Constants.JULIAN_DAY);
1506                 FieldAbsoluteDate<T> plusDays = reference.shiftedBy(i* Constants.JULIAN_DAY);
1507 
1508 
1509                 Assertions.assertEquals(field.getZero().add(i * dayInTimeUnit), reference.durationFrom(minusDays, timeUnit));
1510 
1511                 Assertions.assertEquals(field.getZero().add(-i * dayInTimeUnit), reference.durationFrom(plusDays, timeUnit));
1512 
1513                 AbsoluteDate minusDaysA = minusDays.toAbsoluteDate();
1514                 AbsoluteDate plusDaysA = plusDays.toAbsoluteDate();
1515 
1516                 Assertions.assertEquals(field.getZero().add(i * dayInTimeUnit), reference.durationFrom(minusDaysA, timeUnit));
1517 
1518                 Assertions.assertEquals(field.getZero().add(-i * dayInTimeUnit), reference.durationFrom(plusDaysA, timeUnit));
1519             }
1520 
1521             for (long ns = 1; ns <= 1_000_000_000; ns += 1_000_000) {
1522                 FieldAbsoluteDate<T> minus = reference.shiftedBy(-1e-9 * ns);
1523                 FieldAbsoluteDate<T> plus = reference.shiftedBy(1e-9 * ns);
1524 
1525                 double deltaInTimeUnit = ns / (double) timeUnit.toNanos(1);
1526                 Assertions.assertEquals(field.getZero().add(FastMath.round(deltaInTimeUnit)), reference.durationFrom(minus, timeUnit),
1527                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1528 
1529                 Assertions.assertEquals(field.getZero().add(FastMath.round(-deltaInTimeUnit)), reference.durationFrom(plus, timeUnit),
1530                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1531 
1532                 AbsoluteDate minusA = minus.toAbsoluteDate();
1533                 AbsoluteDate plusA = plus.toAbsoluteDate();
1534 
1535                 Assertions.assertEquals(field.getZero().add(FastMath.round(deltaInTimeUnit)), reference.durationFrom(minusA, timeUnit),
1536                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1537 
1538                 Assertions.assertEquals(field.getZero().add(FastMath.round(-deltaInTimeUnit)), reference.durationFrom(plusA, timeUnit),
1539                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1540             }
1541 
1542 
1543         }
1544     }
1545 
1546     public <T extends CalculusFieldElement<T>> void doTestConstructWithTimeUnitOffset(final Field<T> field) {
1547         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, 2023, 1, 1, 12, 13, 59.12334567, utc);
1548 
1549         for (TimeUnit timeUnit : TimeUnit.values()) {
1550             Assertions.assertEquals(0,
1551                 FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(reference, 0, timeUnit)).getReal()), 1e-10);
1552 
1553             long dayInTimeUnit = timeUnit.convert((long) Constants.JULIAN_DAY, TimeUnit.SECONDS);
1554             for (int i = 1; i <= 365; i++) {
1555                 FieldAbsoluteDate<T> minusDays = reference.shiftedBy(-i * Constants.JULIAN_DAY);
1556                 FieldAbsoluteDate<T> plusDays = reference.shiftedBy(i* Constants.JULIAN_DAY);
1557 
1558                 Assertions.assertEquals(0,
1559                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(minusDays, i * dayInTimeUnit, timeUnit)).getReal()),
1560                     1e-10,
1561                     String.format("TimeUnit: %s", timeUnit));
1562                 Assertions.assertEquals(0,
1563                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(plusDays, -i * dayInTimeUnit, timeUnit)).getReal()),
1564                     1e-10,
1565                     String.format("TimeUnit: %s", timeUnit));
1566 
1567                 Assertions.assertEquals(0,
1568                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(minusDays.toAbsoluteDate(), i * dayInTimeUnit, timeUnit, field)).getReal()),
1569                     1e-10,
1570                     String.format("TimeUnit: %s", timeUnit));
1571                 Assertions.assertEquals(0,
1572                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(plusDays.toAbsoluteDate(), -i * dayInTimeUnit, timeUnit, field)).getReal()),
1573                     1e-10,
1574                     String.format("TimeUnit: %s", timeUnit));
1575             }
1576 
1577             for (long ns = 1; ns <= 1_000_000_000; ns += 1_000_000) {
1578                 if (timeUnit.convert(1, TimeUnit.SECONDS) < 1) {
1579                     //Skip everything larger than one second
1580                     continue;
1581                 }
1582                 FieldAbsoluteDate<T> minus = reference.shiftedBy(-1e-9 * ns);
1583                 FieldAbsoluteDate<T> plus = reference.shiftedBy(1e-9 * ns);
1584 
1585                 double deltaInTimeUnit =  ns / (double) timeUnit.toNanos(1);
1586                 Assertions.assertEquals(0,
1587                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(minus, FastMath.round(deltaInTimeUnit), timeUnit)).getReal()),
1588                     1.0 / timeUnit.convert(1, TimeUnit.SECONDS),
1589                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1590                 Assertions.assertEquals(0,
1591                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(plus, FastMath.round(-deltaInTimeUnit), timeUnit)).getReal()),
1592                     1.0 / timeUnit.convert(1, TimeUnit.SECONDS),
1593                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1594 
1595                 Assertions.assertEquals(0,
1596                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(minus.toAbsoluteDate(), FastMath.round(deltaInTimeUnit), timeUnit, field)).getReal()),
1597                     1.0 / timeUnit.convert(1, TimeUnit.SECONDS),
1598                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1599                 Assertions.assertEquals(0,
1600                     FastMath.abs(reference.durationFrom(new FieldAbsoluteDate<>(plus.toAbsoluteDate(), FastMath.round(-deltaInTimeUnit), timeUnit, field)).getReal()),
1601                     1.0 / timeUnit.convert(1, TimeUnit.SECONDS),
1602                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1603             }
1604         }
1605     }
1606 
1607     public <T extends CalculusFieldElement<T>> void doTestShiftedByWithTimeUnit(final Field<T> field) {
1608         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, 2023, 1, 1, 12, 13, 59.12334567, utc);
1609 
1610         for (TimeUnit timeUnit : TimeUnit.values()) {
1611             Assertions.assertEquals(0,
1612                 FastMath.abs(reference.durationFrom(reference.shiftedBy(0, timeUnit)).getReal()), 1e-10);
1613 
1614             long dayInTimeUnit = timeUnit.convert((long) Constants.JULIAN_DAY, TimeUnit.SECONDS);
1615             for (int i = 1; i <= 365; i++) {
1616                 FieldAbsoluteDate<T> minusDays = reference.shiftedBy(-i * Constants.JULIAN_DAY);
1617                 FieldAbsoluteDate<T> plusDays = reference.shiftedBy(i* Constants.JULIAN_DAY);
1618 
1619                 Assertions.assertEquals(0,
1620                     FastMath.abs(reference.durationFrom(minusDays.shiftedBy( i * dayInTimeUnit, timeUnit)).getReal()),
1621                     1e-10,
1622                     String.format("TimeUnit: %s", timeUnit));
1623                 Assertions.assertEquals(0,
1624                     FastMath.abs(reference.durationFrom(plusDays.shiftedBy( -i * dayInTimeUnit, timeUnit)).getReal()),
1625                     1e-10,
1626                     String.format("TimeUnit: %s", timeUnit));
1627 
1628             }
1629 
1630             for (long ns = 1; ns <= 1_000_000_000; ns += 1_000_000) {
1631                 if (timeUnit.convert(1, TimeUnit.SECONDS) < 1) {
1632                     //Skip everything larger than one second
1633                     continue;
1634                 }
1635                 FieldAbsoluteDate<T> minus = reference.shiftedBy(-1e-9 * ns);
1636                 FieldAbsoluteDate<T> plus = reference.shiftedBy(1e-9 * ns);
1637 
1638                 double deltaInTimeUnit =  ns / (double) timeUnit.toNanos(1);
1639                 Assertions.assertEquals(0,
1640                     FastMath.abs(reference.durationFrom(minus.shiftedBy(FastMath.round(deltaInTimeUnit), timeUnit)).getReal()),
1641                     1.0 / timeUnit.convert(1, TimeUnit.SECONDS),
1642                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1643                 Assertions.assertEquals(0,
1644                     FastMath.abs(reference.durationFrom(plus.shiftedBy(FastMath.round(-deltaInTimeUnit), timeUnit)).getReal()),
1645                     1.0 / timeUnit.convert(1, TimeUnit.SECONDS),
1646                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
1647             }
1648         }
1649     }
1650 
1651     public <T extends CalculusFieldElement<T>> void doTestToStringWithoutUtcOffset(final Field<T> field) {
1652         // setup
1653         FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field,2009, 1, 1, utc);
1654         double one = FastMath.nextDown(1.0);
1655         double zeroUlp = FastMath.nextUp(0.0);
1656         double oneUlp = FastMath.ulp(1.0);
1657         //double sixty = FastMath.nextDown(60.0);
1658         double sixtyUlp = FastMath.ulp(60.0);
1659 
1660         // action
1661         // test midnight
1662         checkToStringNoOffset(date, "2009-01-01T00:00:00.000");
1663         checkToStringNoOffset(date.shiftedBy(1), "2009-01-01T00:00:01.000");
1664         // test digits and rounding
1665         checkToStringNoOffset(date.shiftedBy(12.3456789123456789), "2009-01-01T00:00:12.346");
1666         checkToStringNoOffset(date.shiftedBy(0.0123456789123456789), "2009-01-01T00:00:00.012");
1667         // test min and max values
1668         checkToStringNoOffset(date.shiftedBy(zeroUlp), "2009-01-01T00:00:00.000");
1669         // Orekit 10.1 rounds up
1670         checkToStringNoOffset(date.shiftedBy(59.0).shiftedBy(one), "2009-01-01T00:01:00.000");
1671         // Orekit 10.1 rounds up
1672         checkToStringNoOffset(date.shiftedBy(86399).shiftedBy(one), "2009-01-02T00:00:00.000");
1673         checkToStringNoOffset(date.shiftedBy(oneUlp), "2009-01-01T00:00:00.000");
1674         checkToStringNoOffset(date.shiftedBy(one), "2009-01-01T00:00:01.000");
1675         checkToStringNoOffset(date.shiftedBy(-zeroUlp), "2009-01-01T00:00:00.000");
1676         // test leap
1677         // Orekit 10.1 throw OIAE, 10.2 rounds up
1678         checkToStringNoOffset(date.shiftedBy(-oneUlp), "2009-01-01T00:00:00.000");
1679         // Orekit 10.1 rounds up
1680         checkToStringNoOffset(date.shiftedBy(-1).shiftedBy(one), "2009-01-01T00:00:00.000");
1681         checkToStringNoOffset(date.shiftedBy(-0.5), "2008-12-31T23:59:60.500");
1682         checkToStringNoOffset(date.shiftedBy(-1).shiftedBy(zeroUlp), "2008-12-31T23:59:60.000");
1683         checkToStringNoOffset(date.shiftedBy(-1), "2008-12-31T23:59:60.000");
1684         checkToStringNoOffset(date.shiftedBy(-1).shiftedBy(-zeroUlp), "2008-12-31T23:59:60.000");
1685         checkToStringNoOffset(date.shiftedBy(-1).shiftedBy(-oneUlp), "2008-12-31T23:59:60.000");
1686         checkToStringNoOffset(date.shiftedBy(-2), "2008-12-31T23:59:59.000");
1687         // Orekit 10.1 rounds up
1688         checkToStringNoOffset(date.shiftedBy(-1).shiftedBy(-sixtyUlp), "2008-12-31T23:59:60.000");
1689         checkToStringNoOffset(date.shiftedBy(-61).shiftedBy(zeroUlp), "2008-12-31T23:59:00.000");
1690         checkToStringNoOffset(date.shiftedBy(-61).shiftedBy(oneUlp), "2008-12-31T23:59:00.000");
1691     }
1692 
1693 
1694     private <T extends CalculusFieldElement<T>> void checkToStringNoOffset(final FieldAbsoluteDate<T> d, final String s) {
1695         MatcherAssert.assertThat(d.toStringWithoutUtcOffset(utc, 3), CoreMatchers.is(s));
1696         MatcherAssert.assertThat(
1697             d.getComponents(utc).toStringWithoutUtcOffset(utc.minuteDuration(d), 3),
1698             CoreMatchers.is(s));
1699     }
1700 
1701     private <T extends CalculusFieldElement<T>> void check(FieldAbsoluteDate<T> date,
1702                                                            int year, int month, int day, int hour, int minute, double second,
1703                                                            double roundTripUlps, final int secondUlps, final double absTol) {
1704         DateTimeComponents actual = date.getComponents(utc);
1705         DateComponents d = actual.getDate();
1706         TimeComponents t = actual.getTime();
1707         MatcherAssert.assertThat(d.getYear(), CoreMatchers.is(year));
1708         MatcherAssert.assertThat(d.getMonth(), CoreMatchers.is(month));
1709         MatcherAssert.assertThat(d.getDay(), CoreMatchers.is(day));
1710         MatcherAssert.assertThat(t.getHour(), CoreMatchers.is(hour));
1711         MatcherAssert.assertThat(t.getMinute(), CoreMatchers.is(minute));
1712         MatcherAssert.assertThat("second", t.getSecond(),
1713                 OrekitMatchers.numberCloseTo(second, absTol, secondUlps));
1714         MatcherAssert.assertThat(t.getMinutesFromUTC(), CoreMatchers.is(0));
1715         final double tol = FastMath.ulp(second) * roundTripUlps;
1716         final double difference = new FieldAbsoluteDate<>(date.getField(), actual, utc)
1717                 .durationFrom(date).getReal();
1718         MatcherAssert.assertThat(difference,
1719                 OrekitMatchers.closeTo(0, FastMath.max(absTol, tol)));
1720     }
1721 
1722     static class AnyFieldTimeStamped<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T> {
1723         AbsoluteDate date;
1724         Field<T> field;
1725 
1726         public AnyFieldTimeStamped(Field<T> field, AbsoluteDate date) {
1727             this.date = date;
1728             this.field = field;
1729         }
1730 
1731         @Override
1732         public FieldAbsoluteDate<T> getDate() {
1733             return new FieldAbsoluteDate<>(field, date);
1734         }
1735     }
1736 
1737     static class TestDates<T extends CalculusFieldElement<T>> {
1738         private final AbsoluteDate presentDate;
1739         private final AnyFieldTimeStamped<T> present;
1740         private final AnyFieldTimeStamped<T> past;
1741         private final AnyFieldTimeStamped<T> presentToo;
1742         private final AnyFieldTimeStamped<T> future;
1743 
1744         public TestDates(Field<T> field) {
1745             presentDate = new AbsoluteDate(new DateComponents(2000, 1, 1),
1746                                                 new TimeComponents(12, 0, 0),
1747                                                 TimeScalesFactory.getUTC());
1748             present    = new AnyFieldTimeStamped<>(field, presentDate);
1749             presentToo = new AnyFieldTimeStamped<>(field, presentDate.shiftedBy(0));
1750             past       = new AnyFieldTimeStamped<>(field, presentDate.shiftedBy(-1000));
1751             future     = new AnyFieldTimeStamped<>(field, presentDate.shiftedBy(1000));
1752         }
1753 
1754         public FieldAbsoluteDate<T> getPresentFieldAbsoluteDate() {
1755             return present.getDate();
1756         }
1757     }
1758 
1759     @Test
1760     public void doTestGetJulianDatesWithBinar64() {
1761         // GIVEN
1762         final Field<Binary64> field = Binary64Field.getInstance();
1763 
1764         // WHEN & THEN
1765         doTestGetJulianDates(field);
1766     }
1767 
1768     public <T extends CalculusFieldElement<T>> void doTestGetJulianDates(Field<T> field) {
1769         // GIVEN a reference date
1770         final T one = field.getOne();
1771         final TimeScale utc = TimeScalesFactory.getUTC();
1772 
1773         FieldAbsoluteDate<T> reference = new FieldAbsoluteDate<>(field, 2024, 7, 4, 13, 0, 0, utc);
1774         FieldAbsoluteDate<T> referenceFromJDMethod =
1775                 FieldAbsoluteDate.createJDDate(2460496, one.multiply(0.0416667 * Constants.JULIAN_DAY), utc);
1776         FieldAbsoluteDate<T> referenceFromMJDMethod =
1777                 FieldAbsoluteDate.createMJDDate(60495, one.multiply(0.54166670 * Constants.JULIAN_DAY), utc);
1778 
1779         // WHEN converting it to Julian Date or Modified Julian Date
1780         T mjdDateDefaultData = reference.getMJD();
1781         T jdDateDefaultData  = reference.getJD();
1782         T mjdDate = reference.getMJD(utc);
1783         T jdDate  = reference.getJD(utc);
1784 
1785         // THEN
1786         // source : Time/Date Converter - HEASARC - NASA
1787         Assertions.assertEquals(2460496.0416667, jdDateDefaultData.getReal(), 1.0e-6);
1788         Assertions.assertEquals(60495.54166670, mjdDateDefaultData.getReal(), 1.0e-6);
1789         Assertions.assertEquals(jdDate, jdDateDefaultData);
1790         Assertions.assertEquals(mjdDate, mjdDateDefaultData);
1791 
1792         // Assert that static method are correct when creating date from JD or MJD
1793         Assertions.assertTrue(reference.isCloseTo(referenceFromJDMethod, 1e-2));
1794         Assertions.assertTrue(reference.isCloseTo(referenceFromMJDMethod, 1e-2));
1795     }
1796 
1797     @Test
1798     void testGetJD() {
1799         // GIVEN
1800         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
1801         final FieldAbsoluteDate<UnivariateDerivative1> fieldDate = new FieldAbsoluteDate<>(UnivariateDerivative1Field.getInstance(),
1802                 date).shiftedBy(new UnivariateDerivative1(0., 1));
1803         // WHEN
1804         final UnivariateDerivative1 jdField = fieldDate.getJD();
1805         // THEN
1806         final double shift = 10.;
1807         final FieldAbsoluteDate<UnivariateDerivative1> shiftedDate = fieldDate.shiftedBy(shift);
1808         final double expectedJdDerivative = (shiftedDate.getJD().getReal() - jdField.getReal()) / shift;
1809         Assertions.assertEquals(expectedJdDerivative, jdField.getFirstDerivative(), 1e-10);
1810     }
1811 
1812     @Test
1813     void testGetMJD() {
1814         // GIVEN
1815         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
1816         final FieldAbsoluteDate<UnivariateDerivative1> fieldDate = new FieldAbsoluteDate<>(UnivariateDerivative1Field.getInstance(),
1817                 date).shiftedBy(new UnivariateDerivative1(0., 1));
1818         // WHEN
1819         final UnivariateDerivative1 mjdField = fieldDate.getMJD();
1820         // THEN
1821         final double shift = 10.;
1822         final FieldAbsoluteDate<UnivariateDerivative1> shiftedDate = fieldDate.shiftedBy(shift);
1823         final double expectedMjdDerivative = (shiftedDate.getMJD().getReal() - mjdField.getReal()) / shift;
1824         Assertions.assertEquals(expectedMjdDerivative, mjdField.getFirstDerivative(), 1e-10);
1825     }
1826 
1827     @Test
1828     void testMedian() {
1829         final AbsoluteDate date1 = new AbsoluteDate(2003, 6, 13, 14, 15,
1830                                                     new TimeOffset(53, TimeOffset.SECOND, 12, TimeOffset.ATTOSECOND),
1831                                                     TimeScalesFactory.getTT());
1832         final FieldAbsoluteDate<UnivariateDerivative1> fieldDate1 =
1833             new FieldAbsoluteDate<>(UnivariateDerivative1Field.getInstance(), date1).
1834             shiftedBy(new UnivariateDerivative1(0.0, 1.0));
1835         final AbsoluteDate date2 = new AbsoluteDate(2003, 6, 13, 14, 17,
1836                                                     new TimeOffset(25, TimeOffset.SECOND, 120, TimeOffset.ATTOSECOND),
1837                                                     TimeScalesFactory.getTT());
1838         final FieldAbsoluteDate<UnivariateDerivative1> fieldDate2 =
1839             new FieldAbsoluteDate<>(UnivariateDerivative1Field.getInstance(), date2).
1840             shiftedBy(new UnivariateDerivative1(0.0, 2.0));
1841         final AbsoluteDate dateM = new AbsoluteDate(2003, 6, 13, 14, 16,
1842                                                  new TimeOffset(39, TimeOffset.SECOND, 66, TimeOffset.ATTOSECOND),
1843                                                  TimeScalesFactory.getTT());
1844         final FieldAbsoluteDate<UnivariateDerivative1> fieldDateM =
1845             new FieldAbsoluteDate<>(UnivariateDerivative1Field.getInstance(), dateM).
1846             shiftedBy(new UnivariateDerivative1(0.0, 1.5));
1847         Assertions.assertEquals(fieldDateM, FieldAbsoluteDate.createMedian(fieldDate1, fieldDate2));
1848         Assertions.assertEquals(fieldDateM, FieldAbsoluteDate.createMedian(fieldDate2, fieldDate1));
1849     }
1850 
1851     @Test
1852     void testMedianInfinite() {
1853         final FieldAbsoluteDate<Binary64> future    = FieldAbsoluteDate.getFutureInfinity(Binary64Field.getInstance());
1854         final FieldAbsoluteDate<Binary64> past      = FieldAbsoluteDate.getPastInfinity(Binary64Field.getInstance());
1855         final FieldAbsoluteDate<Binary64> arbitrary = FieldAbsoluteDate.getArbitraryEpoch(Binary64Field.getInstance());
1856         Assertions.assertEquals(future, FieldAbsoluteDate.createMedian(future, arbitrary));
1857         Assertions.assertEquals(past,   FieldAbsoluteDate.createMedian(past,   arbitrary));
1858     }
1859 
1860     @Test
1861     void testToFUD1Field() {
1862         // GIVEN
1863         final Field<Binary64> field = Binary64Field.getInstance();
1864         final FieldAbsoluteDate<Binary64> date = FieldAbsoluteDate.getArbitraryEpoch(field);
1865         // WHEN
1866         final FieldAbsoluteDate<FieldUnivariateDerivative1<Binary64>> ud1Date = date.toFUD1Field();
1867         // THEN
1868         Assertions.assertEquals(date.toAbsoluteDate(), ud1Date.toAbsoluteDate());
1869         final FieldUnivariateDerivative1<Binary64> shift = ud1Date.durationFrom(date.toAbsoluteDate());
1870         Assertions.assertEquals(field.getOne(), shift.getFirstDerivative());
1871     }
1872 
1873     @Test
1874     void testToFUD2Field() {
1875         // GIVEN
1876         final Field<Binary64> field = Binary64Field.getInstance();
1877         final FieldAbsoluteDate<Binary64> date = FieldAbsoluteDate.getArbitraryEpoch(field);
1878         // WHEN
1879         final FieldAbsoluteDate<FieldUnivariateDerivative2<Binary64>> ud2Date = date.toFUD2Field();
1880         // THEN
1881         Assertions.assertEquals(date.toAbsoluteDate(), ud2Date.toAbsoluteDate());
1882         final FieldUnivariateDerivative2<Binary64> shift = ud2Date.durationFrom(date.toAbsoluteDate());
1883         Assertions.assertEquals(field.getOne(), shift.getFirstDerivative());
1884         Assertions.assertEquals(field.getZero(), shift.getSecondDerivative());
1885     }
1886 
1887     private <T extends CalculusFieldElement<T>> void doTestGetDayOfYear(final Field<T> field) {
1888         Assertions.assertEquals(0.501,
1889                                 new FieldAbsoluteDate<>(field,
1890                                                         new AbsoluteDate(2004,  1,  1,  0,  0,  0.001, utc)).
1891                                     getDayOfYear(utc).getReal(),
1892                                 1.0e-3);
1893         Assertions.assertEquals(1.000,
1894                                 new FieldAbsoluteDate<>(field,
1895                                                         new AbsoluteDate(2004,  1,  1, 12,  0,  0.000, utc)).
1896                                     getDayOfYear(utc).getReal(),
1897                                 1.0e-3);
1898         Assertions.assertEquals(366.0,
1899                                 new FieldAbsoluteDate<>(field,
1900                                                         new AbsoluteDate(2004, 12, 31, 12,  0,  0.000, utc)).
1901                                     getDayOfYear(utc).getReal(),
1902                                 1.0e-3);
1903         Assertions.assertEquals(366.499999988426,
1904                                 new FieldAbsoluteDate<>(field,
1905                                                         new AbsoluteDate(2004, 12, 31, 23, 59, 59.999, utc)).
1906                                     getDayOfYear(utc).getReal(),
1907                                 1.0e-12);
1908         Assertions.assertEquals(0.500000011574,
1909                                 new FieldAbsoluteDate<>(field,
1910                                                         new AbsoluteDate(2004, 12, 31, 23, 59, 59.999, utc).shiftedBy(0.002)).
1911                                     getDayOfYear(utc).getReal(),
1912                                 1.0e-12);
1913     }
1914 
1915 }