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