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