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 }