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 org.hamcrest.CoreMatchers;
20  import org.hamcrest.MatcherAssert;
21  import org.hipparchus.util.FastMath;
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.Test;
24  import org.orekit.errors.OrekitIllegalArgumentException;
25  import org.orekit.errors.OrekitMessages;
26  
27  
28  public class TimeComponentsTest {
29  
30      @Test
31      public void testOutOfRangeA() throws IllegalArgumentException {
32          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(-1, 10, 10));
33      }
34  
35      @Test
36      public void testOutOfRangeB() throws IllegalArgumentException {
37          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(24, 10, 10));
38      }
39  
40      @Test
41      public void testOutOfRangeC() throws IllegalArgumentException {
42          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(10, -1, 10));
43      }
44  
45      @Test
46      public void testOutOfRangeD() throws IllegalArgumentException {
47          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(10, 60, 10));
48      }
49  
50      @Test
51      public void testOutOfRangeE() throws IllegalArgumentException {
52          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(10, 10, -1));
53      }
54  
55      @Test
56      public void testOutOfRangeF() throws IllegalArgumentException {
57          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(10, 10, 62));
58      }
59  
60      @Test
61      public void testOutOfRangeG() throws IllegalArgumentException {
62          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(86399, 4.5));
63      }
64  
65      @Test
66      public void testOutOfRangeH() throws IllegalArgumentException {
67          Assertions.assertThrows(IllegalArgumentException.class, () -> new TimeComponents(0, -1.0));
68      }
69  
70      @Test
71      public void testInRange() {
72  
73          TimeComponents time = new TimeComponents(10, 10, 10);
74          Assertions.assertEquals(10,   time.getHour());
75          Assertions.assertEquals(10,   time.getMinute());
76          Assertions.assertEquals(10.0, time.getSecond(), 1.0e-10);
77  
78          time = new TimeComponents(0.0);
79          Assertions.assertEquals(0.0, time.getSecondsInUTCDay(), 1.0e-10);
80  
81          time = new TimeComponents(10, 10, 60.999);
82          Assertions.assertEquals(10,   time.getHour());
83          Assertions.assertEquals(10,   time.getMinute());
84          Assertions.assertEquals(60.999, time.getSecond(), 1.0e-10);
85  
86          time = new TimeComponents(43200.0);
87          Assertions.assertEquals(43200.0, time.getSecondsInUTCDay(), 1.0e-10);
88  
89          time = new TimeComponents(86399.999);
90          Assertions.assertEquals(86399.999, time.getSecondsInUTCDay(), 1.0e-10);
91  
92          time = new TimeComponents(2, 30, 0, 180);
93          Assertions.assertEquals(+9000.0, time.getSecondsInLocalDay(), 1.0e-5);
94          Assertions.assertEquals(-1800.0, time.getSecondsInUTCDay(),   1.0e-5);
95      }
96  
97      @Test
98      public void testValues() {
99          Assertions.assertEquals(    0.0, new TimeComponents( 0, 0, 0).getSecondsInLocalDay(), 1.0e-10);
100         Assertions.assertEquals(21600.0, new TimeComponents( 6, 0, 0).getSecondsInLocalDay(), 1.0e-10);
101         Assertions.assertEquals(43200.0, new TimeComponents(12, 0, 0).getSecondsInLocalDay(), 1.0e-10);
102         Assertions.assertEquals(64800.0, new TimeComponents(18, 0, 0).getSecondsInLocalDay(), 1.0e-10);
103         Assertions.assertEquals(86399.9, new TimeComponents(23, 59, 59.9).getSecondsInLocalDay(), 1.0e-10);
104     }
105 
106     @Test
107     public void testString() {
108         Assertions.assertEquals("00:00:00.000+00:00", new TimeComponents(0).toString());
109         Assertions.assertEquals("06:00:00.000+00:00", new TimeComponents(21600).toString());
110         Assertions.assertEquals("12:00:00.000+00:00", new TimeComponents(43200).toString());
111         Assertions.assertEquals("18:00:00.000+00:00", new TimeComponents(64800).toString());
112         Assertions.assertEquals("23:59:59.899999999994179232+00:00", new TimeComponents(86399.9).toString());
113         Assertions.assertEquals("00:00:00.000+10:00", new TimeComponents( 0,  0,  0,    600).toString());
114         Assertions.assertEquals("06:00:00.000+10:00", new TimeComponents( 6,  0,  0,    600).toString());
115         Assertions.assertEquals("12:00:00.000-04:30", new TimeComponents(12,  0,  0,   -270).toString());
116         Assertions.assertEquals("18:00:00.000-04:30", new TimeComponents(18,  0,  0,   -270).toString());
117         Assertions.assertEquals("23:59:59.900-04:30", new TimeComponents(23, 59,
118                                                                          new TimeOffset(59, TimeOffset.SECOND, 900, TimeOffset.MILLISECOND),
119                                                                          -270).toString());
120         // test leap seconds
121         Assertions.assertEquals("23:59:60.500+00:00", new TimeComponents(new TimeOffset(86399).add(new TimeOffset(0.5)),
122                                                                          TimeOffset.SECOND, 61).toString());
123         // leap second on 1961 is between 1 and 2 seconds in duration
124         Assertions.assertEquals("23:59:61.322817980157729984+00:00", new TimeComponents(new TimeOffset(86399).add(new TimeOffset(0.32281798015773)),
125                                                                                         TimeOffset.SECOND.multiply(2), 62).toString());
126         // test rounding
127         Assertions.assertEquals("23:59:59.999999999985448085+00:00", new TimeComponents(86399.99999999999).toString());
128         Assertions.assertEquals("23:59:59.999999999999999889+00:00", new TimeComponents(new TimeOffset(86399).add(new TimeOffset(FastMath.nextDown(1.0))),
129                                                                                         TimeOffset.ZERO, 60).toString());
130     }
131 
132     @Test
133     public void testParse() {
134         Assertions.assertEquals(86399.9, TimeComponents.parseTime("235959.900").getSecondsInLocalDay(), 1.0e-10);
135         Assertions.assertEquals(86399.9, TimeComponents.parseTime("23:59:59.900").getSecondsInLocalDay(), 1.0e-10);
136         Assertions.assertEquals(86399.9, TimeComponents.parseTime("23:59:59,900").getSecondsInLocalDay(), 1.0e-10);
137         Assertions.assertEquals(86399.9, TimeComponents.parseTime("235959.900Z").getSecondsInLocalDay(), 1.0e-10);
138         Assertions.assertEquals(86399.9, TimeComponents.parseTime("23:59:59.900Z").getSecondsInLocalDay(), 1.0e-10);
139         Assertions.assertEquals(86399.9, TimeComponents.parseTime("235959.900+10").getSecondsInLocalDay(), 1.0e-10);
140         Assertions.assertEquals(86399.9, TimeComponents.parseTime("23:59:59.900+00").getSecondsInLocalDay(), 1.0e-10);
141         Assertions.assertEquals(86399.9, TimeComponents.parseTime("235959.900-00:12").getSecondsInLocalDay(), 1.0e-10);
142         Assertions.assertEquals(86399.9, TimeComponents.parseTime("23:59:59.900+00:00").getSecondsInLocalDay(), 1.0e-10);
143         Assertions.assertEquals(86340.0, TimeComponents.parseTime("23:59").getSecondsInLocalDay(), 1.0e-10);
144     }
145 
146     @Test
147     public void testBadFormat() {
148         Assertions.assertThrows(IllegalArgumentException.class, () -> TimeComponents.parseTime("23h59m59s"));
149     }
150 
151     @Test
152     public void testLocalTime() {
153         Assertions.assertEquals(60, TimeComponents.parseTime("23:59:59+01:00").getMinutesFromUTC());
154     }
155 
156     @SuppressWarnings("unlikely-arg-type")
157     @Test
158     public void testComparisons() {
159         TimeComponents[] times = {
160                  new TimeComponents( 0,  0,  0.0),
161                  new TimeComponents( 0,  0,  1.0e-15),
162                  new TimeComponents( 0, 12,  3.0),
163                  new TimeComponents(15,  9,  3.0),
164                  new TimeComponents(23, 59, 59.0),
165                  new TimeComponents(23, 59, 60.0 - 1.0e-12)
166         };
167         for (int i = 0; i < times.length; ++i) {
168             for (int j = 0; j < times.length; ++j) {
169                 if (times[i].compareTo(times[j]) < 0) {
170                     Assertions.assertTrue(times[j].compareTo(times[i]) > 0);
171                     Assertions.assertNotEquals(times[i], times[j]);
172                     Assertions.assertNotEquals(times[j], times[i]);
173                     Assertions.assertTrue(times[i].hashCode() != times[j].hashCode());
174                     Assertions.assertTrue(i < j);
175                 } else if (times[i].compareTo(times[j]) > 0) {
176                     Assertions.assertTrue(times[j].compareTo(times[i]) < 0);
177                     Assertions.assertNotEquals(times[i], times[j]);
178                     Assertions.assertNotEquals(times[j], times[i]);
179                     Assertions.assertTrue(times[i].hashCode() != times[j].hashCode());
180                     Assertions.assertTrue(i > j);
181                 } else {
182                     Assertions.assertEquals(0, times[j].compareTo(times[i]));
183                     Assertions.assertEquals(times[i], times[j]);
184                     Assertions.assertEquals(times[j], times[i]);
185                     Assertions.assertEquals(times[i].hashCode(), times[j].hashCode());
186                     Assertions.assertEquals(i, j);
187                 }
188             }
189         }
190         Assertions.assertNotEquals(times[0], this);
191     }
192 
193     @Test
194     public void testFromSeconds() {
195         // setup
196         double zeroUlp  = FastMath.nextUp(0.0);
197         double sixtyUlp = FastMath.ulp(60.0);
198         double one      =  1.0 - sixtyUlp;
199         double sixty    = 60.0 - sixtyUlp;
200         double sixtyOne = 61.0 - sixtyUlp;
201 
202         // action + verify
203         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(0).add(new TimeOffset(0)), TimeOffset.ZERO, 60).getSecond(),
204                                  CoreMatchers.is(0.0));
205         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(0).add(new TimeOffset(zeroUlp)), TimeOffset.ZERO, 60).getSecond(),
206                                  CoreMatchers.is(0.0));
207         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(86399).add(new TimeOffset(one)), TimeOffset.ZERO, 60).getSecond(),
208                                  CoreMatchers.is(sixty));
209         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(86399).add(new TimeOffset(one)), TimeOffset.SECOND, 61).getSecond(),
210                                  CoreMatchers.is(sixtyOne));
211         // I don't like this NaN behavior, but it matches the 10.1 implementation and
212         // GLONASSAnalyticalPropagatorTest relied on it.
213         // It seems more logical to throw an out of range exception in this case.
214         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(86399).add(new TimeOffset(Double.NaN)), TimeOffset.ZERO, 60).getSecond(),
215                                  CoreMatchers.is(Double.NaN));
216         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(86399).add(new TimeOffset(Double.NaN)), TimeOffset.ZERO, 60).getMinute(),
217                                  CoreMatchers.is(0));
218         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(86399).add(new TimeOffset(Double.NaN)), TimeOffset.SECOND, 61).getSecond(),
219                                  CoreMatchers.is(Double.NaN));
220         MatcherAssert.assertThat(new TimeComponents(new TimeOffset(86399).add(new TimeOffset(Double.NaN)), TimeOffset.SECOND, 61).getMinute(),
221                                  CoreMatchers.is(0));
222 
223         // check errors
224         try {
225             new TimeComponents(new TimeOffset(FastMath.nextDown(0)), TimeOffset.ZERO, 60);
226             Assertions.fail("Expected Exception");
227         } catch (OrekitIllegalArgumentException e) {
228             MatcherAssert.assertThat(e.getSpecifier(),
229                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
230         }
231         try {
232             new TimeComponents(new TimeOffset(86399).add(new TimeOffset(1)), TimeOffset.ZERO, 60);
233             Assertions.fail("Expected Exception");
234         } catch (OrekitIllegalArgumentException e) {
235             MatcherAssert.assertThat(e.getSpecifier(),
236                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
237         }
238         try {
239             new TimeComponents(new TimeOffset(86399).add(new TimeOffset(1)), TimeOffset.SECOND, 61);
240             Assertions.fail("Expected Exception");
241         } catch (OrekitIllegalArgumentException e) {
242             MatcherAssert.assertThat(e.getSpecifier(),
243                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
244         }
245         try {
246             new TimeComponents(new TimeOffset(0).add(new TimeOffset(0)), TimeOffset.SECOND.negate(), 59);
247             Assertions.fail("Expected Exception");
248         } catch (OrekitIllegalArgumentException e) {
249             MatcherAssert.assertThat(e.getSpecifier(),
250                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
251         }
252         try {
253             new TimeComponents(new TimeOffset(0).add(new TimeOffset(0)), TimeOffset.SECOND, 59);
254             Assertions.fail("Expected Exception");
255         } catch (OrekitIllegalArgumentException e) {
256             MatcherAssert.assertThat(e.getSpecifier(),
257                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
258         }
259     }
260 
261     @Test
262     public void testTimeComponentsDouble658() {
263         // setup
264         double zeroUlp = FastMath.nextUp(0.0);
265         double dayUlp = FastMath.ulp(86400.0);
266 
267         // action + verify
268         check(new TimeComponents(0.0), 0, 0, 0);
269         check(new TimeComponents(zeroUlp), 0, 0, 0);
270         check(new TimeComponents(86399.5), 23, 59, 59.5);
271         check(new TimeComponents(FastMath.nextDown(86400.0)), 23, 59, 60 - dayUlp);
272         check(new TimeComponents(86400), 23, 59, 60);
273         check(new TimeComponents(FastMath.nextUp(86400.0)), 23, 59, 60 + dayUlp);
274         check(new TimeComponents(86400.5), 23, 59, 60.5);
275         check(new TimeComponents(FastMath.nextDown(86401.0)), 23, 59, 61 - dayUlp);
276         try {
277             new TimeComponents(86401);
278             Assertions.fail("Expected Exception");
279         } catch (OrekitIllegalArgumentException e) {
280             MatcherAssert.assertThat(e.getSpecifier(),
281                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
282             MatcherAssert.assertThat(e.getParts()[0], CoreMatchers.is(86401.0));
283         }
284         try {
285             new TimeComponents(-zeroUlp);
286             Assertions.fail("Expected Exception");
287         } catch (OrekitIllegalArgumentException e) {
288             MatcherAssert.assertThat(e.getSpecifier(),
289                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
290             MatcherAssert.assertThat(e.getParts()[0], CoreMatchers.is(0.0));
291         }
292     }
293 
294     @Test
295     public void testTimeComponentsIntDouble658() {
296         // setup
297         double zeroUlp  = FastMath.nextUp(0.0);
298         double sixtyUlp = FastMath.ulp(60.0);
299         double one      =  1.0 - sixtyUlp;
300         double sixty    = 60.0 - sixtyUlp;
301         double sixtyOne = 61.0 - sixtyUlp;
302 
303         // action + verify
304         check(new TimeComponents(0, 0.0), 0, 0, 0);
305         check(new TimeComponents(0, zeroUlp), 0, 0, 0);
306         check(new TimeComponents(86399, 0.5), 23, 59, 59.5);
307         check(new TimeComponents(86399, one), 23, 59, sixty);
308         check(new TimeComponents(86400, 0.0), 23, 59, 60);
309         check(new TimeComponents(86400, sixtyUlp), 23, 59, 60 + sixtyUlp);
310         check(new TimeComponents(86400, 0.5), 23, 59, 60.5);
311         check(new TimeComponents(86400, one), 23, 59, sixtyOne);
312         try {
313             new TimeComponents(86401, 0.0);
314             Assertions.fail("Expected Exception");
315         } catch (OrekitIllegalArgumentException e) {
316             MatcherAssert.assertThat(e.getSpecifier(),
317                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
318             MatcherAssert.assertThat(e.getParts()[0], CoreMatchers.is(86400.0));
319         }
320         try {
321             new TimeComponents(86400, 1.0);
322             Assertions.fail("Expected Exception");
323         } catch (OrekitIllegalArgumentException e) {
324             MatcherAssert.assertThat(e.getSpecifier(),
325                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
326             MatcherAssert.assertThat(e.getParts()[0], CoreMatchers.is(86400.0));
327         }
328         try {
329             new TimeComponents(0, -1.0e-18);
330             Assertions.fail("Expected Exception");
331         } catch (OrekitIllegalArgumentException e) {
332             MatcherAssert.assertThat(e.getSpecifier(),
333                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
334             MatcherAssert.assertThat(e.getParts()[0], CoreMatchers.is(-1.0e-18));
335         }
336         try {
337             new TimeComponents(-1, 0.0);
338             Assertions.fail("Expected Exception");
339         } catch (OrekitIllegalArgumentException e) {
340             MatcherAssert.assertThat(e.getSpecifier(),
341                     CoreMatchers.is(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER_DETAIL));
342             MatcherAssert.assertThat(e.getParts()[0], CoreMatchers.is(-1.0));
343         }
344     }
345 
346     private void check(final TimeComponents tc, int hour, int minute, double second) {
347         MatcherAssert.assertThat(tc.getHour(), CoreMatchers.is(hour));
348         MatcherAssert.assertThat(tc.getMinute(), CoreMatchers.is(minute));
349         MatcherAssert.assertThat(tc.getSecond(), CoreMatchers.is(second));
350     }
351 
352 }