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