UTCScale.java

  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. import java.io.Serializable;
  19. import java.util.ArrayList;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.SortedMap;

  23. import org.orekit.errors.OrekitException;
  24. import org.orekit.errors.TimeStampedCacheException;
  25. import org.orekit.utils.Constants;
  26. import org.orekit.utils.ImmutableTimeStampedCache;
  27. import org.orekit.utils.TimeStampedCache;

  28. /** Coordinated Universal Time.
  29.  * <p>UTC is related to TAI using step adjustments from time to time
  30.  * according to IERS (International Earth Rotation Service) rules. Before 1972,
  31.  * these adjustments were piecewise linear offsets. Since 1972, these adjustments
  32.  * are piecewise constant offsets, which require introduction of leap seconds.</p>
  33.  * <p>Leap seconds are always inserted as additional seconds at the last minute
  34.  * of the day, pushing the next day forward. Such minutes are therefore more
  35.  * than 60 seconds long. In theory, there may be seconds removal instead of seconds
  36.  * insertion, but up to now (2010) it has never been used. As an example, when a
  37.  * one second leap was introduced at the end of 2005, the UTC time sequence was
  38.  * 2005-12-31T23:59:59 UTC, followed by 2005-12-31T23:59:60 UTC, followed by
  39.  * 2006-01-01T00:00:00 UTC.</p>
  40.  * <p>The OREKIT library retrieves the post-1972 constant time steps data thanks
  41.  * to the {@link org.orekit.data.DataProvidersManager DataProvidersManager} class.
  42.  * The linear models used between 1961 and 1972 are built-in in the class itself.</p>
  43.  * <p>This is intended to be accessed thanks to the {@link TimeScalesFactory} class,
  44.  * so there is no public constructor. Every call to {@link TimeScalesFactory#getUTC()}
  45.  * will create a new {@link UTCScale} instance, sharing the UTC-TAI offset table between
  46.  * all instances.</p>
  47.  * @author Luc Maisonobe
  48.  * @see AbsoluteDate
  49.  */
  50. public class UTCScale implements TimeScale {

  51.     /** Serializable UID. */
  52.     private static final long serialVersionUID = 20131209L;

  53.     /** Time steps. */
  54.     private transient TimeStampedCache<UTCTAIOffset> cache;

  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 entries user supplied entries
  60.      * @exception OrekitException if cache cannot be set up
  61.      */
  62.     UTCScale(final SortedMap<DateComponents, Integer> entries) throws OrekitException {
  63.         // create cache
  64.         final List<UTCTAIOffset> data = new Generator(entries).getOffsets();
  65.         cache = new ImmutableTimeStampedCache<UTCTAIOffset>(2, data);
  66.     }

  67.     /** Generator for leap seconds entries. */
  68.     private static class Generator {

  69.         /** List of {@link UTCTAIOffset} entries. */
  70.         private final List<UTCTAIOffset> offsets;

  71.         /** Simple constructor.
  72.          * @param entries user supplied entries
  73.          */
  74.         public Generator(final SortedMap<DateComponents, Integer> entries) {

  75.             offsets = new ArrayList<UTCTAIOffset>();

  76.             // set up the linear offsets used between 1961-01-01 and 1971-12-31
  77.             // excerpt from UTC-TAI.history file:
  78.             //  1961  Jan.  1 - 1961  Aug.  1     1.422 818 0s + (MJD - 37 300) x 0.001 296s
  79.             //        Aug.  1 - 1962  Jan.  1     1.372 818 0s +        ""
  80.             //  1962  Jan.  1 - 1963  Nov.  1     1.845 858 0s + (MJD - 37 665) x 0.001 123 2s
  81.             //  1963  Nov.  1 - 1964  Jan.  1     1.945 858 0s +        ""
  82.             //  1964  Jan.  1 -       April 1     3.240 130 0s + (MJD - 38 761) x 0.001 296s
  83.             //        April 1 -       Sept. 1     3.340 130 0s +        ""
  84.             //        Sept. 1 - 1965  Jan.  1     3.440 130 0s +        ""
  85.             //  1965  Jan.  1 -       March 1     3.540 130 0s +        ""
  86.             //        March 1 -       Jul.  1     3.640 130 0s +        ""
  87.             //        Jul.  1 -       Sept. 1     3.740 130 0s +        ""
  88.             //        Sept. 1 - 1966  Jan.  1     3.840 130 0s +        ""
  89.             //  1966  Jan.  1 - 1968  Feb.  1     4.313 170 0s + (MJD - 39 126) x 0.002 592s
  90.             //  1968  Feb.  1 - 1972  Jan.  1     4.213 170 0s +        ""
  91.             addOffsetModel(new DateComponents(1961,  1, 1), 37300, 1.4228180, 0.0012960);
  92.             addOffsetModel(new DateComponents(1961,  8, 1), 37300, 1.3728180, 0.0012960);
  93.             addOffsetModel(new DateComponents(1962,  1, 1), 37665, 1.8458580, 0.0011232);
  94.             addOffsetModel(new DateComponents(1963, 11, 1), 37665, 1.9458580, 0.0011232);
  95.             addOffsetModel(new DateComponents(1964,  1, 1), 38761, 3.2401300, 0.0012960);
  96.             addOffsetModel(new DateComponents(1964,  4, 1), 38761, 3.3401300, 0.0012960);
  97.             addOffsetModel(new DateComponents(1964,  9, 1), 38761, 3.4401300, 0.0012960);
  98.             addOffsetModel(new DateComponents(1965,  1, 1), 38761, 3.5401300, 0.0012960);
  99.             addOffsetModel(new DateComponents(1965,  3, 1), 38761, 3.6401300, 0.0012960);
  100.             addOffsetModel(new DateComponents(1965,  7, 1), 38761, 3.7401300, 0.0012960);
  101.             addOffsetModel(new DateComponents(1965,  9, 1), 38761, 3.8401300, 0.0012960);
  102.             addOffsetModel(new DateComponents(1966,  1, 1), 39126, 4.3131700, 0.0025920);
  103.             addOffsetModel(new DateComponents(1968,  2, 1), 39126, 4.2131700, 0.0025920);

  104.             // add leap second entries in chronological order
  105.             for (Map.Entry<DateComponents, Integer> entry : entries.entrySet()) {
  106.                 addOffsetModel(entry.getKey(), 0, entry.getValue(), 0);
  107.             }

  108.         }

  109.         /** Retrieve the generated offsets.
  110.          *
  111.          * @return the {@link UTCTAIOffset}s.
  112.          */
  113.         public List<UTCTAIOffset> getOffsets() {
  114.             return this.offsets;
  115.         }

  116.         /** Add an offset model.
  117.          * <p>
  118.          * This method <em>must</em> be called in chronological order.
  119.          * </p>
  120.          * @param date date of the constant offset model start
  121.          * @param mjdRef reference date of the linear model as a modified julian day
  122.          * @param offset offset at reference date in seconds (TAI minus UTC)
  123.          * @param slope offset slope in seconds per UTC day (TAI minus UTC / dUTC)
  124.          */
  125.         private void addOffsetModel(final DateComponents date, final int mjdRef,
  126.                                     final double offset, final double slope) {

  127.             final TimeScale tai = TimeScalesFactory.getTAI();

  128.             // start of the leap
  129.             final UTCTAIOffset previous    = offsets.isEmpty() ? null : offsets.get(offsets.size() - 1);
  130.             final double previousOffset    = (previous == null) ? 0.0 : previous.getOffset(date, TimeComponents.H00);
  131.             final AbsoluteDate leapStart   = new AbsoluteDate(date, tai).shiftedBy(previousOffset);

  132.             // end of the leap
  133.             final double startOffset       = offset + slope * (date.getMJD() - mjdRef);
  134.             final AbsoluteDate leapEnd     = new AbsoluteDate(date, tai).shiftedBy(startOffset);

  135.             // leap computed at leap start and in UTC scale
  136.             final double normalizedSlope   = slope / Constants.JULIAN_DAY;
  137.             final double leap              = leapEnd.durationFrom(leapStart) / (1 + normalizedSlope);

  138.             if (previous != null) {
  139.                 previous.setValidityEnd(leapStart);
  140.             }
  141.             offsets.add(new UTCTAIOffset(leapStart, date.getMJD(), leap, offset, mjdRef, normalizedSlope));

  142.         }

  143.     }

  144.     /** {@inheritDoc} */
  145.     public double offsetFromTAI(final AbsoluteDate date) {
  146.         if (cache.getEarliest().getDate().compareTo(date) > 0) {
  147.             // the date is before the first known leap
  148.             return 0;
  149.         } else if (cache.getLatest().getDate().compareTo(date) < 0) {
  150.             // the date is after the last known leap
  151.             return -cache.getLatest().getOffset(date);
  152.         } else {
  153.             // the date is nominally bracketed by two leaps
  154.             try {
  155.                 return -cache.getNeighbors(date).get(0).getOffset(date);
  156.             } catch (TimeStampedCacheException tce) {
  157.                 // this should never happen as boundaries have been handled in the previous statements
  158.                 throw OrekitException.createInternalError(tce);
  159.             }
  160.         }
  161.     }

  162.     /** {@inheritDoc} */
  163.     public double offsetToTAI(final DateComponents date,
  164.                               final TimeComponents time) {

  165.         if (cache.getEarliest().getMJD() > date.getMJD()) {
  166.             // the date is before the first known leap
  167.             return 0;
  168.         } else if (cache.getLatest().getMJD() <= date.getMJD()) {
  169.             // the date is after the last known leap
  170.             return cache.getLatest().getOffset(date, time);
  171.         } else {
  172.             // the date is nominally bracketed by two leaps
  173.             try {
  174.                 // find close neighbors, assuming date in TAI, i.e a date earlier than real UTC date
  175.                 final List<UTCTAIOffset> neighbors =
  176.                         cache.getNeighbors(new AbsoluteDate(date, time, TimeScalesFactory.getTAI()));
  177.                 if (neighbors.get(1).getMJD() <= date.getMJD()) {
  178.                     // the date is in fact just after a leap second!
  179.                     return neighbors.get(1).getOffset(date, time);
  180.                 } else {
  181.                     return neighbors.get(0).getOffset(date, time);
  182.                 }
  183.             } catch (TimeStampedCacheException tce) {
  184.                 // this should never happen as boundaries have been handled in the previous statements
  185.                 throw OrekitException.createInternalError(tce);
  186.             }
  187.         }

  188.     }

  189.     /** {@inheritDoc} */
  190.     public String getName() {
  191.         return "UTC";
  192.     }

  193.     /** {@inheritDoc} */
  194.     public String toString() {
  195.         return getName();
  196.     }

  197.     /** Get the date of the first known leap second.
  198.      * @return date of the first known leap second
  199.      */
  200.     public AbsoluteDate getFirstKnownLeapSecond() {
  201.         return cache.getEarliest().getDate();
  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.     /** Check if date is within a leap second introduction.
  210.      * @param date date to check
  211.      * @return true if time is within a leap second introduction
  212.      */
  213.     public boolean insideLeap(final AbsoluteDate date) {
  214.         if (cache.getEarliest().getDate().compareTo(date) > 0) {
  215.             // the date is before the first known leap
  216.             return false;
  217.         } else if (cache.getLatest().getDate().compareTo(date) < 0) {
  218.             // the date is after the last known leap
  219.             return date.compareTo(cache.getLatest().getValidityStart()) < 0;
  220.         } else {
  221.             // the date is nominally bracketed by two leaps
  222.             try {
  223.                 return date.compareTo(cache.getNeighbors(date).get(0).getValidityStart()) < 0;
  224.             } catch (TimeStampedCacheException tce) {
  225.                 // this should never happen as boundaries have been handled in the previous statements
  226.                 throw OrekitException.createInternalError(tce);
  227.             }
  228.         }
  229.     }

  230.     /** Get the value of the previous leap.
  231.      * @param date date to check
  232.      * @return value of the previous leap
  233.      */
  234.     public double getLeap(final AbsoluteDate date) {
  235.         if (cache.getEarliest().getDate().compareTo(date) > 0) {
  236.             return 0;
  237.         } else if (cache.getLatest().getDate().compareTo(date) < 0) {
  238.             // the date is after the last known leap
  239.             return cache.getLatest().getLeap();
  240.         } else {
  241.             // the date is nominally bracketed by two leaps
  242.             try {
  243.                 return cache.getNeighbors(date).get(0).getLeap();
  244.             } catch (TimeStampedCacheException tce) {
  245.                 // this should never happen as boundaries have been handled in the previous statements
  246.                 throw OrekitException.createInternalError(tce);
  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.     /** Internal class used only for serialization. */
  260.     private static class DataTransferObject implements Serializable {

  261.         /** Serializable UID. */
  262.         private static final long serialVersionUID = 20131209L;

  263.         /** Replace the deserialized data transfer object with a {@link UTCScale}.
  264.          * @return replacement {@link UTCScale}
  265.          */
  266.         private Object readResolve() {
  267.             try {
  268.                 return TimeScalesFactory.getUTC();
  269.             } catch (OrekitException oe) {
  270.                 throw OrekitException.createInternalError(oe);
  271.             }
  272.         }

  273.     }

  274. }