AGILeapSecondFilesLoader.java

  1. /* Copyright 2002-2025 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. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.nio.charset.StandardCharsets;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.regex.Matcher;
  26. import java.util.regex.Pattern;

  27. import org.hipparchus.util.FastMath;
  28. import org.orekit.annotation.DefaultDataContext;
  29. import org.orekit.data.AbstractSelfFeedingLoader;
  30. import org.orekit.data.DataContext;
  31. import org.orekit.data.DataProvidersManager;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitMessages;

  34. /** Loader for UTC-TAI extracted from LeapSecond file from AGI.
  35.  * <p>
  36.  * This class is immutable and hence thread-safe
  37.  * </p>
  38.  * @see <a href="ftp://ftp.agi.com/pub/STKData/Astro/LeapSecond.dat">LeapSecond.dat</a>
  39.  * @author Luc Maisonobe
  40.  * @since 10.3
  41.  */
  42. public class AGILeapSecondFilesLoader extends AbstractSelfFeedingLoader
  43.         implements UTCTAIOffsetsLoader {

  44.     /** Default supported files name pattern. */
  45.     public static final String DEFAULT_SUPPORTED_NAMES = "^LeapSecond\\.dat$";

  46.     /** Number of seconds in one day. */
  47.     private static final long SEC_PER_DAY = 86400L;

  48.     /** Number of attoseconds in one second. */
  49.     private static final long ATTOS_PER_NANO = 1000000000L;

  50.     /** Slope conversion factor from seconds per day to nanoseconds per second. */
  51.     private static final long SLOPE_FACTOR = SEC_PER_DAY * ATTOS_PER_NANO;

  52.     /**
  53.      * Build a loader for LeapSecond.dat file from AGI. This constructor uses the {@link
  54.      * DataContext#getDefault() default data context}.
  55.      *
  56.      * @param supportedNames regular expression for supported files names
  57.      * @see #AGILeapSecondFilesLoader(String, DataProvidersManager)
  58.      */
  59.     @DefaultDataContext
  60.     public AGILeapSecondFilesLoader(final String supportedNames) {
  61.         this(supportedNames, DataContext.getDefault().getDataProvidersManager());
  62.     }

  63.     /**
  64.      * Build a loader for LeapSecond.dat file from AGI.
  65.      *
  66.      * @param supportedNames regular expression for supported files names
  67.      * @param manager        provides access to the {@code tai-utc.dat} file.
  68.      */
  69.     public AGILeapSecondFilesLoader(final String supportedNames,
  70.                                     final DataProvidersManager manager) {
  71.         super(supportedNames, manager);
  72.     }

  73.     /** {@inheritDoc} */
  74.     @Override
  75.     public List<OffsetModel> loadOffsets() {
  76.         final UtcTaiOffsetLoader parser = new UtcTaiOffsetLoader(new Parser());
  77.         this.feed(parser);
  78.         return parser.getOffsets();
  79.     }

  80.     /** Internal class performing the parsing. */
  81.     public static class Parser implements UTCTAIOffsetsLoader.Parser {

  82.         /** Regular expression for optional blanks. */
  83.         private static final String BLANKS               = "\\p{Blank}*";

  84.         /** Regular expression for storage start. */
  85.         private static final String STORAGE_START        = "(";

  86.         /** Regular expression for storage end. */
  87.         private static final String STORAGE_END          = ")";

  88.         /** Regular expression for alternative. */
  89.         private static final String ALTERNATIVE          = "|";

  90.         /** Regular expression matching blanks at start of line. */
  91.         private static final String LINE_START_REGEXP     = "^" + BLANKS;

  92.         /** Regular expression matching blanks at end of line. */
  93.         private static final String LINE_END_REGEXP       = BLANKS + "$";

  94.         /** Regular expression matching integers. */
  95.         private static final String INTEGER_REGEXP        = "[-+]?\\p{Digit}+";

  96.         /** Regular expression matching real numbers. */
  97.         private static final String REAL_REGEXP           = "[-+]?(?:\\p{Digit}+(?:\\.\\p{Digit}*)?|\\.\\p{Digit}+)(?:[eE][-+]?\\p{Digit}+)?";

  98.         /** Regular expression matching an integer field to store. */
  99.         private static final String STORED_INTEGER_FIELD  = BLANKS + STORAGE_START + INTEGER_REGEXP + STORAGE_END;

  100.         /** Regular expression matching a real field to store. */
  101.         private static final String STORED_REAL_FIELD     = BLANKS + STORAGE_START + REAL_REGEXP + STORAGE_END;

  102.         /** Data lines pattern. */
  103.         private final Pattern dataPattern;

  104.         /** Simple constructor.
  105.          */
  106.         public Parser() {

  107.             // data lines read:
  108.             // 28
  109.             // 1972 JAN  1   2441317.5     10.0        41317.  0.0
  110.             // 1972 JUL  1   2441499.5     11.0        41317.  0.0
  111.             // 1973 JAN  1   2441683.5     12.0        41317.  0.0
  112.             // 1974 JAN  1   2442048.5     13.0        41317.  0.0
  113.             // 1975 JAN  1   2442413.5     14.0        41317.  0.0
  114.             // 1976 JAN  1   2442778.5     15.0        41317.  0.0
  115.             // 1977 JAN  1   2443144.5     16.0        41317.  0.0
  116.             // 1978 JAN  1   2443509.5     17.0        41317.  0.0

  117.             // month as a three letters upper case abbreviation
  118.             final StringBuilder builder = new StringBuilder(BLANKS + STORAGE_START);
  119.             for (final Month month : Month.values()) {
  120.                 builder.append(month.getUpperCaseAbbreviation());
  121.                 builder.append(ALTERNATIVE);
  122.             }
  123.             builder.delete(builder.length() - 1, builder.length());
  124.             builder.append(STORAGE_END);
  125.             final String monthField = builder.toString();

  126.             dataPattern = Pattern.compile(LINE_START_REGEXP +
  127.                                           STORED_INTEGER_FIELD + monthField + STORED_INTEGER_FIELD +
  128.                                           BLANKS + STORED_REAL_FIELD +
  129.                                           BLANKS + STORED_REAL_FIELD +
  130.                                           BLANKS + STORED_REAL_FIELD +
  131.                                           BLANKS + STORED_REAL_FIELD +
  132.                                           LINE_END_REGEXP);


  133.         }

  134.         /** Load UTC-TAI offsets entries read from some file.
  135.          * <p>The time steps are extracted from some {@code LeapSecond.dat} file.
  136.          * Since entries are stored in a {@link java.util.SortedMap SortedMap},
  137.          * they are chronologically sorted and only one entry remains for a given date.</p>
  138.          * @param input data input stream
  139.          * @param name name of the file (or zip entry)
  140.          * @exception IOException if data can't be read
  141.          */
  142.         @Override
  143.         public List<OffsetModel> parse(final InputStream input, final String name)
  144.             throws IOException {

  145.             final List<OffsetModel> offsets = new ArrayList<>();

  146.             int lineNumber = 0;
  147.             DateComponents lastDate = null;
  148.             String line = null;
  149.             // set up a reader for line-oriented file
  150.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

  151.                 // read all file, ignoring not recognized lines
  152.                 for (line = reader.readLine(); line != null; line = reader.readLine()) {
  153.                     ++lineNumber;

  154.                     // check matching for data lines
  155.                     final Matcher matcher = dataPattern.matcher(line);
  156.                     if (matcher.matches()) {

  157.                         // build an entry from the extracted fields
  158.                         final DateComponents dc1 = new DateComponents(Integer.parseInt(matcher.group(1)),
  159.                                                                       Month.parseMonth(matcher.group(2)),
  160.                                                                       Integer.parseInt(matcher.group(3)));
  161.                         final DateComponents dc2 = new DateComponents(DateComponents.JULIAN_EPOCH,
  162.                                                                       (int) FastMath.ceil(Double.parseDouble(matcher.group(4))));
  163.                         if (!dc1.equals(dc2)) {
  164.                             throw new OrekitException(OrekitMessages.INCONSISTENT_DATES_IN_IERS_FILE,
  165.                                                       name, dc1.getYear(), dc1.getMonth(), dc1.getDay(), dc2.getMJD());
  166.                         }

  167.                         if (lastDate != null && dc1.compareTo(lastDate) <= 0) {
  168.                             throw new OrekitException(OrekitMessages.NON_CHRONOLOGICAL_DATES_IN_FILE,
  169.                                                       name, lineNumber);
  170.                         }
  171.                         lastDate = dc1;

  172.                         final double mjdRef = Double.parseDouble(matcher.group(6));
  173.                         offsets.add(new OffsetModel(dc1, (int) FastMath.rint(mjdRef),
  174.                                                     TimeOffset.parse(matcher.group(5)),
  175.                                                     (int) (TimeOffset.parse(matcher.group(7)).getAttoSeconds() / SLOPE_FACTOR)));

  176.                     }
  177.                 }

  178.             } catch (NumberFormatException nfe) {
  179.                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  180.                                           lineNumber, name, line);
  181.             }

  182.             if (offsets.isEmpty()) {
  183.                 throw new OrekitException(OrekitMessages.NO_ENTRIES_IN_IERS_UTC_TAI_HISTORY_FILE, name);
  184.             }

  185.             return offsets;

  186.         }

  187.     }

  188. }