UTCScale.java

  1. /* Copyright 2002-2017 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.List;

  20. import org.hipparchus.RealFieldElement;
  21. import org.orekit.errors.OrekitException;
  22. import org.orekit.errors.OrekitInternalError;
  23. import org.orekit.utils.Constants;

  24. /** Coordinated Universal Time.
  25.  * <p>UTC is related to TAI using step adjustments from time to time
  26.  * according to IERS (International Earth Rotation Service) rules. Before 1972,
  27.  * these adjustments were piecewise linear offsets. Since 1972, these adjustments
  28.  * are piecewise constant offsets, which require introduction of leap seconds.</p>
  29.  * <p>Leap seconds are always inserted as additional seconds at the last minute
  30.  * of the day, pushing the next day forward. Such minutes are therefore more
  31.  * than 60 seconds long. In theory, there may be seconds removal instead of seconds
  32.  * insertion, but up to now (2010) it has never been used. As an example, when a
  33.  * one second leap was introduced at the end of 2005, the UTC time sequence was
  34.  * 2005-12-31T23:59:59 UTC, followed by 2005-12-31T23:59:60 UTC, followed by
  35.  * 2006-01-01T00:00:00 UTC.</p>
  36.  * <p>This is intended to be accessed thanks to the {@link TimeScalesFactory} class,
  37.  * so there is no public constructor.</p>
  38.  * @author Luc Maisonobe
  39.  * @see AbsoluteDate
  40.  */
  41. public class UTCScale implements TimeScale {

  42.     /** Serializable UID. */
  43.     private static final long serialVersionUID = 20150402L;

  44.     /** UTC-TAI offsets. */
  45.     private UTCTAIOffset[] offsets;

  46.     /** Package private constructor for the factory.
  47.      * Used to create the prototype instance of this class that is used to
  48.      * clone all subsequent instances of {@link UTCScale}. Initializes the offset
  49.      * table that is shared among all instances.
  50.      * @param offsetModels UTC-TAI offsets
  51.      * @exception OrekitException if cache cannot be set up
  52.      */
  53.     UTCScale(final List<OffsetModel> offsetModels) throws OrekitException {

  54.         if (offsetModels.get(0).getStart().getYear() > 1968) {
  55.             // the pre-1972 linear offsets are missing, add them manually
  56.             // excerpt from UTC-TAI.history file:
  57.             //  1961  Jan.  1 - 1961  Aug.  1     1.422 818 0s + (MJD - 37 300) x 0.001 296s
  58.             //        Aug.  1 - 1962  Jan.  1     1.372 818 0s +        ""
  59.             //  1962  Jan.  1 - 1963  Nov.  1     1.845 858 0s + (MJD - 37 665) x 0.001 123 2s
  60.             //  1963  Nov.  1 - 1964  Jan.  1     1.945 858 0s +        ""
  61.             //  1964  Jan.  1 -       April 1     3.240 130 0s + (MJD - 38 761) x 0.001 296s
  62.             //        April 1 -       Sept. 1     3.340 130 0s +        ""
  63.             //        Sept. 1 - 1965  Jan.  1     3.440 130 0s +        ""
  64.             //  1965  Jan.  1 -       March 1     3.540 130 0s +        ""
  65.             //        March 1 -       Jul.  1     3.640 130 0s +        ""
  66.             //        Jul.  1 -       Sept. 1     3.740 130 0s +        ""
  67.             //        Sept. 1 - 1966  Jan.  1     3.840 130 0s +        ""
  68.             //  1966  Jan.  1 - 1968  Feb.  1     4.313 170 0s + (MJD - 39 126) x 0.002 592s
  69.             //  1968  Feb.  1 - 1972  Jan.  1     4.213 170 0s +        ""
  70.             offsetModels.add( 0, new OffsetModel(new DateComponents(1961,  1, 1), 37300, 1.4228180, 0.0012960));
  71.             offsetModels.add( 1, new OffsetModel(new DateComponents(1961,  8, 1), 37300, 1.3728180, 0.0012960));
  72.             offsetModels.add( 2, new OffsetModel(new DateComponents(1962,  1, 1), 37665, 1.8458580, 0.0011232));
  73.             offsetModels.add( 3, new OffsetModel(new DateComponents(1963, 11, 1), 37665, 1.9458580, 0.0011232));
  74.             offsetModels.add( 4, new OffsetModel(new DateComponents(1964,  1, 1), 38761, 3.2401300, 0.0012960));
  75.             offsetModels.add( 5, new OffsetModel(new DateComponents(1964,  4, 1), 38761, 3.3401300, 0.0012960));
  76.             offsetModels.add( 6, new OffsetModel(new DateComponents(1964,  9, 1), 38761, 3.4401300, 0.0012960));
  77.             offsetModels.add( 7, new OffsetModel(new DateComponents(1965,  1, 1), 38761, 3.5401300, 0.0012960));
  78.             offsetModels.add( 8, new OffsetModel(new DateComponents(1965,  3, 1), 38761, 3.6401300, 0.0012960));
  79.             offsetModels.add( 9, new OffsetModel(new DateComponents(1965,  7, 1), 38761, 3.7401300, 0.0012960));
  80.             offsetModels.add(10, new OffsetModel(new DateComponents(1965,  9, 1), 38761, 3.8401300, 0.0012960));
  81.             offsetModels.add(11, new OffsetModel(new DateComponents(1966,  1, 1), 39126, 4.3131700, 0.0025920));
  82.             offsetModels.add(12, new OffsetModel(new DateComponents(1968,  2, 1), 39126, 4.2131700, 0.0025920));
  83.         }

  84.         // create cache
  85.         offsets = new UTCTAIOffset[offsetModels.size()];

  86.         UTCTAIOffset previous = null;

  87.         // link the offsets together
  88.         final TimeScale tai = TimeScalesFactory.getTAI();
  89.         for (int i = 0; i < offsetModels.size(); ++i) {

  90.             final OffsetModel    o      = offsetModels.get(i);
  91.             final DateComponents date   = o.getStart();
  92.             final int            mjdRef = o.getMJDRef();
  93.             final double         offset = o.getOffset();
  94.             final double         slope  = o.getSlope();

  95.             // start of the leap
  96.             final double previousOffset    = (previous == null) ? 0.0 : previous.getOffset(date, TimeComponents.H00);
  97.             final AbsoluteDate leapStart   = new AbsoluteDate(date, tai).shiftedBy(previousOffset);

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

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

  104.             previous = new UTCTAIOffset(leapStart, date.getMJD(), leap, offset, mjdRef, normalizedSlope);
  105.             offsets[i] = previous;

  106.         }

  107.     }

  108.     /** {@inheritDoc} */
  109.     @Override
  110.     public double offsetFromTAI(final AbsoluteDate date) {
  111.         final int offsetIndex = findOffsetIndex(date);
  112.         if (offsetIndex < 0) {
  113.             // the date is before the first known leap
  114.             return 0;
  115.         } else {
  116.             return -offsets[offsetIndex].getOffset(date);
  117.         }
  118.     }

  119.     /** {@inheritDoc} */
  120.     @Override
  121.     public <T extends RealFieldElement<T>> T offsetFromTAI(final FieldAbsoluteDate<T> date) {
  122.         final int offsetIndex = findOffsetIndex(date.toAbsoluteDate());
  123.         if (offsetIndex < 0) {
  124.             // the date is before the first known leap
  125.             return date.getField().getZero();
  126.         } else {
  127.             return offsets[offsetIndex].getOffset(date).negate();
  128.         }
  129.     }

  130.     /** {@inheritDoc} */
  131.     @Override
  132.     public double offsetToTAI(final DateComponents date,
  133.                               final TimeComponents time) {

  134.         // take offset from local time into account, but ignoring seconds,
  135.         // so when we parse an hour like 23:59:60.5 during leap seconds introduction,
  136.         // we do not jump to next day
  137.         final int minuteInDay = time.getHour() * 60 + time.getMinute() - time.getMinutesFromUTC();
  138.         final int correction  = minuteInDay < 0 ? (minuteInDay - 1439) / 1440 : minuteInDay / 1440;

  139.         // find close neighbors, assuming date in TAI, i.e a date earlier than real UTC date
  140.         final int mjd = date.getMJD() + correction;
  141.         final UTCTAIOffset offset = findOffset(mjd);
  142.         if (offset == null) {
  143.             // the date is before the first known leap
  144.             return 0;
  145.         } else {
  146.             return offset.getOffset(date, time);
  147.         }

  148.     }

  149.     /** {@inheritDoc} */
  150.     public String getName() {
  151.         return "UTC";
  152.     }

  153.     /** {@inheritDoc} */
  154.     public String toString() {
  155.         return getName();
  156.     }

  157.     /** Get the date of the first known leap second.
  158.      * @return date of the first known leap second
  159.      */
  160.     public AbsoluteDate getFirstKnownLeapSecond() {
  161.         return offsets[0].getDate();
  162.     }

  163.     /** Get the date of the last known leap second.
  164.      * @return date of the last known leap second
  165.      */
  166.     public AbsoluteDate getLastKnownLeapSecond() {
  167.         return offsets[offsets.length - 1].getDate();
  168.     }

  169.     /** {@inheritDoc} */
  170.     @Override
  171.     public boolean insideLeap(final AbsoluteDate date) {
  172.         final int offsetIndex = findOffsetIndex(date);
  173.         if (offsetIndex < 0) {
  174.             // the date is before the first known leap
  175.             return false;
  176.         } else {
  177.             return date.compareTo(offsets[offsetIndex].getValidityStart()) < 0;
  178.         }
  179.     }

  180.     /** {@inheritDoc} */
  181.     @Override
  182.     public <T extends RealFieldElement<T>> boolean insideLeap(final FieldAbsoluteDate<T> date) {
  183.         return insideLeap(date.toAbsoluteDate());
  184.     }

  185.     /** {@inheritDoc} */
  186.     @Override
  187.     public int minuteDuration(final AbsoluteDate date) {
  188.         final int offsetIndex = findOffsetIndex(date);
  189.         if (offsetIndex < 0) {
  190.             // the date is before the first known leap
  191.             return 60;
  192.         } else {
  193.             if (date.compareTo(offsets[offsetIndex].getValidityStart()) < 0) {
  194.                 // the date is during the leap itself
  195.                 return 61;
  196.             } else {
  197.                 // the date is after a leap, but it may be just before the next one
  198.                 if (offsetIndex + 1 < offsets.length &&
  199.                     offsets[offsetIndex + 1].getDate().durationFrom(date) <= 60.0) {
  200.                     // the next leap will start in one minute, it will extend the current minute
  201.                     return 61;
  202.                 } else {
  203.                     // no leap is expected within the next minute
  204.                     return 60;
  205.                 }
  206.             }
  207.         }
  208.     }

  209.     /** {@inheritDoc} */
  210.     @Override
  211.     public <T extends RealFieldElement<T>> int minuteDuration(final FieldAbsoluteDate<T> date) {
  212.         return minuteDuration(date.toAbsoluteDate());
  213.     }

  214.     /** {@inheritDoc} */
  215.     @Override
  216.     public double getLeap(final AbsoluteDate date) {
  217.         final int offsetIndex = findOffsetIndex(date);
  218.         if (offsetIndex < 0) {
  219.             // the date is before the first known leap
  220.             return 0;
  221.         } else {
  222.             return offsets[offsetIndex].getLeap();
  223.         }
  224.     }

  225.     /** {@inheritDoc} */
  226.     @Override
  227.     public <T extends RealFieldElement<T>> T getLeap(final FieldAbsoluteDate<T> date) {
  228.         return date.getField().getZero().add(getLeap(date.toAbsoluteDate()));
  229.     }

  230.     /** Find the index of the offset valid at some date.
  231.      * @param date date at which offset is requested
  232.      * @return index of the offset valid at this date, or -1 if date is before first offset.
  233.      */
  234.     private int findOffsetIndex(final AbsoluteDate date) {
  235.         int inf = 0;
  236.         int sup = offsets.length;
  237.         while (sup - inf > 1) {
  238.             final int middle = (inf + sup) >>> 1;
  239.             if (date.compareTo(offsets[middle].getDate()) < 0) {
  240.                 sup = middle;
  241.             } else {
  242.                 inf = middle;
  243.             }
  244.         }
  245.         if (sup == offsets.length) {
  246.             // the date is after the last known leap second
  247.             return offsets.length - 1;
  248.         } else if (date.compareTo(offsets[inf].getDate()) < 0) {
  249.             // the date is before the first known leap
  250.             return -1;
  251.         } else {
  252.             return inf;
  253.         }
  254.     }

  255.     /** Find the offset valid at some date.
  256.      * @param mjd Modified Julian Day of the date at which offset is requested
  257.      * @return offset valid at this date, or null if date is before first offset.
  258.      */
  259.     private UTCTAIOffset findOffset(final int mjd) {
  260.         int inf = 0;
  261.         int sup = offsets.length;
  262.         while (sup - inf > 1) {
  263.             final int middle = (inf + sup) >>> 1;
  264.             if (mjd < offsets[middle].getMJD()) {
  265.                 sup = middle;
  266.             } else {
  267.                 inf = middle;
  268.             }
  269.         }
  270.         if (sup == offsets.length) {
  271.             // the date is after the last known leap second
  272.             return offsets[offsets.length - 1];
  273.         } else if (mjd < offsets[inf].getMJD()) {
  274.             // the date is before the first known leap
  275.             return null;
  276.         } else {
  277.             return offsets[inf];
  278.         }
  279.     }

  280.     /** Replace the instance with a data transfer object for serialization.
  281.      * @return data transfer object that will be serialized
  282.      */
  283.     private Object writeReplace() {
  284.         return new DataTransferObject();
  285.     }

  286.     /** Internal class used only for serialization. */
  287.     private static class DataTransferObject implements Serializable {

  288.         /** Serializable UID. */
  289.         private static final long serialVersionUID = 20131209L;

  290.         /** Replace the deserialized data transfer object with a {@link UTCScale}.
  291.          * @return replacement {@link UTCScale}
  292.          */
  293.         private Object readResolve() {
  294.             try {
  295.                 return TimeScalesFactory.getUTC();
  296.             } catch (OrekitException oe) {
  297.                 throw new OrekitInternalError(oe);
  298.             }
  299.         }

  300.     }

  301. }