1 /* Copyright 2002-2026 CS GROUP
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.time;
18
19 import java.io.Serial;
20 import java.io.Serializable;
21
22 import org.hipparchus.CalculusFieldElement;
23
24 /** Offset between {@link UTCScale UTC} and {@link TAIScale TAI} time scales.
25 * <p>The {@link UTCScale UTC} and {@link TAIScale TAI} time scales are two
26 * scales offset with respect to each other. The {@link TAIScale TAI} scale is
27 * continuous whereas the {@link UTCScale UTC} includes some discontinuity when
28 * leap seconds are introduced by the <a href="https://www.iers.org/">International
29 * Earth Rotation Service</a> (IERS).</p>
30 * <p>This class represents the offset between the two scales that is
31 * valid between two leap seconds occurrences. It handles both the linear offsets
32 * used from 1961-01-01 to 1971-12-31 and the constant integer offsets used since
33 * 1972-01-01.</p>
34 * @author Luc Maisonobe
35 * @see UTCScale
36 * @see UTCTAIHistoryFilesLoader
37 */
38 public class UTCTAIOffset implements TimeStamped, Serializable {
39
40 /** Serializable UID. */
41 @Serial
42 private static final long serialVersionUID = 20240720L;
43
44 /** Nanoseconds in one second. */
45 private static final int NANOS_IN_SECOND = 1000000000;
46
47 /** Leap date. */
48 private final AbsoluteDate leapDate;
49
50 /** Leap date in Modified Julian Day. */
51 private final int leapDateMJD;
52
53 /** Offset start of validity date. */
54 private final AbsoluteDate validityStart;
55
56 /** Reference date for the slope multiplication as Modified Julian Day. */
57 private final int mjdRef;
58
59 /** Reference date for the slope multiplication. */
60 private final AbsoluteDate reference;
61
62 /** Value of the leap at offset validity start (in seconds). */
63 private final TimeOffset leap;
64
65 /** Offset at validity start in seconds (TAI minus UTC). */
66 private final TimeOffset offset;
67
68 /** Offset slope in nanoseconds per UTC second (TAI minus UTC / dUTC). */
69 private final int slope;
70
71 /** Simple constructor for a linear model.
72 * @param leapDate leap date
73 * @param leapDateMJD leap date in Modified Julian Day
74 * @param leap value of the leap at offset validity start (in seconds)
75 * @param offset offset in seconds (TAI minus UTC)
76 * @param mjdRef reference date for the slope multiplication as Modified Julian Day
77 * @param slope offset slope in nanoseconds per UTC second (TAI minus UTC / dUTC)
78 * @param reference date for slope computations.
79 */
80 UTCTAIOffset(final AbsoluteDate leapDate, final int leapDateMJD,
81 final TimeOffset leap, final TimeOffset offset,
82 final int mjdRef, final int slope, final AbsoluteDate reference) {
83 this.leapDate = leapDate;
84 this.leapDateMJD = leapDateMJD;
85 this.validityStart = leapDate.shiftedBy(leap);
86 this.mjdRef = mjdRef;
87 this.reference = reference;
88 this.leap = leap;
89 this.offset = offset;
90
91 // at some absolute instant t₀, we can associate reading a₀ on a TAI clock and u₀ on a UTC clock
92 // at this instant, the offset between TAI and UTC is therefore τ₀ = a₀ - u₀
93 // at another absolute instant t₁, we can associate reading a₁ on a TAI clock and u₁ on a UTC clock
94 // at this instant, the offset between TAI and UTC is therefore τ₁ = a₁ - u₁
95 // the slope is defined according to offsets counted in UTC, i.e.:
96 // τ₁ = τ₀ + (u₁ - u₀) * slope/n (where n = 10⁹ because the slope is in ns/s)
97 // if we have a₁ - a₀ (i.e. dates in TAI) instead of u₁ - u₀, we need to invert the expression
98 // we get: τ₁ = τ₀ + (a₁ - a₀) * slope / (n + slope)
99 this.slope = slope;
100
101 }
102
103 /** Get the date of the start of the leap.
104 * @return date of the start of the leap
105 * @see #getValidityStart()
106 */
107 public AbsoluteDate getDate() {
108 return leapDate;
109 }
110
111 /** Get the date of the start of the leap as Modified Julian Day.
112 * @return date of the start of the leap as Modified Julian Day
113 */
114 public int getMJD() {
115 return leapDateMJD;
116 }
117
118 /** Get the start time of validity for this offset.
119 * <p>The start of the validity of the offset is {@link #getLeap()}
120 * seconds after the start of the leap itself.</p>
121 * @return start of validity date
122 * @see #getDate()
123 */
124 public AbsoluteDate getValidityStart() {
125 return validityStart;
126 }
127
128 /** Get the value of the leap at offset validity start.
129 * @return value of the leap at offset validity start
130 */
131 public TimeOffset getLeap() {
132 return leap;
133 }
134
135 /** Get the TAI - UTC offset in seconds.
136 * @param date date at which the offset is requested
137 * @return TAI - UTC offset in seconds.
138 */
139 public TimeOffset getOffset(final AbsoluteDate date) {
140 if (slope == 0) {
141 // we use an if statement here so the offset computation returns
142 // a finite value when date is AbsoluteDate.FUTURE_INFINITY
143 // without this if statement, the multiplication between an
144 // infinite duration and a zero slope would induce a NaN offset
145 return offset;
146 } else {
147
148 // time during which slope applies
149 final TimeOffset delta = date.accurateDurationFrom(reference);
150
151 // accumulated drift
152 final TimeOffset drift = delta.multiply(slope).divide(slope + NANOS_IN_SECOND);
153
154 return offset.add(drift);
155
156 }
157 }
158
159 /** Get the TAI - UTC offset in seconds.
160 * @param date date at which the offset is requested
161 * @param <T> type of the filed elements
162 * @return TAI - UTC offset in seconds.
163 * @since 9.0
164 */
165 public <T extends CalculusFieldElement<T>> T getOffset(final FieldAbsoluteDate<T> date) {
166 if (slope == 0) {
167 // we use an if statement here so the offset computation returns
168 // a finite value when date is FieldAbsoluteDate.getFutureInfinity(field)
169 // without this if statement, the multiplication between an
170 // infinite duration and a zero slope would induce a NaN offset
171 return date.getField().getZero().newInstance(offset.toDouble());
172 } else {
173 // TODO perform complete computation
174 return date.getField().getZero().newInstance(getOffset(date.toAbsoluteDate()).toDouble());
175 }
176 }
177
178 /** Get the TAI - UTC offset in seconds.
179 * @param date date components (in UTC) at which the offset is requested
180 * @param time time components (in UTC) at which the offset is requested
181 * @return TAI - UTC offset in seconds.
182 */
183 public TimeOffset getOffset(final DateComponents date, final TimeComponents time) {
184 if (slope == 0) {
185 return offset;
186 } else {
187
188 // time during which slope applies
189 final TimeOffset delta = new TimeOffset((date.getMJD() - mjdRef) * TimeOffset.DAY.getSeconds() +
190 time.getHour() * TimeOffset.HOUR.getSeconds() +
191 time.getMinute() * TimeOffset.MINUTE.getSeconds() +
192 time.getSplitSecond().getSeconds(),
193 time.getSplitSecond().getAttoSeconds());
194
195 // accumulated drift
196 final TimeOffset drift = delta.multiply(slope).divide(NANOS_IN_SECOND);
197
198 return offset.add(drift);
199
200 }
201 }
202
203 }