1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.time;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Comparator;
23 import java.util.List;
24
25 import org.hipparchus.CalculusFieldElement;
26 import org.hipparchus.util.FastMath;
27 import org.orekit.annotation.DefaultDataContext;
28 import org.orekit.errors.OrekitException;
29 import org.orekit.errors.OrekitInternalError;
30 import org.orekit.utils.Constants;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class UTCScale implements TimeScale {
50
51
52 private static final long serialVersionUID = 20230302L;
53
54
55 private final TimeScale tai;
56
57
58 private final Collection<? extends OffsetModel> baseOffsets;
59
60
61 private final UTCTAIOffset[] offsets;
62
63
64
65
66
67
68
69
70 UTCScale(final TimeScale tai, final Collection<? extends OffsetModel> baseOffsets) {
71
72 this.tai = tai;
73 this.baseOffsets = baseOffsets;
74
75
76 final List<OffsetModel> offsetModels = new ArrayList<>(baseOffsets);
77 offsetModels.sort(Comparator.comparing(OffsetModel::getStart));
78 if (offsetModels.get(0).getStart().getYear() > 1968) {
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 offsetModels.add( 0, new OffsetModel(new DateComponents(1961, 1, 1), 37300, 1.4228180, 0.0012960));
95 offsetModels.add( 1, new OffsetModel(new DateComponents(1961, 8, 1), 37300, 1.3728180, 0.0012960));
96 offsetModels.add( 2, new OffsetModel(new DateComponents(1962, 1, 1), 37665, 1.8458580, 0.0011232));
97 offsetModels.add( 3, new OffsetModel(new DateComponents(1963, 11, 1), 37665, 1.9458580, 0.0011232));
98 offsetModels.add( 4, new OffsetModel(new DateComponents(1964, 1, 1), 38761, 3.2401300, 0.0012960));
99 offsetModels.add( 5, new OffsetModel(new DateComponents(1964, 4, 1), 38761, 3.3401300, 0.0012960));
100 offsetModels.add( 6, new OffsetModel(new DateComponents(1964, 9, 1), 38761, 3.4401300, 0.0012960));
101 offsetModels.add( 7, new OffsetModel(new DateComponents(1965, 1, 1), 38761, 3.5401300, 0.0012960));
102 offsetModels.add( 8, new OffsetModel(new DateComponents(1965, 3, 1), 38761, 3.6401300, 0.0012960));
103 offsetModels.add( 9, new OffsetModel(new DateComponents(1965, 7, 1), 38761, 3.7401300, 0.0012960));
104 offsetModels.add(10, new OffsetModel(new DateComponents(1965, 9, 1), 38761, 3.8401300, 0.0012960));
105 offsetModels.add(11, new OffsetModel(new DateComponents(1966, 1, 1), 39126, 4.3131700, 0.0025920));
106 offsetModels.add(12, new OffsetModel(new DateComponents(1968, 2, 1), 39126, 4.2131700, 0.0025920));
107 }
108
109
110 this.offsets = new UTCTAIOffset[offsetModels.size()];
111
112 UTCTAIOffset previous = null;
113
114
115 for (int i = 0; i < offsetModels.size(); ++i) {
116
117 final OffsetModel o = offsetModels.get(i);
118 final DateComponents date = o.getStart();
119 final int mjdRef = o.getMJDRef();
120 final double offset = o.getOffset();
121 final double slope = o.getSlope();
122
123
124 final double previousOffset = (previous == null) ? 0.0 : previous.getOffset(date, TimeComponents.H00);
125 final AbsoluteDate leapStart = new AbsoluteDate(date, tai).shiftedBy(previousOffset);
126
127
128 final double startOffset = offset + slope * (date.getMJD() - mjdRef);
129 final AbsoluteDate leapEnd = new AbsoluteDate(date, tai).shiftedBy(startOffset);
130
131
132 final double normalizedSlope = slope / Constants.JULIAN_DAY;
133 final double leap = leapEnd.durationFrom(leapStart) / (1 + normalizedSlope);
134
135 final AbsoluteDate reference = AbsoluteDate.createMJDDate(mjdRef, 0, tai)
136 .shiftedBy(offset);
137 previous = new UTCTAIOffset(leapStart, date.getMJD(), leap, offset, mjdRef,
138 normalizedSlope, reference);
139 this.offsets[i] = previous;
140
141 }
142
143 }
144
145
146
147
148
149 public Collection<? extends OffsetModel> getBaseOffsets() {
150 return baseOffsets;
151 }
152
153
154
155
156
157
158
159
160 public List<UTCTAIOffset> getUTCTAIOffsets() {
161 final List<UTCTAIOffset> offsetList = new ArrayList<>(offsets.length);
162 for (int i = 0; i < offsets.length; ++i) {
163 offsetList.add(offsets[i]);
164 }
165 return offsetList;
166 }
167
168
169 @Override
170 public double offsetFromTAI(final AbsoluteDate date) {
171 final int offsetIndex = findOffsetIndex(date);
172 if (offsetIndex < 0) {
173
174 return 0;
175 } else {
176 return -offsets[offsetIndex].getOffset(date);
177 }
178 }
179
180
181 @Override
182 public <T extends CalculusFieldElement<T>> T offsetFromTAI(final FieldAbsoluteDate<T> date) {
183 final int offsetIndex = findOffsetIndex(date.toAbsoluteDate());
184 if (offsetIndex < 0) {
185
186 return date.getField().getZero();
187 } else {
188 return offsets[offsetIndex].getOffset(date).negate();
189 }
190 }
191
192
193 @Override
194 public double offsetToTAI(final DateComponents date,
195 final TimeComponents time) {
196
197
198
199
200 final int minuteInDay = time.getHour() * 60 + time.getMinute() - time.getMinutesFromUTC();
201 final int correction = minuteInDay < 0 ? (minuteInDay - 1439) / 1440 : minuteInDay / 1440;
202
203
204 final int mjd = date.getMJD() + correction;
205 final UTCTAIOffset offset = findOffset(mjd);
206 if (offset == null) {
207
208 return 0;
209 } else {
210 return offset.getOffset(date, time);
211 }
212
213 }
214
215
216 public String getName() {
217 return "UTC";
218 }
219
220
221 public String toString() {
222 return getName();
223 }
224
225
226
227
228 public AbsoluteDate getFirstKnownLeapSecond() {
229 return offsets[0].getDate();
230 }
231
232
233
234
235 public AbsoluteDate getLastKnownLeapSecond() {
236 return offsets[offsets.length - 1].getDate();
237 }
238
239
240 @Override
241 public boolean insideLeap(final AbsoluteDate date) {
242 final int offsetIndex = findOffsetIndex(date);
243 if (offsetIndex < 0) {
244
245 return false;
246 } else {
247 return date.compareTo(offsets[offsetIndex].getValidityStart()) < 0;
248 }
249 }
250
251
252 @Override
253 public <T extends CalculusFieldElement<T>> boolean insideLeap(final FieldAbsoluteDate<T> date) {
254 return insideLeap(date.toAbsoluteDate());
255 }
256
257
258 @Override
259 public int minuteDuration(final AbsoluteDate date) {
260 final int offsetIndex = findOffsetIndex(date);
261 final UTCTAIOffset offset;
262 if (offsetIndex >= 0 &&
263 date.compareTo(offsets[offsetIndex].getValidityStart()) < 0) {
264
265 offset = offsets[offsetIndex];
266 } else if (offsetIndex + 1 < offsets.length &&
267 offsets[offsetIndex + 1].getDate().durationFrom(date) <= 60.0) {
268
269
270 offset = offsets[offsetIndex + 1];
271 } else {
272 offset = null;
273 }
274 if (offset != null) {
275
276
277 return 60 + (int) FastMath.ceil(offset.getLeap());
278 }
279
280 return 60;
281 }
282
283
284 @Override
285 public <T extends CalculusFieldElement<T>> int minuteDuration(final FieldAbsoluteDate<T> date) {
286 return minuteDuration(date.toAbsoluteDate());
287 }
288
289
290 @Override
291 public double getLeap(final AbsoluteDate date) {
292 final int offsetIndex = findOffsetIndex(date);
293 if (offsetIndex < 0) {
294
295 return 0;
296 } else {
297 return offsets[offsetIndex].getLeap();
298 }
299 }
300
301
302 @Override
303 public <T extends CalculusFieldElement<T>> T getLeap(final FieldAbsoluteDate<T> date) {
304 return date.getField().getZero().newInstance(getLeap(date.toAbsoluteDate()));
305 }
306
307
308
309
310
311 private int findOffsetIndex(final AbsoluteDate date) {
312 int inf = 0;
313 int sup = offsets.length;
314 while (sup - inf > 1) {
315 final int middle = (inf + sup) >>> 1;
316 if (date.compareTo(offsets[middle].getDate()) < 0) {
317 sup = middle;
318 } else {
319 inf = middle;
320 }
321 }
322 if (sup == offsets.length) {
323
324 return offsets.length - 1;
325 } else if (date.compareTo(offsets[inf].getDate()) < 0) {
326
327 return -1;
328 } else {
329 return inf;
330 }
331 }
332
333
334
335
336
337 private UTCTAIOffset findOffset(final int mjd) {
338 int inf = 0;
339 int sup = offsets.length;
340 while (sup - inf > 1) {
341 final int middle = (inf + sup) >>> 1;
342 if (mjd < offsets[middle].getMJD()) {
343 sup = middle;
344 } else {
345 inf = middle;
346 }
347 }
348 if (sup == offsets.length) {
349
350 return offsets[offsets.length - 1];
351 } else if (mjd < offsets[inf].getMJD()) {
352
353 return null;
354 } else {
355 return offsets[inf];
356 }
357 }
358
359
360
361
362 @DefaultDataContext
363 private Object writeReplace() {
364 return new DataTransferObject(tai, baseOffsets);
365 }
366
367
368 @DefaultDataContext
369 private static class DataTransferObject implements Serializable {
370
371
372 private static final long serialVersionUID = 20230302L;
373
374
375 private final TimeScale tai;
376
377
378 private final Collection<? extends OffsetModel> baseOffsets;
379
380
381
382
383
384 DataTransferObject(final TimeScale tai, final Collection<? extends OffsetModel> baseOffsets) {
385 this.tai = tai;
386 this.baseOffsets = baseOffsets;
387 }
388
389
390
391
392 private Object readResolve() {
393 try {
394 return new UTCScale(tai, baseOffsets);
395 } catch (OrekitException oe) {
396 throw new OrekitInternalError(oe);
397 }
398 }
399
400 }
401
402 }