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