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, false);
171 
172     /** Formatter for attoseconds.
173      * @since 13.0.3
174      */
175     private static final FastLongFormatter ATTOSECONDS_FORMATTER = new FastLongFormatter(18, true, 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         return switch (unit) {
657             case DAYS ->
658                 sign * ((sign * seconds + DAY.seconds / 2) / DAY.seconds);
659             case HOURS ->
660                 sign * ((sign * seconds + HOUR.seconds / 2) / HOUR.seconds);
661             case MINUTES ->
662                 sign * ((sign * seconds + MINUTE.seconds / 2) / MINUTE.seconds);
663             case SECONDS ->
664                 seconds + ((attoSeconds >= ATTOS_IN_SECOND / 2) ? 1 : 0);
665             case MILLISECONDS ->
666                 seconds * MILLIS_IN_SECOND + (attoSeconds + MILLISECOND.attoSeconds / 2) / MILLISECOND.attoSeconds;
667             case MICROSECONDS ->
668                 seconds * MICROS_IN_SECOND + (attoSeconds + MICROSECOND.attoSeconds / 2) / MICROSECOND.attoSeconds;
669             case NANOSECONDS ->
670                 seconds * NANOS_IN_SECOND + (attoSeconds + NANOSECOND.attoSeconds / 2) / NANOSECOND.attoSeconds;
671         };
672     }
673 
674     /** Round to specified accuracy.
675      * <p>
676      * For simplicity of implementation, the tiebreaking rule applied here is to round half
677      * towards positive infinity. This implies that rounding to 3 fraction digits an
678      * offset of exactly 2.0025s implies adding 0.0005s so the rounded value becomes 2.003s, whereas
679      * rounding to 3 fraction digits an offset of exactly -2.0025s also implies adding 0.0005s
680      * so the rounded value becomes -2.002s.
681      * </p>
682      * @param fractionDigits the number of decimal digits after the decimal point in the seconds number
683      * @return rounded time offset
684      * @since 13.0.3
685      */
686     public TimeOffset getRoundedOffset(final int fractionDigits) {
687 
688         // handle special cases
689         if (attoSeconds < 0) {
690             // gather all special cases in one big check to avoid rare multiple tests
691             return ZERO;
692         }
693 
694         final long scaling = SCALING[FastMath.min(18, FastMath.max(0, 18 - fractionDigits))];
695         return new TimeOffset(seconds, ((attoSeconds + scaling / 2) / scaling) * scaling);
696 
697     }
698 
699     /** Get the normalized seconds part of the time.
700      * @return normalized seconds part of the time (may be negative)
701      */
702     public long getSeconds() {
703         return seconds;
704     }
705 
706     /** Get the normalized attoseconds part of the time.
707      * <p>
708      * The normalized attoseconds is always between {@code 0L} and
709      * {@code 1000000000000000000L} for <em>finite</em> ranges. Note that it
710      * may reach {@code 1000000000000000000L} if for example the time is less
711      * than 1 attosecond <em>before</em> a whole second. It is negative
712      * for {@link #isNaN() NaN} or {@link #isInfinite() infinite} times.
713      * </p>
714      * @return normalized attoseconds part of the time
715      */
716     public long getAttoSeconds() {
717         return attoSeconds;
718     }
719 
720     /** Get the time collapsed into a single double.
721      * <p>
722      * Beware that lots of accuracy is lost when combining {@link #getSeconds()} and {@link #getAttoSeconds()}
723      * into a single double.
724      * </p>
725      * @return time as a single double
726      */
727     public double toDouble() {
728         if (isFinite()) {
729             // regular value
730             long closeSeconds      = seconds;
731             long signedAttoSeconds = attoSeconds;
732             if (attoSeconds > ATTOS_IN_HALF_SECOND) {
733                 // we are closer to next second than to previous one
734                 // take this into account in the computation
735                 // in order to avoid losing precision
736                 closeSeconds++;
737                 signedAttoSeconds -= ATTOS_IN_SECOND;
738             }
739             return closeSeconds + ((double) signedAttoSeconds) / ATTOS_IN_SECOND;
740         } else {
741             // special values
742             return isNaN() ? Double.NaN : FastMath.copySign(Double.POSITIVE_INFINITY, seconds);
743         }
744     }
745 
746     /** Parse a string to produce an accurate split time.
747      * <p>
748      * This method is more accurate than parsing the string as a double and then
749      * calling {@link TimeOffset#TimeOffset(double)} because it reads the before
750      * separator and after separator parts in decimal, hence avoiding problems like
751      * for example 0.1 not being an exact IEEE754 number.
752      * </p>
753      * @param s string to parse
754      * @return parsed split time
755      */
756     public static TimeOffset parse(final String s) {
757 
758         // decompose the string
759         // we use neither Long.parseLong nor Integer.parseInt because we want to avoid
760         // performing several loops over the characters as we need to keep track of
761         // delimiters decimal point and exponent marker positions
762         final int length = s.length();
763         long significandSign = 1L;
764         int  exponentSign    = 1;
765         int  separatorIndex  = length;
766         int  exponentIndex   = length;
767         long beforeSeparator = 0L;
768         long afterSeparator  = 0L;
769         int  exponent        = 0;
770         int  digitsBefore    = 0;
771         int  digitsAfter     = 0;
772         int  digitsExponent  = 0;
773         int index = 0;
774         while (index < length) {
775 
776             // current character
777             final char c = s.charAt(index);
778 
779             if (Character.isDigit(c)) {
780                 if (separatorIndex == length) {
781                     // we are parsing the part before separator
782                     ++digitsBefore;
783                     beforeSeparator = beforeSeparator * 10 + c - '0';
784                     if (digitsBefore > 19 || beforeSeparator < 0) {
785                         // overflow occurred
786                         break;
787                     }
788                 } else if (exponentIndex == length) {
789                     // we are parsing the part between separator and exponent
790                     if (digitsAfter < DIGITS_ATTOS) {
791                         // we never overflow here, we just ignore extra digits
792                         afterSeparator = afterSeparator * 10 + c - '0';
793                         ++digitsAfter;
794                     }
795                 } else {
796                     // we are parsing the exponent
797                     ++digitsExponent;
798                     exponent = exponent * 10 + c - '0';
799                     if (digitsExponent > 10 || exponent < 0) {
800                         // overflow occurred
801                         break;
802                     }
803                 }
804             } else if (c == '.' && separatorIndex == length) {
805                 separatorIndex = index;
806             } else if ((c == 'e' || c == 'E') && exponentIndex == length) {
807                 if (separatorIndex == length) {
808                     separatorIndex = index;
809                 }
810                 exponentIndex = index;
811             } else if (c == '-') {
812                 if (index == 0) {
813                     significandSign = -1L;
814                 } else if (index == exponentIndex + 1) {
815                     exponentSign = -1;
816                 } else {
817                     break;
818                 }
819             } else if (c == '+') {
820                 if (index == 0) {
821                     significandSign = 1L;
822                 } else if (index == exponentIndex + 1) {
823                     exponentSign = 1;
824                 } else {
825                     break;
826                 }
827             } else {
828                 break;
829             }
830 
831             ++index;
832 
833         }
834 
835         if (length == 0 || index < length) {
836             // decomposition failed, either it is a special case or an unparsable string
837             if (s.equals(NEGATIVE_INTINITY_STRING)) {
838                 return TimeOffset.NEGATIVE_INFINITY;
839             } else if (s.equals(POSITIVE_INFINITY_STRING)) {
840                 return TimeOffset.POSITIVE_INFINITY;
841             } else if (s.equalsIgnoreCase(NAN_STRING)) {
842                 return TimeOffset.NaN;
843             } else {
844                 throw new OrekitException(OrekitMessages.CANNOT_PARSE_DATA, s);
845             }
846         }
847 
848         // decomposition was successful, build the split time
849         long seconds;
850         long attoseconds;
851         if (exponentSign < 0) {
852             // the part before separator must be split into seconds and attoseconds
853             if (exponent >= SCALING.length) {
854                 seconds = 0L;
855                 if (exponent - DIGITS_ATTOS >= SCALING.length) {
856                     // underflow
857                     attoseconds = 0L;
858                 } else {
859                     attoseconds = beforeSeparator / SCALING[exponent - DIGITS_ATTOS];
860                 }
861             } else {
862                 final long secondsMultiplier    = SCALING[exponent];
863                 final long attoBeforeMultiplier = SCALING[DIGITS_ATTOS - exponent];
864                 seconds     = beforeSeparator / secondsMultiplier;
865                 attoseconds = (beforeSeparator - seconds * secondsMultiplier) * attoBeforeMultiplier;
866                 while (digitsAfter + exponent > DIGITS_ATTOS) {
867                     // drop least significant digits below one attosecond
868                     afterSeparator /= 10;
869                     digitsAfter--;
870                 }
871                 final long attoAfterMultiplier = SCALING[DIGITS_ATTOS - exponent - digitsAfter];
872                 attoseconds += afterSeparator * attoAfterMultiplier;
873             }
874         } else {
875             // the part after separator must be split into seconds and attoseconds
876             if (exponent >= SCALING.length) {
877                 if (beforeSeparator == 0L && afterSeparator == 0L) {
878                     return TimeOffset.ZERO;
879                 } else if (significandSign < 0) {
880                     return TimeOffset.NEGATIVE_INFINITY;
881                 } else {
882                     return TimeOffset.POSITIVE_INFINITY;
883                 }
884             } else {
885                 final long secondsMultiplier = SCALING[exponent];
886                 seconds = beforeSeparator * secondsMultiplier;
887                 if (exponent > digitsAfter) {
888                     seconds += afterSeparator * SCALING[exponent - digitsAfter];
889                     attoseconds = 0L;
890                 } else {
891                     final long q = afterSeparator / SCALING[digitsAfter - exponent];
892                     seconds    += q;
893                     attoseconds = (afterSeparator - q * SCALING[digitsAfter - exponent]) *
894                                   SCALING[DIGITS_ATTOS - digitsAfter + exponent];
895                 }
896             }
897         }
898 
899         return new TimeOffset(significandSign * seconds, significandSign * attoseconds);
900 
901     }
902 
903     /** Compare the instance with another one.
904      * <p>
905      * Not that in order to be consistent with {@code Double#compareTo(Double)},
906      * NaN is considered equal to itself and greater than positive infinity.
907      * </p>
908      * @param other other time to compare the instance to
909      * @return a negative integer, zero, or a positive integer if applying this time
910      * to reference date would result in a date being before, simultaneous, or after
911      * the date obtained by applying the other time to the same reference date.
912      */
913     public int compareTo(final TimeOffset other) {
914         if (isFinite()) {
915             if (other.isFinite()) {
916                 return seconds == other.seconds ?
917                        Long.compare(attoSeconds, other.attoSeconds) :
918                        Long.compare(seconds, other.seconds);
919             } else {
920                 // if other is ±∞ or NaN, and NaN is considered larger than +∞
921                 return other.isNegativeInfinity() ? 1 : -1;
922             }
923         } else {
924             // instance is ±∞ or NaN, and NaN is considered larger than +∞
925             if (isNaN()) {
926                 // for consistency with Double.compareTo, NaN is considered equal to itself
927                 return other.isNaN() ? 0 : 1;
928             } else if (other.isNaN()) {
929                 return -1;
930             } else {
931                 // instance is ±∞, other is either finite or ±∞ but not NaN
932                 // at infinity, seconds are set to either Long.MIN_VALUE or Long.MAX_VALUE
933                 return Long.compare(seconds, other.seconds);
934             }
935         }
936     }
937 
938     /** {@inheritDoc} */
939     @Override
940     public boolean equals(final Object o) {
941         if (this == o) {
942             return true;
943         }
944         if (o == null || o.getClass() != this.getClass()) {
945             return false;
946         }
947         final TimeOffset timeOffset = (TimeOffset) o;
948         return seconds == timeOffset.seconds && attoSeconds == timeOffset.attoSeconds;
949     }
950 
951     /** {@inheritDoc} */
952     @Override
953     public int hashCode() {
954         return Long.hashCode(seconds) ^ Long.hashCode(attoSeconds);
955     }
956 
957     /** {@inheritDoc} */
958     @Override
959     public String toString() {
960         try {
961             if (attoSeconds < 0) {
962                 // gather all special cases in one big check to avoid rare multiple tests
963                 if (isNaN()) {
964                     return NAN_STRING;
965                 } else if (isPositiveInfinity()) {
966                     return POSITIVE_INFINITY_STRING;
967                 } else {
968                     return NEGATIVE_INTINITY_STRING;
969                 }
970             } else {
971                 final StringBuilder builder = new StringBuilder();
972                 final TimeOffset abs;
973                 if (seconds < 0L) {
974                     builder.append('-');
975                     abs = negate();
976                 } else {
977                     abs = this;
978                 }
979                 SECONDS_FORMATTER.appendTo(builder, abs.seconds);
980                 builder.append('.');
981                 ATTOSECONDS_FORMATTER.appendTo(builder, abs.attoSeconds);
982                 return builder.toString();
983             }
984         } catch (IOException ioe) {
985             // this should never happen
986             throw new OrekitInternalError(ioe);
987         }
988     }
989 
990     /** Local class for summing several instances. */
991     private static class RunningSum {
992 
993         /** Number of terms that can be added before normalization is needed. */
994         private static final int COUNT_DOWN_MAX = 9;
995 
996         /** Seconds part. */
997         private long seconds;
998 
999         /** AttoSeconds part. */
1000         private long attoSeconds;
1001 
1002         /** Indicator for NaN presence. */
1003         private boolean addedNaN;
1004 
1005         /** Indicator for +∞ presence. */
1006         private boolean addedPositiveInfinity;
1007 
1008         /** Indicator for -∞ presence. */
1009         private boolean addedNegativeInfinity;
1010 
1011         /** Countdown for checking carry. */
1012         private int countDown;
1013 
1014         /** Simple constructor.
1015          */
1016         RunningSum() {
1017             countDown = COUNT_DOWN_MAX;
1018         }
1019 
1020         /** Add one term.
1021          * @param term term to add
1022          */
1023         public void add(final TimeOffset term) {
1024             if (term.isFinite()) {
1025                 // regular addition
1026                 seconds     += term.seconds;
1027                 attoSeconds += term.attoSeconds;
1028                 if (--countDown == 0) {
1029                     // we have added several terms, we should normalize
1030                     // the fields before attoseconds overflow (it may overflow after 9 additions)
1031                     normalize();
1032                 }
1033             } else if (term.isNegativeInfinity()) {
1034                 addedNegativeInfinity = true;
1035             } else if (term.isPositiveInfinity()) {
1036                 addedPositiveInfinity = true;
1037             } else {
1038                 addedNaN = true;
1039             }
1040         }
1041 
1042         /** Normalize current running sum.
1043          * @return normalized value
1044          */
1045         public TimeOffset normalize() {
1046 
1047             // after normalization, we will have the equivalent of one entry processed
1048             countDown = COUNT_DOWN_MAX - 1;
1049 
1050             if (addedNaN || addedNegativeInfinity && addedPositiveInfinity) {
1051                 // we have built a NaN
1052                 seconds     = NaN.seconds;
1053                 attoSeconds = NaN.attoSeconds;
1054                 return NaN;
1055             } else if (addedNegativeInfinity) {
1056                 // we have built -∞
1057                 seconds     = NEGATIVE_INFINITY.seconds;
1058                 attoSeconds = NEGATIVE_INFINITY.attoSeconds;
1059                 return NEGATIVE_INFINITY;
1060             } else if (addedPositiveInfinity) {
1061                 // we have built +∞
1062                 seconds     = POSITIVE_INFINITY.seconds;
1063                 attoSeconds = POSITIVE_INFINITY.attoSeconds;
1064                 return POSITIVE_INFINITY;
1065             } else {
1066                 // this is a regular time
1067                 final TimeOffset regular = new TimeOffset(seconds, attoSeconds);
1068                 seconds     = regular.seconds;
1069                 attoSeconds = regular.attoSeconds;
1070                 return regular;
1071             }
1072         }
1073 
1074     }
1075 
1076 }