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