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