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