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.util.concurrent.TimeUnit;
20  import org.hamcrest.CoreMatchers;
21  import org.hamcrest.MatcherAssert;
22  import org.hipparchus.util.FastMath;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.Test;
25  import org.orekit.utils.Constants;
26  
27  
28  public class DateTimeComponentsTest {
29  
30      @SuppressWarnings("unlikely-arg-type")
31      @Test
32      public void testComparisons() {
33          DateTimeComponents[] dates = {
34                  new DateTimeComponents(2003,  1,  1, 7, 15, 33),
35                  new DateTimeComponents(2003,  1,  1, 7, 15, 34),
36                  new DateTimeComponents(2003,  1,  1, 7, 16, 34),
37                  new DateTimeComponents(2003,  1,  1, 8, 16, 34),
38                  new DateTimeComponents(2003,  1,  2, 8, 16, 34),
39                  new DateTimeComponents(2003,  2,  2, 8, 16, 34),
40                  new DateTimeComponents(2004,  2,  2, 8, 16, 34)
41          };
42          for (int i = 0; i < dates.length; ++i) {
43              for (int j = 0; j < dates.length; ++j) {
44                  Assertions.assertEquals(i  < j, dates[i].compareTo(dates[j])  < 0);
45                  Assertions.assertEquals(i  > j, dates[j].compareTo(dates[i])  < 0);
46                  Assertions.assertEquals(i == j, dates[i].compareTo(dates[j]) == 0);
47                  Assertions.assertEquals(i  > j, dates[i].compareTo(dates[j])  > 0);
48                  Assertions.assertEquals(i  < j, dates[j].compareTo(dates[i])  > 0);
49              }
50          }
51          Assertions.assertNotEquals(dates[0], this);
52          Assertions.assertNotEquals(dates[0], dates[0].getDate());
53          Assertions.assertNotEquals(dates[0], dates[0].getTime());
54      }
55  
56      @Test
57      public void testOffset() {
58          DateTimeComponents reference = new DateTimeComponents(2005, 12, 31, 23, 59, 59);
59          DateTimeComponents expected  = new DateTimeComponents(2006,  1,  1,  0,  0,  0);
60          Assertions.assertEquals(expected, new DateTimeComponents(reference, 1));
61      }
62  
63      @Test
64      public void testSymmetry() {
65          DateTimeComponents reference1 = new DateTimeComponents(2005, 12, 31, 12, 0, 0);
66          DateTimeComponents reference2 = new DateTimeComponents(2006,  1,  1,  1, 2, 3);
67          for (double dt = -100000; dt < 100000; dt += 100) {
68              Assertions.assertEquals(dt, new DateTimeComponents(reference1, dt).offsetFrom(reference1), 1.0e-15);
69              Assertions.assertEquals(dt, new DateTimeComponents(reference2, dt).offsetFrom(reference2), 1.0e-15);
70          }
71      }
72  
73      @Test
74      public void testString() {
75          final DateTimeComponents date =
76              new DateTimeComponents(DateComponents.J2000_EPOCH, TimeComponents.H12);
77          Assertions.assertEquals("2000-01-01T12:00:00.000+00:00", date.toString());
78      }
79  
80      @Test
81      public void testMonth() {
82          Assertions.assertEquals(new DateTimeComponents(2011, 2, 23),
83                              new DateTimeComponents(2011, Month.FEBRUARY, 23));
84          Assertions.assertEquals(new DateTimeComponents(2011, 2, 23, 1, 2, 3.4),
85                              new DateTimeComponents(2011, Month.FEBRUARY, 23, 1, 2, 3.4));
86      }
87  
88      @Test
89      public void testParse() {
90          String s = "2000-01-02T03:04:05.000";
91          Assertions.assertEquals(s, DateTimeComponents.parseDateTime(s).toStringWithoutUtcOffset());
92          s = "2000-01-02T03:04:05.000+00:00";
93          Assertions.assertEquals(s, DateTimeComponents.parseDateTime(s).toString());
94      }
95  
96      @Test
97      public void testBadDay() {
98          Assertions.assertThrows(IllegalArgumentException.class,
99                                  () -> DateTimeComponents.parseDateTime("2000-02-30T03:04:05.000+00:00"));
100     }
101 
102     @Test
103     public void testLocalTime() {
104         final DateTimeComponents dtc = DateTimeComponents.parseDateTime("2000-02-29T03:04:05.000+00:01");
105         Assertions.assertEquals(1, dtc.getTime().getMinutesFromUTC());
106     }
107 
108     /**
109      * This test is for offsets from UTC. The corresponding test in AbsoluteDateTest
110      * handles UTC.
111      */
112     @Test
113     public void testToStringRfc3339() {
114         // setup
115         int m = 779; // 12 hours, 59 minutes
116         final double sixtyOne = FastMath.nextDown(61.0);
117 
118         // action + verify
119         check(2009, 1, 1, 12, 0, 0, m, "2009-01-01T12:00:00+12:59");
120         check(2009, 1, 1, 12, 0, 0, -m, "2009-01-01T12:00:00-12:59");
121         check(2009, 1, 1, 12, 0, 0, 1, "2009-01-01T12:00:00+00:01");
122         check(2009, 1, 1, 12, 0, 0, -1, "2009-01-01T12:00:00-00:01");
123         // 00:00:00 local time
124         check(2009, 1, 1, 0, 0, 0, 59, "2009-01-01T00:00:00+00:59");
125         check(2009, 1, 1, 0, 0, 0, -59, "2009-01-01T00:00:00-00:59");
126         check(2009, 1, 1, 0, 0, 0, 0, "2009-01-01T00:00:00Z");
127         // 00:00:00 UTC, but in a time zone
128         check(2009, 1, 2, 1, 0, 0, 60, "2009-01-02T01:00:00+01:00");
129         check(2009, 1, 1, 23, 0, 0, -60, "2009-01-01T23:00:00-01:00");
130         // leap seconds
131         check(2009, 12, 31, 23, 59, sixtyOne, m, "2009-12-31T23:59:60.999999999999992895+12:59");
132         check(2009, 12, 31, 23, 59, sixtyOne, -m, "2009-12-31T23:59:60.999999999999992895-12:59");
133         check(9999, 2, 3, 4, 5, 60.5, 60, "9999-02-03T04:05:60.5+01:00");
134         check(9999, 2, 3, 4, 5, 60.5, -60, "9999-02-03T04:05:60.5-01:00");
135         // time zone offsets larger than 99:59?
136         // TimeComponents should limit time zone offset to valid values.
137         // toString will just format the values given
138         check(2009, 1, 1, 12, 0, 0, 100*60, "2009-01-01T12:00:00+100:00");
139         check(2009, 1, 1, 12, 0, 0, -100*60, "2009-01-01T12:00:00-100:00");
140         // same for negative years
141         check(-1, 1, 1, 12, 0, 0, m, "-001-01-01T12:00:00+12:59");
142         check(-1, 1, 1, 12, 0, 0, -m, "-001-01-01T12:00:00-12:59");
143         check(-1000, 1, 1, 12, 0, 0, m, "-1000-01-01T12:00:00+12:59");
144         check(-1000, 1, 1, 12, 0, 0, -m, "-1000-01-01T12:00:00-12:59");
145     }
146 
147     private static void check(
148             int year, int month, int day, int hour, int minute, double second,
149             int minutesFromUtc,
150             String expected) {
151         DateTimeComponents actual = new DateTimeComponents(
152                 new DateComponents(year, month, day),
153                 new TimeComponents(hour, minute, second, minutesFromUtc));
154 
155         MatcherAssert.assertThat(actual.toStringRfc3339(), CoreMatchers.is(expected));
156     }
157 
158     @Test
159     public void testToStringRounding() {
160         // these tests were copied from AbsoluteDateTest
161         check(2015, 9, 30, 7, 54, 60 - 9.094947e-13, 60,
162                 "2015-09-30T07:54:59.99999999999909+00:00",
163                 "2015-09-30T07:55:00.000+00:00",
164                 "2015-09-30T07:55:00+00:00");
165         check(2008, 2, 29, 23, 59, 59.9994, 60,
166                 "2008-02-29T23:59:59.99940000000000+00:00",
167                 "2008-02-29T23:59:59.999+00:00",
168                 "2008-03-01T00:00:00+00:00");
169         check(2008, 2, 29, 23, 59, 59.9996, 60,
170                 "2008-02-29T23:59:59.99960000000000+00:00",
171                 "2008-03-01T00:00:00.000+00:00",
172                 "2008-03-01T00:00:00+00:00");
173         // check a leap second
174         check(2015, 6, 30, 23, 59, 59.999999, 61,
175                 "2015-06-30T23:59:59.99999900000000+00:00",
176                 "2015-06-30T23:59:60.000+00:00",
177                 "2015-06-30T23:59:60+00:00");
178         check(2015, 6, 30, 23, 59, 60.5, 61,
179                 "2015-06-30T23:59:60.50000000000000+00:00",
180                 "2015-06-30T23:59:60.500+00:00",
181                 "2015-07-01T00:00:00+00:00");
182         // check a bigger leap second. First leap was 1.422818 s.
183         // TODO can't run this test because of #707
184         //check(1960, 12, 31, 23, 59, 61.42281, 62,
185         //        "1960-12-31T23:59:61.42281000000000+00:00",
186         //        "1960-12-31T23:59:61.42300000000000+00:00", // TODO this date is invalid
187         //        "1961-01-01T00:00:00+00:00");
188     }
189 
190     private void check(int year, int month, int day, int hour, int minute, double second,
191                        int minuteDuration, String full, String medium, String shor) {
192         DateTimeComponents dtc =
193                 new DateTimeComponents(year, month, day, hour, minute, second);
194         MatcherAssert.assertThat(dtc.toString(minuteDuration), CoreMatchers.is(medium));
195         MatcherAssert.assertThat(dtc.toString(minuteDuration, 3), CoreMatchers.is(medium));
196         MatcherAssert.assertThat(dtc.toString(minuteDuration, 0), CoreMatchers.is(shor));
197         MatcherAssert.assertThat(dtc.toString(minuteDuration, 14), CoreMatchers.is(full));
198     }
199 
200     @Test
201     public void testToStringRoundingUtcOffset() {
202         DateTimeComponents dtc =
203                 new DateTimeComponents(new DateComponents(2000, 12, 31), new TimeComponents(23, 59, 59.9, -92));
204         MatcherAssert.assertThat(dtc.toString(60), CoreMatchers.is("2000-12-31T23:59:59.900-01:32"));
205         MatcherAssert.assertThat(dtc.toString(60, 3), CoreMatchers.is("2000-12-31T23:59:59.900-01:32"));
206         MatcherAssert.assertThat(dtc.toString(60, 0), CoreMatchers.is("2001-01-01T00:00:00-01:32"));
207         MatcherAssert.assertThat(dtc.toString(60, 14), CoreMatchers.is("2000-12-31T23:59:59.90000000000000-01:32"));
208     }
209 
210     @Test
211     public void testToStringWithoutUtcOffsetRoundingUtcOffset() {
212         DateTimeComponents dtc =
213                 new DateTimeComponents(new DateComponents(2000, 12, 31), new TimeComponents(23, 59, 59.9, -92));
214         MatcherAssert.assertThat(dtc.toStringWithoutUtcOffset(60, 3), CoreMatchers.is("2000-12-31T23:59:59.900"));
215         MatcherAssert.assertThat(dtc.toStringWithoutUtcOffset(60, 0), CoreMatchers.is("2001-01-01T00:00:00"));
216         MatcherAssert.assertThat(dtc.toStringWithoutUtcOffset(60, 14), CoreMatchers.is("2000-12-31T23:59:59.90000000000000"));
217     }
218 
219     @Test
220     public void testOffsetFromWithTimeUnit() {
221         DateTimeComponents reference = new DateTimeComponents(2023, 1, 1, 23, 13, 59.12334567);
222         for (TimeUnit timeUnit : TimeUnit.values()) {
223             Assertions.assertEquals(0, reference.offsetFrom(reference, timeUnit));
224 
225             long dayInTimeUnit = timeUnit.convert((long) Constants.JULIAN_DAY, TimeUnit.SECONDS);
226             for (int i = 1; i <= 365; i++) {
227                 DateTimeComponents minusDays = new DateTimeComponents(reference, -i, TimeUnit.DAYS);
228                 DateTimeComponents plusDays = new DateTimeComponents(reference,i, TimeUnit.DAYS);
229 
230 
231                 Assertions.assertEquals(i * dayInTimeUnit, reference.offsetFrom(minusDays, timeUnit));
232 
233                 Assertions.assertEquals(-i * dayInTimeUnit, reference.offsetFrom(plusDays, timeUnit));
234             }
235 
236             for (long ns = 1; ns <= 1_000_000_000; ns += 1_000_000) {
237                 DateTimeComponents minus =  new DateTimeComponents(reference,-ns, TimeUnit.NANOSECONDS);
238                 DateTimeComponents plus =  new DateTimeComponents(reference,ns, TimeUnit.NANOSECONDS);
239 
240                 double deltaInTimeUnit = ns / (double) timeUnit.toNanos(1);
241                 Assertions.assertEquals(FastMath.round(deltaInTimeUnit), reference.offsetFrom(minus, timeUnit),
242                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
243 
244                 Assertions.assertEquals(FastMath.round(-deltaInTimeUnit), reference.offsetFrom(plus, timeUnit),
245                     String.format("TimeUnit: %s, ns: %d", timeUnit, ns));
246             }
247 
248 
249         }
250     }
251 
252 }