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 }