1   /* Copyright 2002-2024 Luc Maisonobe
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.hipparchus.exception.LocalizedCoreFormats;
20  import org.hipparchus.util.FastMath;
21  import org.orekit.errors.OrekitException;
22  import org.orekit.errors.OrekitMessages;
23  
24  import java.io.Serializable;
25  import java.util.concurrent.TimeUnit;
26  
27  /** This class represents a time range split into seconds and attoseconds.
28   * <p>
29   * Instances of this class may either be interpreted as offsets from a reference
30   * date, or they may be interpreted as durations. Negative values represent
31   * dates earlier than the reference date in the first interpretation, and
32   * negative durations in the second interpretation.
33   * </p>
34   * <p>
35   * The whole number of seconds is stored as signed primitive long, so the range
36   * of dates that can be represented is ±292 billion years. The fractional part
37   * within the second is stored as non-negative primitive long with fixed precision
38   * at a resolution of one attosecond (10⁻¹⁸s). The choice of attoseconds allows
39   * to represent exactly all important offsets (between TT and TAI, or between UTC
40   * and TAI during the linear eras), as well as all times converted from standard
41   * Java Instant, Date or TimeUnit classes. It also allows simple computation as
42   * adding or subtracting a few values in attoseconds that are less than one second
43   * does not overflow (a primitive long could hold any values between ±9.22s in
44   * attoseconds so simple additions and subtractions followed by handling a carry
45   * to bring the value back between 0 and 10¹⁸ is straightforward). There are also
46   * special encodings (internally using negative longs in the fractional part) to
47   * represent {@link #NaN}, {@link #POSITIVE_INFINITY} and {@link #NEGATIVE_INFINITY}.
48   * </p>
49   * @author Luc Maisonobe
50   * @see AbsoluteDate
51   * @see FieldAbsoluteDate
52   * @since 13.0
53   */
54  public class TimeOffset
55      implements Comparable<TimeOffset>, Serializable {
56  
57      /** Split time representing 0. */
58      public static final TimeOffset ZERO = new TimeOffset(0L, 0L);
59  
60      /** Split time representing 1 attosecond. */
61      public static final TimeOffset ATTOSECOND = new TimeOffset(0L, 1L);
62  
63      /** Split time representing 1 femtosecond. */
64      public static final TimeOffset FEMTOSECOND = new TimeOffset(0L, 1000L);
65  
66      /** Split time representing 1 picosecond. */
67      public static final TimeOffset PICOSECOND = new TimeOffset(0L, 1000000L);
68  
69      /** Split time representing 1 nanosecond. */
70      public static final TimeOffset NANOSECOND = new TimeOffset(0L, 1000000000L);
71  
72      /** Split time representing 1 microsecond. */
73      public static final TimeOffset MICROSECOND = new TimeOffset(0L, 1000000000000L);
74  
75      /** Split time representing 1 millisecond. */
76      public static final TimeOffset MILLISECOND = new TimeOffset(0L, 1000000000000000L);
77  
78      /** Split time representing 1 second. */
79      public static final TimeOffset SECOND = new TimeOffset(1L, 0L);
80  
81      /** Split time representing 1 minute. */
82      public static final TimeOffset MINUTE = new TimeOffset(60L, 0L);
83  
84      /** Split time representing 1 hour. */
85      public static final TimeOffset HOUR = new TimeOffset(3600L, 0L);
86  
87      /** Split time representing 1 day. */
88      public static final TimeOffset DAY = new TimeOffset(86400L, 0L);
89  
90      /** Split time representing 1 day that includes an additional leap second. */
91      public static final TimeOffset DAY_WITH_POSITIVE_LEAP = new TimeOffset(86401L, 0L);
92  
93      // CHECKSTYLE: stop ConstantName
94      /** Split time representing a NaN. */
95      public static final TimeOffset NaN = new TimeOffset(Double.NaN);
96      // CHECKSTYLE: resume ConstantName
97  
98      /** Split time representing negative infinity. */
99      public static final TimeOffset NEGATIVE_INFINITY = new TimeOffset(Double.NEGATIVE_INFINITY);
100 
101     /** Split time representing positive infinity. */
102     public static final TimeOffset POSITIVE_INFINITY = new TimeOffset(Double.POSITIVE_INFINITY);
103 
104     /** Indicator for NaN time (bits pattern arbitrarily selected to avoid hashcode collisions). */
105     private static final long NAN_INDICATOR      = -0XFFL;
106 
107     /** Indicator for positive infinite time (bits pattern arbitrarily selected to avoid hashcode collisions). */
108     private static final long POSITIVE_INFINITY_INDICATOR = -0XFF00L;
109 
110     /** Indicator for negative infinite time (bits pattern arbitrarily selected to avoid hashcode collisions). */
111     private static final long NEGATIVE_INFINITY_INDICATOR = -0XFF0000L;
112 
113     /** Milliseconds in one second. */
114     private static final long MILLIS_IN_SECOND = 1000L;
115 
116     /** Microseconds in one second. */
117     private static final long MICROS_IN_SECOND = 1000000L;
118 
119     /** Nanoseconds in one second. */
120     private static final long NANOS_IN_SECOND = 1000000000L;
121 
122     /** Attoseconds in one second. */
123     private static final long ATTOS_IN_SECOND = 1000000000000000000L;
124 
125     /** Attoseconds in one half-second. */
126     private static final long ATTOS_IN_HALF_SECOND = 500000000000000000L;
127 
128     /** Factor to split long for multiplications.
129      * <p>
130      * It is important that SPLIT * SPLIT = ATTOS_IN_SECOND.
131      * </p>
132      */
133     private static final long SPLIT = 1000000000L;
134 
135     /** Number of digits after separator for attoseconds. */
136     private static final int DIGITS_ATTOS = 18;
137 
138     /** Multipliers for parsing partial strings. */
139     // CHECKSTYLE: stop Indentation check
140     private static final long[] MULTIPLIERS = new long[] {
141                          1L,
142                         10L,
143                        100L,
144                       1000L,
145                      10000L,
146                     100000L,
147                    1000000L,
148                   10000000L,
149                  100000000L,
150                 1000000000L,
151                10000000000L,
152               100000000000L,
153              1000000000000L,
154             10000000000000L,
155            100000000000000L,
156           1000000000000000L,
157          10000000000000000L,
158         100000000000000000L,
159        1000000000000000000L
160     };
161     // CHECKSTYLE: resume Indentation check
162 
163     /** Serializable UID. */
164     private static final long serialVersionUID = 20240711L;
165 
166     /** Seconds part. */
167     private final long seconds;
168 
169     /** AttoSeconds part. */
170     private final long attoSeconds;
171 
172     /**
173      * Build a time by adding several times.
174      * @param times times to add
175      */
176     public TimeOffset(final TimeOffset... times) {
177         final RunningSum runningSum = new RunningSum();
178         for (final TimeOffset time : times) {
179             runningSum.add(time);
180         }
181         final TimeOffset sum = runningSum.normalize();
182         this.seconds     = sum.getSeconds();
183         this.attoSeconds = sum.getAttoSeconds();
184     }
185 
186     /**
187      * Build a time from its components.
188      * <p>
189      * The components will be normalized so that {@link #getAttoSeconds()}
190      * returns a value between {@code 0L} and {1000000000000000000L}
191      * </p>
192      * @param seconds seconds part
193      * @param attoSeconds attoseconds part
194      */
195     public TimeOffset(final long seconds, final long attoSeconds) {
196         final long qAtto = attoSeconds / ATTOS_IN_SECOND;
197         final long rAtto = attoSeconds - qAtto * ATTOS_IN_SECOND;
198         if (rAtto < 0L) {
199             this.seconds     = seconds + qAtto - 1L;
200             this.attoSeconds = ATTOS_IN_SECOND + rAtto;
201         } else {
202             this.seconds     = seconds + qAtto;
203             this.attoSeconds = rAtto;
204         }
205     }
206 
207     /**
208      * Build a time from a value in seconds.
209      *
210      * @param time time
211      */
212     public TimeOffset(final double time) {
213         if (Double.isNaN(time)) {
214             seconds     = 0L;
215             attoSeconds = NAN_INDICATOR;
216         } else if (time < Long.MIN_VALUE || time > Long.MAX_VALUE) {
217             if (time < 0L) {
218                 seconds     = Long.MIN_VALUE;
219                 attoSeconds = NEGATIVE_INFINITY_INDICATOR;
220             } else {
221                 seconds     = Long.MAX_VALUE;
222                 attoSeconds = POSITIVE_INFINITY_INDICATOR;
223             }
224         } else {
225             final double tiSeconds  = FastMath.rint(time);
226             final double subSeconds = time - tiSeconds;
227             if (subSeconds < 0L) {
228                 seconds     = (long) tiSeconds - 1L;
229                 attoSeconds = FastMath.round(subSeconds * ATTOS_IN_SECOND) + ATTOS_IN_SECOND;
230             } else {
231                 seconds     = (long) tiSeconds;
232                 attoSeconds = FastMath.round(subSeconds * ATTOS_IN_SECOND);
233             }
234         }
235     }
236 
237     /**
238      * Multiplicative constructor.
239      * <p>
240      * This constructors builds a split time corresponding to {@code factor} ⨉ {@code time}
241      * </p>
242      * @param factor multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
243      * @param time base time
244      */
245     public TimeOffset(final long factor, final TimeOffset time) {
246         this(factor < 0 ? time.multiply(-factor).negate() : time.multiply(factor));
247     }
248 
249     /**
250      * Linear combination constructor.
251      * <p>
252      * This constructors builds a split time corresponding to
253      * {@code f1} ⨉ {@code t1} + {@code f2} ⨉ {@code t2}
254      * </p>
255      * @param f1 first multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
256      * @param t1 first base time
257      * @param f2 second multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
258      * @param t2 second base time
259      */
260     public TimeOffset(final long f1, final TimeOffset t1,
261                       final long f2, final TimeOffset t2) {
262         this(new TimeOffset(f1, t1).add(new TimeOffset(f2, t2)));
263     }
264 
265     /**
266      * Linear combination constructor.
267      * <p>
268      * This constructors builds a split time corresponding to
269      * {@code f1} ⨉ {@code t1} + {@code f2} ⨉ {@code t2} + {@code f3} ⨉ {@code t3}
270      * </p>
271      * @param f1 first multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
272      * @param t1 first base time
273      * @param f2 second multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
274      * @param t2 second base time
275      * @param f3 third multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
276      * @param t3 third base time
277      */
278     public TimeOffset(final long f1, final TimeOffset t1,
279                       final long f2, final TimeOffset t2,
280                       final long f3, final TimeOffset t3) {
281         this(new TimeOffset(f1, t1).add(new TimeOffset(f2, t2)).add(new TimeOffset(f3, t3)));
282     }
283 
284     /**
285      * Linear combination constructor.
286      * <p>
287      * This constructors builds a split time corresponding to
288      * {@code f1} ⨉ {@code t1} + {@code f2} ⨉ {@code t2} + {@code f3} ⨉ {@code t3} + {@code f4} ⨉ {@code t4}
289      * </p>
290      * @param f1 first multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
291      * @param t1 first base time
292      * @param f2 second multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
293      * @param t2 second base time
294      * @param f3 third multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
295      * @param t3 third base time
296      * @param f4 fourth multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
297      * @param t4 fourth base time
298      */
299     public TimeOffset(final long f1, final TimeOffset t1,
300                       final long f2, final TimeOffset t2,
301                       final long f3, final TimeOffset t3,
302                       final long f4, final TimeOffset t4) {
303         this(new TimeOffset(f1, t1).
304              add(new TimeOffset(f2, t2)).
305              add(new TimeOffset(f3, t3)).
306              add(new TimeOffset(f4, t4)));
307     }
308 
309     /**
310      * Linear combination constructor.
311      * <p>
312      * This constructors builds a split time corresponding to
313      * {@code f1} ⨉ {@code t1} + {@code f2} ⨉ {@code t2} + {@code f3} ⨉ {@code t3} + {@code f4} ⨉ {@code t4} + {@code f5} ⨉ {@code t5}
314      * </p>
315      * @param f1 first multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
316      * @param t1 first base time
317      * @param f2 second multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
318      * @param t2 second base time
319      * @param f3 third multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
320      * @param t3 third base time
321      * @param f4 fourth multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
322      * @param t4 fourth base time
323      * @param f5 fifth multiplicative factor (negative values allowed here, contrary to {@link #multiply(long)})
324      * @param t5 fifth base time
325      */
326     public TimeOffset(final long f1, final TimeOffset t1,
327                       final long f2, final TimeOffset t2,
328                       final long f3, final TimeOffset t3,
329                       final long f4, final TimeOffset t4,
330                       final long f5, final TimeOffset t5) {
331         this(new TimeOffset(f1, t1).
332              add(new TimeOffset(f2, t2)).
333              add(new TimeOffset(f3, t3)).
334              add(new TimeOffset(f4, t4)).
335              add(new TimeOffset(f5, t5)));
336     }
337 
338     /**
339      * Build a time from a value defined in some time unit.
340      *
341      * @param time time
342      * @param unit   time unit in which {@code time} is expressed
343      */
344     public TimeOffset(final long time, final TimeUnit unit) {
345         switch (unit) {
346             case DAYS: {
347                 final long limit = (Long.MAX_VALUE - DAY.seconds / 2) / DAY.seconds;
348                 if (time < -limit) {
349                     seconds     = Long.MIN_VALUE;
350                     attoSeconds = NEGATIVE_INFINITY_INDICATOR;
351                 } else if (time > limit) {
352                     seconds     = Long.MAX_VALUE;
353                     attoSeconds = POSITIVE_INFINITY_INDICATOR;
354                 } else {
355                     seconds = time * DAY.seconds;
356                     attoSeconds = 0L;
357                 }
358                 break;
359             }
360             case HOURS: {
361                 final long limit = (Long.MAX_VALUE - HOUR.seconds / 2) / HOUR.seconds;
362                 if (time < -limit) {
363                     seconds     = Long.MIN_VALUE;
364                     attoSeconds = NEGATIVE_INFINITY_INDICATOR;
365                 } else if (time > limit) {
366                     seconds     = Long.MAX_VALUE;
367                     attoSeconds = POSITIVE_INFINITY_INDICATOR;
368                 } else {
369                     seconds     = time * HOUR.seconds;
370                     attoSeconds = 0L;
371                 }
372                 break;
373             }
374             case MINUTES: {
375                 final long limit = (Long.MAX_VALUE - MINUTE.seconds / 2) / MINUTE.seconds;
376                 if (time < -limit) {
377                     seconds     = Long.MIN_VALUE;
378                     attoSeconds = NEGATIVE_INFINITY_INDICATOR;
379                 } else if (time > limit) {
380                     seconds     = Long.MAX_VALUE;
381                     attoSeconds = POSITIVE_INFINITY_INDICATOR;
382                 } else {
383                     seconds     = time * MINUTE.seconds;
384                     attoSeconds = 0L;
385                 }
386                 break;
387             }
388             case SECONDS:
389                 seconds     = time;
390                 attoSeconds = 0L;
391                 break;
392             case MILLISECONDS: {
393                 final long s = time / MILLIS_IN_SECOND;
394                 final long r = (time - s * MILLIS_IN_SECOND) * MILLISECOND.attoSeconds;
395                 if (r < 0L) {
396                     seconds     = s - 1L;
397                     attoSeconds = ATTOS_IN_SECOND + r;
398                 } else {
399                     seconds     = s;
400                     attoSeconds = r;
401                 }
402                 break;
403             }
404             case MICROSECONDS: {
405                 final long s = time / MICROS_IN_SECOND;
406                 final long r = (time - s * MICROS_IN_SECOND) * MICROSECOND.attoSeconds;
407                 if (r < 0L) {
408                     seconds     = s - 1L;
409                     attoSeconds = ATTOS_IN_SECOND + r;
410                 } else {
411                     seconds     = s;
412                     attoSeconds = r;
413                 }
414                 break;
415             }
416             case NANOSECONDS: {
417                 final long s = time / NANOS_IN_SECOND;
418                 final long r = (time - s * NANOS_IN_SECOND) * NANOSECOND.attoSeconds;
419                 if (r < 0L) {
420                     seconds     = s - 1L;
421                     attoSeconds = ATTOS_IN_SECOND + r;
422                 } else {
423                     seconds     = s;
424                     attoSeconds = r;
425                 }
426                 break;
427             }
428             default:
429                 throw new OrekitException(OrekitMessages.UNKNOWN_UNIT, unit.name());
430         }
431     }
432 
433     /** Copy constructor, for internal use only.
434      * @param time time to copy
435      */
436     private TimeOffset(final TimeOffset time) {
437         seconds     = time.seconds;
438         attoSeconds = time.attoSeconds;
439     }
440 
441     /** check if the time is zero.
442      * @return true if the time is zero
443      */
444     public boolean isZero() {
445         return seconds == 0L && attoSeconds == 0L;
446     }
447 
448     /** Check if time is finite (i.e. neither {@link #isNaN() NaN} nor {@link #isInfinite() infinite)}.
449      * @return true if time is finite
450      * @see #isNaN()
451      * @see #isInfinite()
452      * @see #isNegativeInfinity()
453      * @see #isPositiveInfinity()
454      */
455     public boolean isFinite() {
456         return attoSeconds >= 0L;
457     }
458 
459     /** Check if time is NaN.
460      * @return true if time is NaN
461      * @see #isFinite()
462      * @see #isInfinite()
463      * @see #isNegativeInfinity()
464      * @see #isPositiveInfinity()
465      */
466     public boolean isNaN() {
467         return attoSeconds == NAN_INDICATOR;
468     }
469 
470     /** Check if time is infinity.
471      * @return true if time is infinity
472      * @see #isFinite()
473      * @see #isNaN()
474      * @see #isNegativeInfinity()
475      * @see #isPositiveInfinity()
476      */
477     public boolean isInfinite() {
478         return isPositiveInfinity() || isNegativeInfinity();
479     }
480 
481     /** Check if time is positive infinity.
482      * @return true if time is positive infinity
483      * @see #isFinite()
484      * @see #isNaN()
485      * @see #isInfinite()
486      * @see #isNegativeInfinity()
487      */
488     public boolean isPositiveInfinity() {
489         return attoSeconds == POSITIVE_INFINITY_INDICATOR;
490     }
491 
492     /** Check if time is negative infinity.
493      * @return true if time is negative infinity
494      * @see #isFinite()
495      * @see #isNaN()
496      * @see #isInfinite()
497      * @see #isPositiveInfinity()
498      */
499     public boolean isNegativeInfinity() {
500         return attoSeconds == NEGATIVE_INFINITY_INDICATOR;
501     }
502 
503     /** Build a time by adding two times.
504      * @param t time to add
505      * @return this+t
506      */
507     public TimeOffset add(final TimeOffset t) {
508         final RunningSum runningSum = new RunningSum();
509         runningSum.add(this);
510         runningSum.add(t);
511         return runningSum.normalize();
512     }
513 
514     /** Build a time by subtracting one time from the instance.
515      * @param t time to subtract
516      * @return this-t
517      */
518     public TimeOffset subtract(final TimeOffset t) {
519         if (attoSeconds < 0 || t.attoSeconds < 0) {
520             // gather all special cases in one big check to avoid rare multiple tests
521             if (isNaN() ||
522                 t.isNaN() ||
523                 isPositiveInfinity() && t.isPositiveInfinity() ||
524                 isNegativeInfinity() && t.isNegativeInfinity()) {
525                 return NaN;
526             } else if (isInfinite()) {
527                 // t is either a finite time or the infinity opposite to this
528                 return this;
529             } else {
530                 // this is either a finite time or the infinity opposite to t
531                 return t.isPositiveInfinity() ? NEGATIVE_INFINITY : POSITIVE_INFINITY;
532             }
533         } else {
534             // regular subtraction between two finite times
535             return new TimeOffset(seconds - t.seconds, attoSeconds - t.attoSeconds);
536         }
537     }
538 
539     /** Multiply the instance by a positive or zero constant.
540      * @param p multiplication factor (must be positive)
541      * @return this ⨉ p
542      */
543     public TimeOffset multiply(final long p) {
544         if (p < 0) {
545             throw new OrekitException(OrekitMessages.NOT_POSITIVE, p);
546         }
547         if (isFinite()) {
548             final TimeOffset abs   = seconds < 0 ? negate() : this;
549             final long pHigh   = p / SPLIT;
550             final long pLow    = p - pHigh * SPLIT;
551             final long sHigh   = abs.seconds / SPLIT;
552             final long sLow    = abs.seconds - sHigh * SPLIT;
553             final long aHigh   = abs.attoSeconds / SPLIT;
554             final long aLow    = abs.attoSeconds - aHigh * SPLIT;
555             final long ps1     = pHigh * sLow + pLow * sHigh;
556             final long ps0     = pLow * sLow;
557             final long pa2     = pHigh * aHigh;
558             final long pa1     = pHigh * aLow + pLow * aHigh;
559             final long pa1High = pa1 / SPLIT;
560             final long pa1Low  = pa1 - pa1High * SPLIT;
561             final long pa0     = pLow * aLow;
562 
563             // check for overflow
564             if (pHigh * sHigh != 0 || ps1 / SPLIT != 0) {
565                 throw new OrekitException(LocalizedCoreFormats.OVERFLOW_IN_MULTIPLICATION, abs.seconds, p);
566             }
567 
568             // here we use the fact that SPLIT * SPLIT = ATTOS_IN_SECOND
569             final TimeOffset mul = new TimeOffset(SPLIT * ps1 + ps0 + pa2 + pa1High, SPLIT * pa1Low + pa0);
570             return seconds < 0 ? mul.negate() : mul;
571         } else {
572             // already NaN, +∞ or -∞, unchanged except 0 ⨉ ±∞ = NaN
573             return p == 0 ? TimeOffset.NaN : this;
574         }
575     }
576 
577     /** Divide the instance by a positive constant.
578      * @param q division factor (must be strictly positive)
579      * @return this ÷ q
580      */
581     public TimeOffset divide(final int q) {
582         if (q <= 0) {
583             throw new OrekitException(OrekitMessages.NOT_STRICTLY_POSITIVE, q);
584         }
585         if (isFinite()) {
586             final long      sSec  = seconds         / q;
587             final long      rSec  = seconds         - sSec * q;
588             final long      sK    = ATTOS_IN_SECOND / q;
589             final long      rK    = ATTOS_IN_SECOND - sK * q;
590             final TimeOffset tsSec = new TimeOffset(0L, sSec);
591             final TimeOffset trSec = new TimeOffset(0L, rSec);
592             return new TimeOffset(tsSec.multiply(sK).multiply(q),
593                                   tsSec.multiply(rK),
594                                   trSec.multiply(sK),
595                                   // here, we use the fact q is a positive int (not a long!)
596                                   // hence rSec * rK < q² does not overflow
597                                   new TimeOffset(0L, (attoSeconds + rSec * rK) / q));
598         } else {
599             // already NaN, +∞ or -∞, unchanged as q > 0
600             return this;
601         }
602     }
603 
604     /** Negate the instance.
605      * @return new instance corresponding to opposite time
606      */
607     public TimeOffset negate() {
608         // handle special cases
609         if (attoSeconds < 0) {
610             // gather all special cases in one big check to avoid rare multiple tests
611             return isNaN() ? this : (seconds < 0 ? POSITIVE_INFINITY : NEGATIVE_INFINITY);
612         } else {
613             // the negative number of attoseconds will be normalized back to positive by the constructor
614             return new TimeOffset(-seconds, -attoSeconds);
615         }
616     }
617 
618     /** Get the time in some unit.
619      * @param unit time unit
620      * @return time in this unit, rounded to the closest long,
621      * returns arbitrarily {@link Long#MAX_VALUE} for {@link #isNaN() NaN times}
622      */
623     public long getRoundedTime(final TimeUnit unit) {
624 
625         // handle special cases
626         if (attoSeconds < 0) {
627             // gather all special cases in one big check to avoid rare multiple tests
628             return (isNaN() || seconds >= 0) ? Long.MAX_VALUE : Long.MIN_VALUE;
629         }
630 
631         final long sign = seconds < 0L ? -1L : 1L;
632         switch (unit) {
633             case DAYS:
634                 return sign * ((sign * seconds + DAY.seconds / 2) / DAY.seconds);
635             case HOURS:
636                 return sign * ((sign * seconds + HOUR.seconds / 2) / HOUR.seconds);
637             case MINUTES:
638                 return sign * ((sign * seconds + MINUTE.seconds / 2) / MINUTE.seconds);
639             case SECONDS:
640                 return seconds + ((attoSeconds >= ATTOS_IN_SECOND / 2) ? 1 : 0);
641             case MILLISECONDS:
642                 return seconds * MILLIS_IN_SECOND +
643                        (attoSeconds + MILLISECOND.attoSeconds / 2) / MILLISECOND.attoSeconds;
644             case MICROSECONDS:
645                 return seconds * MICROS_IN_SECOND +
646                        (attoSeconds + MICROSECOND.attoSeconds / 2) / MICROSECOND.attoSeconds;
647             case NANOSECONDS:
648                 return seconds * NANOS_IN_SECOND +
649                        (attoSeconds + NANOSECOND.attoSeconds / 2) / NANOSECOND.attoSeconds;
650             default:
651                 throw new OrekitException(OrekitMessages.UNKNOWN_UNIT, unit.name());
652         }
653     }
654 
655     /** Get the normalized seconds part of the time.
656      * @return normalized seconds part of the time (may be negative)
657      */
658     public long getSeconds() {
659         return seconds;
660     }
661 
662     /** Get the normalized attoseconds part of the time.
663      * <p>
664      * The normalized attoseconds is always between {@code 0L} and
665      * {@code 1000000000000000000L} for <em>finite</em> ranges. Note that it
666      * may reach {@code 1000000000000000000L} if for example the time is less
667      * than 1 attosecond <em>before</em> a whole second. It is negative
668      * for {@link #isNaN() NaN} or {@link #isInfinite() infinite} times.
669      * </p>
670      * @return normalized attoseconds part of the time
671      */
672     public long getAttoSeconds() {
673         return attoSeconds;
674     }
675 
676     /** Get the time collapsed into a single double.
677      * <p>
678      * Beware that lots of accuracy is lost when combining {@link #getSeconds()} and {@link #getAttoSeconds()}
679      * into a single double.
680      * </p>
681      * @return time as a single double
682      */
683     public double toDouble() {
684         if (isFinite()) {
685             // regular value
686             long closeSeconds      = seconds;
687             long signedAttoSeconds = attoSeconds;
688             if (attoSeconds > ATTOS_IN_HALF_SECOND) {
689                 // we are closer to next second than to previous one
690                 // take this into account in the computation
691                 // in order to avoid losing precision
692                 closeSeconds++;
693                 signedAttoSeconds -= ATTOS_IN_SECOND;
694             }
695             return closeSeconds + ((double) signedAttoSeconds) / ATTOS_IN_SECOND;
696         } else {
697             // special values
698             return isNaN() ? Double.NaN : FastMath.copySign(Double.POSITIVE_INFINITY, seconds);
699         }
700     }
701 
702     /** Parse a string to produce an accurate split time.
703      * <p>
704      * This method is more accurate than parsing the string as a double and then
705      * calling {@link TimeOffset#TimeOffset(double)} because it reads the before
706      * separator and after separator parts in decimal, hence avoiding problems like
707      * for example 0.1 not being an exact IEEE754 number.
708      * </p>
709      * @param s string to parse
710      * @return parsed split time
711      */
712     public static TimeOffset parse(final String s) {
713 
714         // decompose the string
715         // we use neither Long.parseLong nor Integer.parseInt because we want to avoid
716         // performing several loops over the characters as we need to keep track of
717         // delimiters decimal point and exponent marker positions
718         final int length = s.length();
719         long significandSign = 1L;
720         int  exponentSign    = 1;
721         int  separatorIndex  = length;
722         int  exponentIndex   = length;
723         long beforeSeparator = 0L;
724         long afterSeparator  = 0L;
725         int  exponent        = 0;
726         int  digitsBefore    = 0;
727         int  digitsAfter     = 0;
728         int  digitsExponent  = 0;
729         int index = 0;
730         while (index < length) {
731 
732             // current character
733             final char c = s.charAt(index);
734 
735             if (Character.isDigit(c)) {
736                 if (separatorIndex == length) {
737                     // we are parsing the part before separator
738                     ++digitsBefore;
739                     beforeSeparator = beforeSeparator * 10 + c - '0';
740                     if (digitsBefore > 19 || beforeSeparator < 0) {
741                         // overflow occurred
742                         break;
743                     }
744                 } else if (exponentIndex == length) {
745                     // we are parsing the part between separator and exponent
746                     if (digitsAfter < DIGITS_ATTOS) {
747                         // we never overflow here, we just ignore extra digits
748                         afterSeparator = afterSeparator * 10 + c - '0';
749                         ++digitsAfter;
750                     }
751                 } else {
752                     // we are parsing the exponent
753                     ++digitsExponent;
754                     exponent = exponent * 10 + c - '0';
755                     if (digitsExponent > 10 || exponent < 0) {
756                         // overflow occurred
757                         break;
758                     }
759                 }
760             } else if (c == '.' && separatorIndex == length) {
761                 separatorIndex = index;
762             } else if ((c == 'e' || c == 'E') && exponentIndex == length) {
763                 if (separatorIndex == length) {
764                     separatorIndex = index;
765                 }
766                 exponentIndex = index;
767             } else if (c == '-') {
768                 if (index == 0) {
769                     significandSign = -1L;
770                 } else if (index == exponentIndex + 1) {
771                     exponentSign = -1;
772                 } else {
773                     break;
774                 }
775             } else if (c == '+') {
776                 if (index == 0) {
777                     significandSign = 1L;
778                 } else if (index == exponentIndex + 1) {
779                     exponentSign = 1;
780                 } else {
781                     break;
782                 }
783             } else {
784                 break;
785             }
786 
787             ++index;
788 
789         }
790 
791         if (length == 0 || index < length) {
792             // decomposition failed, either it is a special case or an unparsable string
793             if (s.equals("-∞")) {
794                 return TimeOffset.NEGATIVE_INFINITY;
795             } else if (s.equals("+∞")) {
796                 return TimeOffset.POSITIVE_INFINITY;
797             } else if (s.equalsIgnoreCase("NaN")) {
798                 return TimeOffset.NaN;
799             } else {
800                 throw new OrekitException(OrekitMessages.CANNOT_PARSE_DATA, s);
801             }
802         }
803 
804         // decomposition was successful, build the split time
805         long seconds;
806         long attoseconds;
807         if (exponentSign < 0) {
808             // the part before separator must be split into seconds and attoseconds
809             if (exponent >= MULTIPLIERS.length) {
810                 seconds = 0L;
811                 if (exponent - DIGITS_ATTOS >= MULTIPLIERS.length) {
812                     // underflow
813                     attoseconds = 0L;
814                 } else {
815                     attoseconds = beforeSeparator / MULTIPLIERS[exponent - DIGITS_ATTOS];
816                 }
817             } else {
818                 final long secondsMultiplier    = MULTIPLIERS[exponent];
819                 final long attoBeforeMultiplier = MULTIPLIERS[DIGITS_ATTOS - exponent];
820                 seconds     = beforeSeparator / secondsMultiplier;
821                 attoseconds = (beforeSeparator - seconds * secondsMultiplier) * attoBeforeMultiplier;
822                 while (digitsAfter + exponent > DIGITS_ATTOS) {
823                     // drop least significant digits below one attosecond
824                     afterSeparator /= 10;
825                     digitsAfter--;
826                 }
827                 final long attoAfterMultiplier = MULTIPLIERS[DIGITS_ATTOS - exponent - digitsAfter];
828                 attoseconds += afterSeparator * attoAfterMultiplier;
829             }
830         } else {
831             // the part after separator must be split into seconds and attoseconds
832             if (exponent >= MULTIPLIERS.length) {
833                 if (beforeSeparator == 0L && afterSeparator == 0L) {
834                     return TimeOffset.ZERO;
835                 } else if (significandSign < 0) {
836                     return TimeOffset.NEGATIVE_INFINITY;
837                 } else {
838                     return TimeOffset.POSITIVE_INFINITY;
839                 }
840             } else {
841                 final long secondsMultiplier = MULTIPLIERS[exponent];
842                 seconds = beforeSeparator * secondsMultiplier;
843                 if (exponent > digitsAfter) {
844                     seconds += afterSeparator * MULTIPLIERS[exponent - digitsAfter];
845                     attoseconds = 0L;
846                 } else {
847                     final long q = afterSeparator / MULTIPLIERS[digitsAfter - exponent];
848                     seconds    += q;
849                     attoseconds = (afterSeparator - q * MULTIPLIERS[digitsAfter - exponent]) *
850                                   MULTIPLIERS[DIGITS_ATTOS - digitsAfter + exponent];
851                 }
852             }
853         }
854 
855         return new TimeOffset(significandSign * seconds, significandSign * attoseconds);
856 
857     }
858 
859     /** Compare the instance with another one.
860      * <p>
861      * Not that in order to be consistent with {@code Double#compareTo(Double)},
862      * NaN is considered equal to itself and greater than positive infinity.
863      * </p>
864      * @param other other time to compare the instance to
865      * @return a negative integer, zero, or a positive integer if applying this time
866      * to reference date would result in a date being before, simultaneous, or after
867      * the date obtained by applying the other time to the same reference date.
868      */
869     public int compareTo(final TimeOffset other) {
870         if (isFinite()) {
871             if (other.isFinite()) {
872                 return seconds == other.seconds ?
873                        Long.compare(attoSeconds, other.attoSeconds) :
874                        Long.compare(seconds, other.seconds);
875             } else {
876                 // if other is ±∞ or NaN, and NaN is considered larger than +∞
877                 return other.isNegativeInfinity() ? 1 : -1;
878             }
879         } else {
880             // instance is ±∞ or NaN, and NaN is considered larger than +∞
881             if (isNaN()) {
882                 // for consistency with Double.compareTo, NaN is considered equal to itself
883                 return other.isNaN() ? 0 : 1;
884             } else if (other.isNaN()) {
885                 return -1;
886             } else {
887                 // instance is ±∞, other is either finite or ±∞ but not NaN
888                 // at infinity, seconds are set to either Long.MIN_VALUE or Long.MAX_VALUE
889                 return Long.compare(seconds, other.seconds);
890             }
891         }
892     }
893 
894     /** {@inheritDoc} */
895     @Override
896     public boolean equals(final Object o) {
897         if (this == o) {
898             return true;
899         }
900         if (o == null || o.getClass() != this.getClass()) {
901             return false;
902         }
903         final TimeOffset timeOffset = (TimeOffset) o;
904         return seconds == timeOffset.seconds && attoSeconds == timeOffset.attoSeconds;
905     }
906 
907     /** {@inheritDoc} */
908     @Override
909     public int hashCode() {
910         return Long.hashCode(seconds) ^ Long.hashCode(attoSeconds);
911     }
912 
913     /** Local class for summing several instances. */
914     private static class RunningSum {
915 
916         /** Number of terms that can be added before normalization is needed. */
917         private static final int COUNT_DOWN_MAX = 9;
918 
919         /** Seconds part. */
920         private long seconds;
921 
922         /** AttoSeconds part. */
923         private long attoSeconds;
924 
925         /** Indicator for NaN presence. */
926         private boolean addedNaN;
927 
928         /** Indicator for +∞ presence. */
929         private boolean addedPositiveInfinity;
930 
931         /** Indicator for -∞ presence. */
932         private boolean addedNegativeInfinity;
933 
934         /** Countdown for checking carry. */
935         private int countDown;
936 
937         /** Simple constructor.
938          */
939         RunningSum() {
940             countDown = COUNT_DOWN_MAX;
941         }
942 
943         /** Add one term.
944          * @param term term to add
945          */
946         public void add(final TimeOffset term) {
947             if (term.isFinite()) {
948                 // regular addition
949                 seconds     += term.seconds;
950                 attoSeconds += term.attoSeconds;
951                 if (--countDown == 0) {
952                     // we have added several terms, we should normalize
953                     // the fields before attoseconds overflow (it may overflow after 9 additions)
954                     normalize();
955                 }
956             } else if (term.isNegativeInfinity()) {
957                 addedNegativeInfinity = true;
958             } else if (term.isPositiveInfinity()) {
959                 addedPositiveInfinity = true;
960             } else {
961                 addedNaN = true;
962             }
963         }
964 
965         /** Normalize current running sum.
966          * @return normalized value
967          */
968         public TimeOffset normalize() {
969 
970             // after normalization, we will have the equivalent of one entry processed
971             countDown = COUNT_DOWN_MAX - 1;
972 
973             if (addedNaN || addedNegativeInfinity && addedPositiveInfinity) {
974                 // we have built a NaN
975                 seconds     = NaN.seconds;
976                 attoSeconds = NaN.attoSeconds;
977                 return NaN;
978             } else if (addedNegativeInfinity) {
979                 // we have built -∞
980                 seconds     = NEGATIVE_INFINITY.seconds;
981                 attoSeconds = NEGATIVE_INFINITY.attoSeconds;
982                 return NEGATIVE_INFINITY;
983             } else if (addedPositiveInfinity) {
984                 // we have built +∞
985                 seconds     = POSITIVE_INFINITY.seconds;
986                 attoSeconds = POSITIVE_INFINITY.attoSeconds;
987                 return POSITIVE_INFINITY;
988             } else {
989                 // this is a regular time
990                 final TimeOffset regular = new TimeOffset(seconds, attoSeconds);
991                 seconds     = regular.seconds;
992                 attoSeconds = regular.attoSeconds;
993                 return regular;
994             }
995         }
996 
997     }
998 
999 }