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 }