1 /* Copyright 2002-2024 CS GROUP 2 * Licensed to CS GROUP (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 21 import org.hipparchus.CalculusFieldElement; 22 23 /** Offset between {@link UTCScale UTC} and {@link TAIScale TAI} time scales. 24 * <p>The {@link UTCScale UTC} and {@link TAIScale TAI} time scales are two 25 * scales offset with respect to each other. The {@link TAIScale TAI} scale is 26 * continuous whereas the {@link UTCScale UTC} includes some discontinuity when 27 * leap seconds are introduced by the <a href="http://www.iers.org/">International 28 * Earth Rotation Service</a> (IERS).</p> 29 * <p>This class represents the offset between the two scales that is 30 * valid between two leap seconds occurrences. It handles both the linear offsets 31 * used from 1961-01-01 to 1971-12-31 and the constant integer offsets used since 32 * 1972-01-01.</p> 33 * @author Luc Maisonobe 34 * @see UTCScale 35 * @see UTCTAIHistoryFilesLoader 36 */ 37 public class UTCTAIOffset implements TimeStamped, Serializable { 38 39 /** Serializable UID. */ 40 private static final long serialVersionUID = 20240720L; 41 42 /** Nanoseconds in one second. */ 43 private static final int NANOS_IN_SECOND = 1000000000; 44 45 /** Leap date. */ 46 private final AbsoluteDate leapDate; 47 48 /** Leap date in Modified Julian Day. */ 49 private final int leapDateMJD; 50 51 /** Offset start of validity date. */ 52 private final AbsoluteDate validityStart; 53 54 /** Reference date for the slope multiplication as Modified Julian Day. */ 55 private final int mjdRef; 56 57 /** Reference date for the slope multiplication. */ 58 private final AbsoluteDate reference; 59 60 /** Value of the leap at offset validity start (in seconds). */ 61 private final TimeOffset leap; 62 63 /** Offset at validity start in seconds (TAI minus UTC). */ 64 private final TimeOffset offset; 65 66 /** Offset slope in nanoseconds per UTC second (TAI minus UTC / dUTC). */ 67 private final int slope; 68 69 /** Simple constructor for a linear model. 70 * @param leapDate leap date 71 * @param leapDateMJD leap date in Modified Julian Day 72 * @param leap value of the leap at offset validity start (in seconds) 73 * @param offset offset in seconds (TAI minus UTC) 74 * @param mjdRef reference date for the slope multiplication as Modified Julian Day 75 * @param slope offset slope in nanoseconds per UTC second (TAI minus UTC / dUTC) 76 * @param reference date for slope computations. 77 */ 78 UTCTAIOffset(final AbsoluteDate leapDate, final int leapDateMJD, 79 final TimeOffset leap, final TimeOffset offset, 80 final int mjdRef, final int slope, final AbsoluteDate reference) { 81 this.leapDate = leapDate; 82 this.leapDateMJD = leapDateMJD; 83 this.validityStart = leapDate.shiftedBy(leap); 84 this.mjdRef = mjdRef; 85 this.reference = reference; 86 this.leap = leap; 87 this.offset = offset; 88 89 // at some absolute instant t₀, we can associate reading a₀ on a TAI clock and u₀ on a UTC clock 90 // at this instant, the offset between TAI and UTC is therefore τ₀ = a₀ - u₀ 91 // at another absolute instant t₁, we can associate reading a₁ on a TAI clock and u₁ on a UTC clock 92 // at this instant, the offset between TAI and UTC is therefore τ₁ = a₁ - u₁ 93 // the slope is defined according to offsets counted in UTC, i.e.: 94 // τ₁ = τ₀ + (u₁ - u₀) * slope/n (where n = 10⁹ because the slope is in ns/s) 95 // if we have a₁ - a₀ (i.e. dates in TAI) instead of u₁ - u₀, we need to invert the expression 96 // we get: τ₁ = τ₀ + (a₁ - a₀) * slope / (n + slope) 97 this.slope = slope; 98 99 } 100 101 /** Get the date of the start of the leap. 102 * @return date of the start of the leap 103 * @see #getValidityStart() 104 */ 105 public AbsoluteDate getDate() { 106 return leapDate; 107 } 108 109 /** Get the date of the start of the leap as Modified Julian Day. 110 * @return date of the start of the leap as Modified Julian Day 111 */ 112 public int getMJD() { 113 return leapDateMJD; 114 } 115 116 /** Get the start time of validity for this offset. 117 * <p>The start of the validity of the offset is {@link #getLeap()} 118 * seconds after the start of the leap itself.</p> 119 * @return start of validity date 120 * @see #getDate() 121 */ 122 public AbsoluteDate getValidityStart() { 123 return validityStart; 124 } 125 126 /** Get the value of the leap at offset validity start. 127 * @return value of the leap at offset validity start 128 */ 129 public TimeOffset getLeap() { 130 return leap; 131 } 132 133 /** Get the TAI - UTC offset in seconds. 134 * @param date date at which the offset is requested 135 * @return TAI - UTC offset in seconds. 136 */ 137 public TimeOffset getOffset(final AbsoluteDate date) { 138 if (slope == 0) { 139 // we use an if statement here so the offset computation returns 140 // a finite value when date is AbsoluteDate.FUTURE_INFINITY 141 // without this if statement, the multiplication between an 142 // infinite duration and a zero slope would induce a NaN offset 143 return offset; 144 } else { 145 146 // time during which slope applies 147 final TimeOffset delta = date.accurateDurationFrom(reference); 148 149 // accumulated drift 150 final TimeOffset drift = delta.multiply(slope).divide(slope + NANOS_IN_SECOND); 151 152 return offset.add(drift); 153 154 } 155 } 156 157 /** Get the TAI - UTC offset in seconds. 158 * @param date date at which the offset is requested 159 * @param <T> type of the filed elements 160 * @return TAI - UTC offset in seconds. 161 * @since 9.0 162 */ 163 public <T extends CalculusFieldElement<T>> T getOffset(final FieldAbsoluteDate<T> date) { 164 if (slope == 0) { 165 // we use an if statement here so the offset computation returns 166 // a finite value when date is FieldAbsoluteDate.getFutureInfinity(field) 167 // without this if statement, the multiplication between an 168 // infinite duration and a zero slope would induce a NaN offset 169 return date.getField().getZero().newInstance(offset.toDouble()); 170 } else { 171 // TODO perform complete computation 172 return date.getField().getZero().newInstance(getOffset(date.toAbsoluteDate()).toDouble()); 173 } 174 } 175 176 /** Get the TAI - UTC offset in seconds. 177 * @param date date components (in UTC) at which the offset is requested 178 * @param time time components (in UTC) at which the offset is requested 179 * @return TAI - UTC offset in seconds. 180 */ 181 public TimeOffset getOffset(final DateComponents date, final TimeComponents time) { 182 if (slope == 0) { 183 return offset; 184 } else { 185 186 // time during which slope applies 187 final TimeOffset delta = new TimeOffset((date.getMJD() - mjdRef) * TimeOffset.DAY.getSeconds() + 188 time.getHour() * TimeOffset.HOUR.getSeconds() + 189 time.getMinute() * TimeOffset.MINUTE.getSeconds() + 190 time.getSplitSecond().getSeconds(), 191 time.getSplitSecond().getAttoSeconds()); 192 193 // accumulated drift 194 final TimeOffset drift = delta.multiply(slope).divide(NANOS_IN_SECOND); 195 196 return offset.add(drift); 197 198 } 199 } 200 201 }