1 /* Copyright 2002-2018 CS Systèmes d'Information
2 * Licensed to CS Systèmes d'Information (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.Serializable;
20 import java.util.List;
21
22 import org.hipparchus.RealFieldElement;
23 import org.orekit.errors.OrekitException;
24 import org.orekit.errors.OrekitInternalError;
25 import org.orekit.utils.Constants;
26
27 /** Coordinated Universal Time.
28 * <p>UTC is related to TAI using step adjustments from time to time
29 * according to IERS (International Earth Rotation Service) rules. Before 1972,
30 * these adjustments were piecewise linear offsets. Since 1972, these adjustments
31 * are piecewise constant offsets, which require introduction of leap seconds.</p>
32 * <p>Leap seconds are always inserted as additional seconds at the last minute
33 * of the day, pushing the next day forward. Such minutes are therefore more
34 * than 60 seconds long. In theory, there may be seconds removal instead of seconds
35 * insertion, but up to now (2010) it has never been used. As an example, when a
36 * one second leap was introduced at the end of 2005, the UTC time sequence was
37 * 2005-12-31T23:59:59 UTC, followed by 2005-12-31T23:59:60 UTC, followed by
38 * 2006-01-01T00:00:00 UTC.</p>
39 * <p>This is intended to be accessed thanks to the {@link TimeScalesFactory} class,
40 * so there is no public constructor.</p>
41 * @author Luc Maisonobe
42 * @see AbsoluteDate
43 */
44 public class UTCScale implements TimeScale {
45
46 /** Serializable UID. */
47 private static final long serialVersionUID = 20150402L;
48
49 /** UTC-TAI offsets. */
50 private UTCTAIOffset[] offsets;
51
52 /** Package private constructor for the factory.
53 * Used to create the prototype instance of this class that is used to
54 * clone all subsequent instances of {@link UTCScale}. Initializes the offset
55 * table that is shared among all instances.
56 * @param offsetModels UTC-TAI offsets
57 * @exception OrekitException if cache cannot be set up
58 */
59 UTCScale(final List<OffsetModel> offsetModels) throws OrekitException {
60
61 if (offsetModels.get(0).getStart().getYear() > 1968) {
62 // the pre-1972 linear offsets are missing, add them manually
63 // excerpt from UTC-TAI.history file:
64 // 1961 Jan. 1 - 1961 Aug. 1 1.422 818 0s + (MJD - 37 300) x 0.001 296s
65 // Aug. 1 - 1962 Jan. 1 1.372 818 0s + ""
66 // 1962 Jan. 1 - 1963 Nov. 1 1.845 858 0s + (MJD - 37 665) x 0.001 123 2s
67 // 1963 Nov. 1 - 1964 Jan. 1 1.945 858 0s + ""
68 // 1964 Jan. 1 - April 1 3.240 130 0s + (MJD - 38 761) x 0.001 296s
69 // April 1 - Sept. 1 3.340 130 0s + ""
70 // Sept. 1 - 1965 Jan. 1 3.440 130 0s + ""
71 // 1965 Jan. 1 - March 1 3.540 130 0s + ""
72 // March 1 - Jul. 1 3.640 130 0s + ""
73 // Jul. 1 - Sept. 1 3.740 130 0s + ""
74 // Sept. 1 - 1966 Jan. 1 3.840 130 0s + ""
75 // 1966 Jan. 1 - 1968 Feb. 1 4.313 170 0s + (MJD - 39 126) x 0.002 592s
76 // 1968 Feb. 1 - 1972 Jan. 1 4.213 170 0s + ""
77 offsetModels.add( 0, new OffsetModel(new DateComponents(1961, 1, 1), 37300, 1.4228180, 0.0012960));
78 offsetModels.add( 1, new OffsetModel(new DateComponents(1961, 8, 1), 37300, 1.3728180, 0.0012960));
79 offsetModels.add( 2, new OffsetModel(new DateComponents(1962, 1, 1), 37665, 1.8458580, 0.0011232));
80 offsetModels.add( 3, new OffsetModel(new DateComponents(1963, 11, 1), 37665, 1.9458580, 0.0011232));
81 offsetModels.add( 4, new OffsetModel(new DateComponents(1964, 1, 1), 38761, 3.2401300, 0.0012960));
82 offsetModels.add( 5, new OffsetModel(new DateComponents(1964, 4, 1), 38761, 3.3401300, 0.0012960));
83 offsetModels.add( 6, new OffsetModel(new DateComponents(1964, 9, 1), 38761, 3.4401300, 0.0012960));
84 offsetModels.add( 7, new OffsetModel(new DateComponents(1965, 1, 1), 38761, 3.5401300, 0.0012960));
85 offsetModels.add( 8, new OffsetModel(new DateComponents(1965, 3, 1), 38761, 3.6401300, 0.0012960));
86 offsetModels.add( 9, new OffsetModel(new DateComponents(1965, 7, 1), 38761, 3.7401300, 0.0012960));
87 offsetModels.add(10, new OffsetModel(new DateComponents(1965, 9, 1), 38761, 3.8401300, 0.0012960));
88 offsetModels.add(11, new OffsetModel(new DateComponents(1966, 1, 1), 39126, 4.3131700, 0.0025920));
89 offsetModels.add(12, new OffsetModel(new DateComponents(1968, 2, 1), 39126, 4.2131700, 0.0025920));
90 }
91
92 // create cache
93 offsets = new UTCTAIOffset[offsetModels.size()];
94
95 UTCTAIOffset previous = null;
96
97 // link the offsets together
98 final TimeScale tai = TimeScalesFactory.getTAI();
99 for (int i = 0; i < offsetModels.size(); ++i) {
100
101 final OffsetModel o = offsetModels.get(i);
102 final DateComponents date = o.getStart();
103 final int mjdRef = o.getMJDRef();
104 final double offset = o.getOffset();
105 final double slope = o.getSlope();
106
107 // start of the leap
108 final double previousOffset = (previous == null) ? 0.0 : previous.getOffset(date, TimeComponents.H00);
109 final AbsoluteDate leapStart = new AbsoluteDate(date, tai).shiftedBy(previousOffset);
110
111 // end of the leap
112 final double startOffset = offset + slope * (date.getMJD() - mjdRef);
113 final AbsoluteDate leapEnd = new AbsoluteDate(date, tai).shiftedBy(startOffset);
114
115 // leap computed at leap start and in UTC scale
116 final double normalizedSlope = slope / Constants.JULIAN_DAY;
117 final double leap = leapEnd.durationFrom(leapStart) / (1 + normalizedSlope);
118
119 previous = new UTCTAIOffset(leapStart, date.getMJD(), leap, offset, mjdRef, normalizedSlope);
120 offsets[i] = previous;
121
122 }
123
124 }
125
126 /** {@inheritDoc} */
127 @Override
128 public double offsetFromTAI(final AbsoluteDate date) {
129 final int offsetIndex = findOffsetIndex(date);
130 if (offsetIndex < 0) {
131 // the date is before the first known leap
132 return 0;
133 } else {
134 return -offsets[offsetIndex].getOffset(date);
135 }
136 }
137
138 /** {@inheritDoc} */
139 @Override
140 public <T extends RealFieldElement<T>> T offsetFromTAI(final FieldAbsoluteDate<T> date) {
141 final int offsetIndex = findOffsetIndex(date.toAbsoluteDate());
142 if (offsetIndex < 0) {
143 // the date is before the first known leap
144 return date.getField().getZero();
145 } else {
146 return offsets[offsetIndex].getOffset(date).negate();
147 }
148 }
149
150 /** {@inheritDoc} */
151 @Override
152 public double offsetToTAI(final DateComponents date,
153 final TimeComponents time) {
154
155 // take offset from local time into account, but ignoring seconds,
156 // so when we parse an hour like 23:59:60.5 during leap seconds introduction,
157 // we do not jump to next day
158 final int minuteInDay = time.getHour() * 60 + time.getMinute() - time.getMinutesFromUTC();
159 final int correction = minuteInDay < 0 ? (minuteInDay - 1439) / 1440 : minuteInDay / 1440;
160
161 // find close neighbors, assuming date in TAI, i.e a date earlier than real UTC date
162 final int mjd = date.getMJD() + correction;
163 final UTCTAIOffset offset = findOffset(mjd);
164 if (offset == null) {
165 // the date is before the first known leap
166 return 0;
167 } else {
168 return offset.getOffset(date, time);
169 }
170
171 }
172
173 /** {@inheritDoc} */
174 public String getName() {
175 return "UTC";
176 }
177
178 /** {@inheritDoc} */
179 public String toString() {
180 return getName();
181 }
182
183 /** Get the date of the first known leap second.
184 * @return date of the first known leap second
185 */
186 public AbsoluteDate getFirstKnownLeapSecond() {
187 return offsets[0].getDate();
188 }
189
190 /** Get the date of the last known leap second.
191 * @return date of the last known leap second
192 */
193 public AbsoluteDate getLastKnownLeapSecond() {
194 return offsets[offsets.length - 1].getDate();
195 }
196
197 /** {@inheritDoc} */
198 @Override
199 public boolean insideLeap(final AbsoluteDate date) {
200 final int offsetIndex = findOffsetIndex(date);
201 if (offsetIndex < 0) {
202 // the date is before the first known leap
203 return false;
204 } else {
205 return date.compareTo(offsets[offsetIndex].getValidityStart()) < 0;
206 }
207 }
208
209 /** {@inheritDoc} */
210 @Override
211 public <T extends RealFieldElement<T>> boolean insideLeap(final FieldAbsoluteDate<T> date) {
212 return insideLeap(date.toAbsoluteDate());
213 }
214
215 /** {@inheritDoc} */
216 @Override
217 public int minuteDuration(final AbsoluteDate date) {
218 final int offsetIndex = findOffsetIndex(date);
219 if (offsetIndex < 0) {
220 // the date is before the first known leap
221 return 60;
222 } else {
223 if (date.compareTo(offsets[offsetIndex].getValidityStart()) < 0) {
224 // the date is during the leap itself
225 return 61;
226 } else {
227 // the date is after a leap, but it may be just before the next one
228 if (offsetIndex + 1 < offsets.length &&
229 offsets[offsetIndex + 1].getDate().durationFrom(date) <= 60.0) {
230 // the next leap will start in one minute, it will extend the current minute
231 return 61;
232 } else {
233 // no leap is expected within the next minute
234 return 60;
235 }
236 }
237 }
238 }
239
240 /** {@inheritDoc} */
241 @Override
242 public <T extends RealFieldElement<T>> int minuteDuration(final FieldAbsoluteDate<T> date) {
243 return minuteDuration(date.toAbsoluteDate());
244 }
245
246 /** {@inheritDoc} */
247 @Override
248 public double getLeap(final AbsoluteDate date) {
249 final int offsetIndex = findOffsetIndex(date);
250 if (offsetIndex < 0) {
251 // the date is before the first known leap
252 return 0;
253 } else {
254 return offsets[offsetIndex].getLeap();
255 }
256 }
257
258 /** {@inheritDoc} */
259 @Override
260 public <T extends RealFieldElement<T>> T getLeap(final FieldAbsoluteDate<T> date) {
261 return date.getField().getZero().add(getLeap(date.toAbsoluteDate()));
262 }
263
264 /** Find the index of the offset valid at some date.
265 * @param date date at which offset is requested
266 * @return index of the offset valid at this date, or -1 if date is before first offset.
267 */
268 private int findOffsetIndex(final AbsoluteDate date) {
269 int inf = 0;
270 int sup = offsets.length;
271 while (sup - inf > 1) {
272 final int middle = (inf + sup) >>> 1;
273 if (date.compareTo(offsets[middle].getDate()) < 0) {
274 sup = middle;
275 } else {
276 inf = middle;
277 }
278 }
279 if (sup == offsets.length) {
280 // the date is after the last known leap second
281 return offsets.length - 1;
282 } else if (date.compareTo(offsets[inf].getDate()) < 0) {
283 // the date is before the first known leap
284 return -1;
285 } else {
286 return inf;
287 }
288 }
289
290 /** Find the offset valid at some date.
291 * @param mjd Modified Julian Day of the date at which offset is requested
292 * @return offset valid at this date, or null if date is before first offset.
293 */
294 private UTCTAIOffset findOffset(final int mjd) {
295 int inf = 0;
296 int sup = offsets.length;
297 while (sup - inf > 1) {
298 final int middle = (inf + sup) >>> 1;
299 if (mjd < offsets[middle].getMJD()) {
300 sup = middle;
301 } else {
302 inf = middle;
303 }
304 }
305 if (sup == offsets.length) {
306 // the date is after the last known leap second
307 return offsets[offsets.length - 1];
308 } else if (mjd < offsets[inf].getMJD()) {
309 // the date is before the first known leap
310 return null;
311 } else {
312 return offsets[inf];
313 }
314 }
315
316 /** Replace the instance with a data transfer object for serialization.
317 * @return data transfer object that will be serialized
318 */
319 private Object writeReplace() {
320 return new DataTransferObject();
321 }
322
323 /** Internal class used only for serialization. */
324 private static class DataTransferObject implements Serializable {
325
326 /** Serializable UID. */
327 private static final long serialVersionUID = 20131209L;
328
329 /** Replace the deserialized data transfer object with a {@link UTCScale}.
330 * @return replacement {@link UTCScale}
331 */
332 private Object readResolve() {
333 try {
334 return TimeScalesFactory.getUTC();
335 } catch (OrekitException oe) {
336 throw new OrekitInternalError(oe);
337 }
338 }
339
340 }
341
342 }