1   /* Copyright 2002-2022 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  
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectOutputStream;
25  import java.lang.reflect.Field;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.concurrent.ExecutorService;
30  import java.util.concurrent.Executors;
31  import java.util.concurrent.TimeUnit;
32  
33  import org.hamcrest.MatcherAssert;
34  import org.hipparchus.random.RandomGenerator;
35  import org.hipparchus.random.Well1024a;
36  import org.hipparchus.util.Decimal64Field;
37  import org.junit.After;
38  import org.junit.Assert;
39  import org.junit.Before;
40  import org.junit.Test;
41  import org.orekit.OrekitMatchers;
42  import org.orekit.Utils;
43  import org.orekit.errors.OrekitException;
44  import org.orekit.errors.OrekitMessages;
45  import org.orekit.utils.Constants;
46  
47  public class UTCScaleTest {
48  
49      @Test
50      public void testAfter() {
51          AbsoluteDate d1 = new AbsoluteDate(new DateComponents(2020, 12, 31),
52                                             new TimeComponents(23, 59, 59),
53                                             utc);
54          Assert.assertEquals("2020-12-31T23:59:59.000Z", d1.toString());
55      }
56  
57      @Test
58      public void testNoLeap() {
59          Assert.assertEquals("UTC", utc.toString());
60          AbsoluteDate d1 = new AbsoluteDate(new DateComponents(1999, 12, 31),
61                                             new TimeComponents(23, 59, 59),
62                                             utc);
63          AbsoluteDate d2 = new AbsoluteDate(new DateComponents(2000, 01, 01),
64                                             new TimeComponents(00, 00, 01),
65                                             utc);
66          Assert.assertEquals(2.0, d2.durationFrom(d1), 1.0e-10);
67      }
68  
69      @Test
70      public void testLeap2006() {
71          AbsoluteDate leapDate =
72              new AbsoluteDate(new DateComponents(2006, 01, 01), TimeComponents.H00, utc);
73          AbsoluteDate d1 = leapDate.shiftedBy(-1);
74          AbsoluteDate d2 = leapDate.shiftedBy(+1);
75          Assert.assertEquals(2.0, d2.durationFrom(d1), 1.0e-10);
76  
77          AbsoluteDate d3 = new AbsoluteDate(new DateComponents(2005, 12, 31),
78                                             new TimeComponents(23, 59, 59),
79                                             utc);
80          AbsoluteDate d4 = new AbsoluteDate(new DateComponents(2006, 01, 01),
81                                             new TimeComponents(00, 00, 01),
82                                             utc);
83          Assert.assertEquals(3.0, d4.durationFrom(d3), 1.0e-10);
84      }
85  
86      @Test
87      public void testDuringLeap() {
88          AbsoluteDate d = new AbsoluteDate(new DateComponents(1983, 06, 30),
89                                            new TimeComponents(23, 59, 59),
90                                            utc);
91          Assert.assertEquals("1983-06-30T23:58:59.000", d.shiftedBy(-60).toString(utc));
92          Assert.assertEquals(60, utc.minuteDuration(d.shiftedBy(-60)));
93          Assert.assertFalse(utc.insideLeap(d.shiftedBy(-60)));
94          Assert.assertEquals("1983-06-30T23:59:59.000", d.toString(utc));
95          Assert.assertEquals(61, utc.minuteDuration(d));
96          Assert.assertFalse(utc.insideLeap(d));
97          d = d.shiftedBy(0.251);
98          Assert.assertEquals("1983-06-30T23:59:59.251", d.toString(utc));
99          Assert.assertEquals(61, utc.minuteDuration(d));
100         Assert.assertFalse(utc.insideLeap(d));
101         d = d.shiftedBy(0.251);
102         Assert.assertEquals("1983-06-30T23:59:59.502", d.toString(utc));
103         Assert.assertEquals(61, utc.minuteDuration(d));
104         Assert.assertFalse(utc.insideLeap(d));
105         d = d.shiftedBy(0.251);
106         Assert.assertEquals("1983-06-30T23:59:59.753", d.toString(utc));
107         Assert.assertEquals(61, utc.minuteDuration(d));
108         Assert.assertFalse(utc.insideLeap(d));
109         d = d.shiftedBy( 0.251);
110         Assert.assertEquals("1983-06-30T23:59:60.004", d.toString(utc));
111         Assert.assertEquals(61, utc.minuteDuration(d));
112         Assert.assertTrue(utc.insideLeap(d));
113         d = d.shiftedBy(0.251);
114         Assert.assertEquals("1983-06-30T23:59:60.255", d.toString(utc));
115         Assert.assertEquals(61, utc.minuteDuration(d));
116         Assert.assertTrue(utc.insideLeap(d));
117         d = d.shiftedBy(0.251);
118         Assert.assertEquals("1983-06-30T23:59:60.506", d.toString(utc));
119         Assert.assertEquals(61, utc.minuteDuration(d));
120         d = d.shiftedBy(0.251);
121         Assert.assertEquals("1983-06-30T23:59:60.757", d.toString(utc));
122         Assert.assertEquals(61, utc.minuteDuration(d));
123         Assert.assertTrue(utc.insideLeap(d));
124         d = d.shiftedBy(0.251);
125         Assert.assertEquals("1983-07-01T00:00:00.008", d.toString(utc));
126         Assert.assertEquals(60, utc.minuteDuration(d));
127         Assert.assertFalse(utc.insideLeap(d));
128     }
129 
130     @Test
131     public void testWrapBeforeLeap() {
132         AbsoluteDate t = new AbsoluteDate("2015-06-30T23:59:59.999999", utc);
133         Assert.assertEquals("2015-06-30T23:59:60.000+00:00",
134                 t.getComponents(utc).toString(utc.minuteDuration(t)));
135     }
136 
137     @Test
138     public void testMinuteDuration() {
139         final AbsoluteDate t0 = new AbsoluteDate("1983-06-30T23:58:59.000", utc);
140         for (double dt = 0; dt < 63; dt += 0.3) {
141             if (dt < 1.0) {
142                 // before the minute of the leap
143                 Assert.assertEquals(60, utc.minuteDuration(t0.shiftedBy(dt)));
144             } else if (dt < 62.0) {
145                 // during the minute of the leap
146                 Assert.assertEquals(61, utc.minuteDuration(t0.shiftedBy(dt)));
147             } else {
148                 // after the minute of the leap
149                 Assert.assertEquals(60, utc.minuteDuration(t0.shiftedBy(dt)));
150             }
151         }
152     }
153 
154     /**
155      * Check the consistency of minute duration with the other data in each offset. Checks
156      * table hard coded in UTCScale.
157      *
158      * @throws ReflectiveOperationException on error.
159      */
160     @Test
161     public void testMinuteDurationConsistentWithLeap() throws ReflectiveOperationException {
162         // setup
163         // get the offsets array, makes this test easier to write
164         Field field = UTCScale.class.getDeclaredField("offsets");
165         field.setAccessible(true);
166         UTCTAIOffset[] offsets = (UTCTAIOffset[]) field.get(utc);
167 
168         // action
169         for (UTCTAIOffset offset : offsets) {
170             // average of start and end of leap second, definitely inside
171             final AbsoluteDate start = offset.getDate();
172             final AbsoluteDate end = offset.getValidityStart();
173             AbsoluteDate d = start.shiftedBy(end.durationFrom(start) / 2.0);
174             int excess = utc.minuteDuration(d) - 60;
175             double leap = offset.getLeap();
176             // verify
177             Assert.assertTrue(
178                     "at MJD" + offset.getMJD() + ": " + leap + " <= " + excess,
179                     leap <= excess);
180             Assert.assertTrue(leap > (excess - 1));
181             // before the leap starts but still in the same minute
182             d = start.shiftedBy(-30);
183             int newExcess = utc.minuteDuration(d) - 60;
184             double newLeap = offset.getLeap();
185             // verify
186             Assert.assertTrue(
187                     "at MJD" + offset.getMJD() + ": " + newLeap + " <= " + newExcess,
188                     newLeap <= newExcess);
189             Assert.assertTrue(leap > (excess - 1));
190             Assert.assertEquals(excess, newExcess);
191             Assert.assertEquals(leap, newLeap, 0.0);
192             MatcherAssert.assertThat("" + offset.getValidityStart(), leap,
193                     OrekitMatchers.numberCloseTo(end.durationFrom(start), 1e-16, 1));
194         }
195     }
196 
197     @Test
198     public void testSymmetry() {
199         TimeScale scale = TimeScalesFactory.getGPS();
200         for (double dt = -10000; dt < 10000; dt += 123.456789) {
201             AbsoluteDate date = AbsoluteDate.J2000_EPOCH.shiftedBy(dt * Constants.JULIAN_DAY);
202             double dt1 = scale.offsetFromTAI(date);
203             DateTimeComponents components = date.getComponents(scale);
204             double dt2 = scale.offsetToTAI(components.getDate(), components.getTime());
205             Assert.assertEquals( 0.0, dt1 + dt2, 1.0e-10);
206         }
207     }
208 
209     @Test
210     public void testOffsets() {
211 
212         // we arbitrary put UTC == TAI before 1961-01-01
213         checkOffset(1950,  1,  1,   0);
214 
215         // excerpt from UTC-TAI.history file:
216         //  1961  Jan.  1 - 1961  Aug.  1     1.422 818 0s + (MJD - 37 300) x 0.001 296s
217         //        Aug.  1 - 1962  Jan.  1     1.372 818 0s +        ""
218         //  1962  Jan.  1 - 1963  Nov.  1     1.845 858 0s + (MJD - 37 665) x 0.001 123 2s
219         //  1963  Nov.  1 - 1964  Jan.  1     1.945 858 0s +        ""
220         //  1964  Jan.  1 -       April 1     3.240 130 0s + (MJD - 38 761) x 0.001 296s
221         //        April 1 -       Sept. 1     3.340 130 0s +        ""
222         //        Sept. 1 - 1965  Jan.  1     3.440 130 0s +        ""
223         //  1965  Jan.  1 -       March 1     3.540 130 0s +        ""
224         //        March 1 -       Jul.  1     3.640 130 0s +        ""
225         //        Jul.  1 -       Sept. 1     3.740 130 0s +        ""
226         //        Sept. 1 - 1966  Jan.  1     3.840 130 0s +        ""
227         //  1966  Jan.  1 - 1968  Feb.  1     4.313 170 0s + (MJD - 39 126) x 0.002 592s
228         //  1968  Feb.  1 - 1972  Jan.  1     4.213 170 0s +        ""
229         checkOffset(1961,  1,  2,  -(1.422818 +   1 * 0.001296));  // MJD 37300 +   1
230         checkOffset(1961,  8,  2,  -(1.372818 + 213 * 0.001296));  // MJD 37300 + 213
231         checkOffset(1962,  1,  2,  -(1.845858 +   1 * 0.0011232)); // MJD 37665 +   1
232         checkOffset(1963, 11,  2,  -(1.945858 + 670 * 0.0011232)); // MJD 37665 + 670
233         checkOffset(1964,  1,  2,  -(3.240130 - 365 * 0.001296));  // MJD 38761 - 365
234         checkOffset(1964,  4,  2,  -(3.340130 - 274 * 0.001296));  // MJD 38761 - 274
235         checkOffset(1964,  9,  2,  -(3.440130 - 121 * 0.001296));  // MJD 38761 - 121
236         checkOffset(1965,  1,  2,  -(3.540130 +   1 * 0.001296));  // MJD 38761 +   1
237         checkOffset(1965,  3,  2,  -(3.640130 +  60 * 0.001296));  // MJD 38761 +  60
238         checkOffset(1965,  7,  2,  -(3.740130 + 182 * 0.001296));  // MJD 38761 + 182
239         checkOffset(1965,  9,  2,  -(3.840130 + 244 * 0.001296));  // MJD 38761 + 244
240         checkOffset(1966,  1,  2,  -(4.313170 +   1 * 0.002592));  // MJD 39126 +   1
241         checkOffset(1968,  2,  2,  -(4.213170 + 762 * 0.002592));  // MJD 39126 + 762
242 
243         // since 1972-01-01, offsets are only whole seconds
244         checkOffset(1972,  3,  5, -10);
245         checkOffset(1972,  7, 14, -11);
246         checkOffset(1979, 12, 31, -18);
247         checkOffset(1980,  1, 22, -19);
248         checkOffset(2006,  7,  7, -33);
249 
250     }
251 
252     private void checkOffset(int year, int month, int day, double offset) {
253         AbsoluteDate date = new AbsoluteDate(year, month, day, utc);
254         Assert.assertEquals(offset, utc.offsetFromTAI(date), 1.0e-10);
255     }
256 
257     @Test
258     public void testCreatingInLeapDateUTC() {
259         AbsoluteDate previous = null;
260         final double step = 0.0625;
261         for (double seconds = 59.0; seconds < 61.0; seconds += step) {
262             final AbsoluteDate date = new AbsoluteDate(2008, 12, 31, 23, 59, seconds, utc);
263             if (previous != null) {
264                 Assert.assertEquals(step, date.durationFrom(previous), 1.0e-12);
265             }
266             previous = date;
267         }
268         AbsoluteDate ad0 = new AbsoluteDate("2008-12-31T23:59:60", utc);
269         Assert.assertTrue(ad0.toString(utc).startsWith("2008-12-31T23:59:"));
270         AbsoluteDate ad1 = new AbsoluteDate("2008-12-31T23:59:59", utc).shiftedBy(1);
271         Assert.assertEquals(0, ad1.durationFrom(ad0), 1.0e-15);
272         Assert.assertEquals(1, new AbsoluteDate("2009-01-01T00:00:00", utc).durationFrom(ad0), 1.0e-15);
273         Assert.assertEquals(2, new AbsoluteDate("2009-01-01T00:00:01", utc).durationFrom(ad0), 1.0e-15);
274     }
275 
276     @Test
277     public void testCreatingInLeapDateLocalTime50HoursWest() {
278         // yes, I know, there are no time zones 50 hours West of UTC, this is a stress test
279         AbsoluteDate previous = null;
280         final double step = 0.0625;
281         for (double seconds = 59.0; seconds < 61.0; seconds += step) {
282             final AbsoluteDate date = new AbsoluteDate(new DateComponents(2008, 12, 29),
283                                                        new TimeComponents(21, 59, seconds, -50 * 60),
284                                                        utc);
285             if (previous != null) {
286                 Assert.assertEquals(step, date.durationFrom(previous), 1.0e-12);
287             }
288             previous = date;
289         }
290         AbsoluteDate ad0 = new AbsoluteDate("2008-12-29T21:59:60-50:00", utc);
291         Assert.assertTrue(ad0.toString(utc).startsWith("2008-12-31T23:59:"));
292         AbsoluteDate ad1 = new AbsoluteDate("2008-12-29T21:59:59-50:00", utc).shiftedBy(1);
293         Assert.assertEquals(0, ad1.durationFrom(ad0), 1.0e-15);
294         Assert.assertEquals(1, new AbsoluteDate("2008-12-29T22:00:00-50:00", utc).durationFrom(ad0), 1.0e-15);
295         Assert.assertEquals(2, new AbsoluteDate("2008-12-29T22:00:01-50:00", utc).durationFrom(ad0), 1.0e-15);
296     }
297 
298     @Test
299     public void testCreatingInLeapDateLocalTime50HoursEast() {
300         // yes, I know, there are no time zones 50 hours East of UTC, this is a stress test
301         AbsoluteDate previous = null;
302         final double step = 0.0625;
303         for (double seconds = 59.0; seconds < 61.0; seconds += step) {
304             final AbsoluteDate date = new AbsoluteDate(new DateComponents(2009, 1, 3),
305                                                        new TimeComponents(1, 59, seconds, +50 * 60),
306                                                        utc);
307             if (previous != null) {
308                 Assert.assertEquals(step, date.durationFrom(previous), 1.0e-12);
309             }
310             previous = date;
311         }
312         AbsoluteDate ad0 = new AbsoluteDate("2009-01-03T01:59:60+50:00", utc);
313         Assert.assertTrue(ad0.toString(utc).startsWith("2008-12-31T23:59:"));
314         AbsoluteDate ad1 = new AbsoluteDate("2009-01-03T01:59:59+50:00", utc).shiftedBy(1);
315         Assert.assertEquals(0, ad1.durationFrom(ad0), 1.0e-15);
316         Assert.assertEquals(1, new AbsoluteDate("2009-01-03T02:00:00+50:00", utc).durationFrom(ad0), 1.0e-15);
317         Assert.assertEquals(2, new AbsoluteDate("2009-01-03T02:00:01+50:00", utc).durationFrom(ad0), 1.0e-15);
318     }
319 
320     @Test
321     public void testDisplayDuringLeap() {
322         AbsoluteDate t0 = utc.getLastKnownLeapSecond().shiftedBy(-1.0);
323         for (double dt = 0.0; dt < 3.0; dt += 0.375) {
324             AbsoluteDate t = t0.shiftedBy(dt);
325             double seconds = t.getComponents(utc).getTime().getSecond();
326             if (dt < 2.0) {
327                 Assert.assertEquals(dt + 59.0, seconds, 1.0e-12);
328             } else {
329                 Assert.assertEquals(dt - 2.0, seconds, 1.0e-12);
330             }
331         }
332     }
333 
334     @Test
335     public void testMultithreading() {
336 
337         // generate reference offsets using a single thread
338         RandomGenerator random = new Well1024a(6392073424l);
339         List<AbsoluteDate> datesList = new ArrayList<AbsoluteDate>();
340         List<Double> offsetsList = new ArrayList<Double>();
341         AbsoluteDate reference = utc.getFirstKnownLeapSecond().shiftedBy(-Constants.JULIAN_YEAR);
342         double testRange = utc.getLastKnownLeapSecond().durationFrom(reference) + Constants.JULIAN_YEAR;
343         for (int i = 0; i < 10000; ++i) {
344             AbsoluteDate randomDate = reference.shiftedBy(random.nextDouble() * testRange);
345             datesList.add(randomDate);
346             offsetsList.add(utc.offsetFromTAI(randomDate));
347         }
348 
349         // check the offsets in multi-threaded mode
350         ExecutorService executorService = Executors.newFixedThreadPool(100);
351 
352         for (int i = 0; i < datesList.size(); ++i) {
353             final AbsoluteDate date = datesList.get(i);
354             final double offset = offsetsList.get(i);
355             executorService.execute(new Runnable() {
356                 public void run() {
357                     Assert.assertEquals(offset, utc.offsetFromTAI(date), 1.0e-12);
358                 }
359             });
360         }
361 
362         try {
363             executorService.shutdown();
364             executorService.awaitTermination(3, TimeUnit.SECONDS);
365         } catch (InterruptedException ie) {
366             Assert.fail(ie.getLocalizedMessage());
367         }
368 
369     }
370 
371     @Test
372     public void testIssue89() {
373         AbsoluteDate firstDayLastLeap = utc.getLastKnownLeapSecond().shiftedBy(10.0);
374         AbsoluteDate rebuilt = new AbsoluteDate(firstDayLastLeap.toString(utc), utc);
375         Assert.assertEquals(0.0, rebuilt.durationFrom(firstDayLastLeap), 1.0e-12);
376     }
377 
378     @Test
379     public void testOffsetToTAIBeforeFirstLeapSecond() {
380         TimeScale scale = TimeScalesFactory.getUTC();
381         // time before first leap second
382         DateComponents dateComponents = new DateComponents(1950, 1, 1);
383         double actual = scale.offsetToTAI(dateComponents, TimeComponents.H00);
384         Assert.assertEquals(0.0, actual, 1.0e-10);
385     }
386 
387     @Test
388     public void testEmptyOffsets() {
389         Utils.setDataRoot("no-data");
390 
391         TimeScalesFactory.addUTCTAIOffsetsLoader(new UTCTAIOffsetsLoader() {
392             public List<OffsetModel> loadOffsets() {
393                 return Collections.emptyList();
394             }
395         });
396 
397         try {
398             TimeScalesFactory.getUTC();
399             Assert.fail("an exception should have been thrown");
400         } catch (OrekitException oe) {
401             Assert.assertEquals(OrekitMessages.NO_IERS_UTC_TAI_HISTORY_DATA_LOADED, oe.getSpecifier());
402         }
403 
404     }
405 
406     @Test
407     public void testInfinityRegularDate() {
408         TimeScale scale = TimeScalesFactory.getUTC();
409         Assert.assertEquals(-36.0,
410                             scale.offsetFromTAI(AbsoluteDate.FUTURE_INFINITY),
411                             1.0e-15);
412         Assert.assertEquals(0.0,
413                             scale.offsetFromTAI(AbsoluteDate.PAST_INFINITY),
414                             1.0e-15);
415     }
416 
417     @Test
418     public void testInfinityFieldDate() {
419         TimeScale scale = TimeScalesFactory.getUTC();
420         Assert.assertEquals(-36.0,
421                             scale.offsetFromTAI(FieldAbsoluteDate.getFutureInfinity(Decimal64Field.getInstance())).getReal(),
422                             1.0e-15);
423         Assert.assertEquals(0.0,
424                             scale.offsetFromTAI(FieldAbsoluteDate.getPastInfinity(Decimal64Field.getInstance())).getReal(),
425                             1.0e-15);
426     }
427 
428     @Test
429     public void testSerialization() throws IOException, ClassNotFoundException {
430         UTCScale utc = TimeScalesFactory.getUTC();
431 
432         ByteArrayOutputStream bos = new ByteArrayOutputStream();
433         ObjectOutputStream    oos = new ObjectOutputStream(bos);
434         oos.writeObject(utc);
435 
436         Assert.assertTrue(bos.size() > 50);
437         Assert.assertTrue(bos.size() < 100);
438 
439         ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
440         ObjectInputStream     ois = new ObjectInputStream(bis);
441         UTCScale deserialized  = (UTCScale) ois.readObject();
442         Assert.assertTrue(utc == deserialized);
443 
444     }
445 
446     @Test
447     public void testFirstAndLast() {
448         // action
449         AbsoluteDate first = utc.getFirstKnownLeapSecond();
450         AbsoluteDate last = utc.getLastKnownLeapSecond();
451 
452         // verify
453         //AbsoluteDate d = new AbsoluteDate(1961, 1, 1, utc);
454         Assert.assertEquals(new AbsoluteDate(2015, 6, 30, 23, 59, 60, utc), last);
455         Assert.assertEquals(new AbsoluteDate(1960, 12, 31, 23, 59, 60, utc), first);
456     }
457 
458     @Test
459     public void testGetUTCTAIOffsets() {
460         final List<UTCTAIOffset> offsets = utc.getUTCTAIOffsets();
461         Assert.assertEquals(40, offsets.size());
462         final UTCTAIOffset firstOffset = offsets.get(0);
463         final UTCTAIOffset lastOffset = offsets.get(offsets.size() - 1);
464         Assert.assertEquals(37300, firstOffset.getMJD()); // 1961-01-01
465         Assert.assertEquals(57204, lastOffset.getMJD()); // 2015-07-01
466     }
467 
468     @Before
469     public void setUp() {
470         Utils.setDataRoot("regular-data");
471         utc = TimeScalesFactory.getUTC();
472     }
473 
474     @After
475     public void tearDown() {
476         utc = null;
477     }
478 
479     private UTCScale utc;
480 
481 }