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