UTCTAIHistoryFilesLoader.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.orekit.annotation.DefaultDataContext;
  28. import org.orekit.data.AbstractSelfFeedingLoader;
  29. import org.orekit.data.DataContext;
  30. import org.orekit.data.DataProvidersManager;
  31. import org.orekit.errors.OrekitException;
  32. import org.orekit.errors.OrekitMessages;


  33. /** Loader for UTC versus TAI history files.
  34.  * <p>UTC versus TAI history files contain {@link UTCTAIOffset
  35.  * leap seconds} data since.</p>
  36.  * <p>The UTC versus TAI history files are recognized thanks to their
  37.  * base names, which must match the pattern <code>UTC-TAI.history</code>
  38.  * (or <code>UTC-TAI.history.gz</code> for gzip-compressed files)</p>
  39.  * <p>Only one history file must be present in the IERS directories
  40.  * hierarchy.</p>
  41.  * @author Luc Maisonobe
  42.  */
  43. public class UTCTAIHistoryFilesLoader extends AbstractSelfFeedingLoader
  44.         implements UTCTAIOffsetsLoader {

  45.     /** Supported files name pattern. */
  46.     private static final String SUPPORTED_NAMES = "^UTC-TAI\\.history$";

  47.     /**
  48.      * Build a loader for UTC-TAI history file. This constructor uses the {@link
  49.      * DataContext#getDefault() default data context}.
  50.      *
  51.      * @see #UTCTAIHistoryFilesLoader(DataProvidersManager)
  52.      */
  53.     @DefaultDataContext
  54.     public UTCTAIHistoryFilesLoader() {
  55.         this(DataContext.getDefault().getDataProvidersManager());
  56.     }

  57.     /**
  58.      * Build a loader for UTC-TAI history file.
  59.      *
  60.      * @param manager provides access to the {@code UTC-TAI.history} file.
  61.      */
  62.     public UTCTAIHistoryFilesLoader(final DataProvidersManager manager) {
  63.         super(SUPPORTED_NAMES, manager);
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public List<OffsetModel> loadOffsets() {
  68.         final UtcTaiOffsetLoader parser = new UtcTaiOffsetLoader(new Parser());
  69.         this.feed(parser);
  70.         return parser.getOffsets();
  71.     }

  72.     /** Internal class performing the parsing. */
  73.     public static class Parser implements UTCTAIOffsetsLoader.Parser {

  74.         /** Regular data lines pattern. */
  75.         private Pattern regularPattern;

  76.         /** Last line pattern pattern. */
  77.         private Pattern lastPattern;

  78.         /** Simple constructor.
  79.          */
  80.         public Parser() {

  81.             // the data lines in the UTC time steps data files have the following form:
  82.             // 1966  Jan.  1 - 1968  Feb.  1     4.313 170 0s + (MJD - 39 126) x 0.002 592s
  83.             // 1968  Feb.  1 - 1972  Jan.  1     4.213 170 0s +        ""
  84.             // 1972  Jan.  1 -       Jul.  1    10s
  85.             //       Jul.  1 - 1973  Jan.  1    11s
  86.             // 1973  Jan.  1 - 1974  Jan.  1    12s
  87.             //  ...
  88.             // 2006  Jan.  1.- 2009  Jan.  1    33s
  89.             // 2009  Jan.  1.- 2012  Jul   1    34s
  90.             // 2012  Jul   1 -                  35s

  91.             // we ignore the non-constant and non integer offsets before 1972-01-01
  92.             final String start = "^";

  93.             // year group
  94.             final String yearField = "\\p{Blank}*((?:\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})|(?:    ))";

  95.             // second group: month as a three letters capitalized abbreviation
  96.             final StringBuilder builder = new StringBuilder("\\p{Blank}+(");
  97.             for (final Month month : Month.values()) {
  98.                 builder.append(month.getCapitalizedAbbreviation());
  99.                 builder.append('|');
  100.             }
  101.             builder.delete(builder.length() - 1, builder.length());
  102.             builder.append(")\\.?");
  103.             final String monthField = builder.toString();

  104.             // day group
  105.             final String dayField = "\\p{Blank}+([ 0-9]+)\\.?";

  106.             // offset group
  107.             final String offsetField = "\\p{Blank}+(\\p{Digit}+)s";

  108.             final String separator   = "\\p{Blank}*-\\p{Blank}+";
  109.             final String finalBlanks = "\\p{Blank}*$";
  110.             regularPattern = Pattern.compile(start + yearField + monthField + dayField +
  111.                                              separator + yearField + monthField + dayField +
  112.                                              offsetField + finalBlanks);
  113.             lastPattern    = Pattern.compile(start + yearField + monthField + dayField +
  114.                                              separator + offsetField + finalBlanks);


  115.         }

  116.         /** Load UTC-TAI offsets entries read from some file.
  117.          *
  118.          * {@inheritDoc}
  119.          *
  120.          * @param input data input stream
  121.          * @param name name of the file (or zip entry)
  122.          * @exception IOException if data can't be read
  123.          */
  124.         @Override
  125.         public List<OffsetModel> parse(final InputStream input, final String name)
  126.             throws IOException {

  127.             final List<OffsetModel> offsets = new ArrayList<>();
  128.             final String emptyYear = "    ";
  129.             int lineNumber = 0;
  130.             DateComponents lastDate = null;
  131.             String line = null;
  132.             int lastLine = 0;
  133.             String previousYear = emptyYear;
  134.             // set up a reader for line-oriented file
  135.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

  136.                 // read all file, ignoring not recognized lines
  137.                 for (line = reader.readLine(); line != null; line = reader.readLine()) {
  138.                     ++lineNumber;

  139.                     // check matching for regular lines and last line
  140.                     Matcher matcher = regularPattern.matcher(line);
  141.                     if (matcher.matches()) {
  142.                         if (lastLine > 0) {
  143.                             throw new OrekitException(OrekitMessages.UNEXPECTED_DATA_AFTER_LINE_IN_FILE,
  144.                                                       lastLine, name, line);
  145.                         }
  146.                     } else {
  147.                         matcher = lastPattern.matcher(line);
  148.                         if (matcher.matches()) {
  149.                             // this is the last line (there is a start date but no end date)
  150.                             lastLine = lineNumber;
  151.                         }
  152.                     }

  153.                     if (matcher.matches()) {

  154.                         // build an entry from the extracted fields

  155.                         String year = matcher.group(1);
  156.                         if (emptyYear.equals(year)) {
  157.                             year = previousYear;
  158.                         }
  159.                         if (lineNumber != lastLine) {
  160.                             if (emptyYear.equals(matcher.group(4))) {
  161.                                 previousYear = year;
  162.                             } else {
  163.                                 previousYear = matcher.group(4);
  164.                             }
  165.                         }
  166.                         final DateComponents leapDay = new DateComponents(Integer.parseInt(year.trim()),
  167.                                                                           Month.parseMonth(matcher.group(2)),
  168.                                                                           Integer.parseInt(matcher.group(3).trim()));

  169.                         final int offset = Integer.parseInt(matcher.group(matcher.groupCount()));
  170.                         if (lastDate != null && leapDay.compareTo(lastDate) <= 0) {
  171.                             throw new OrekitException(OrekitMessages.NON_CHRONOLOGICAL_DATES_IN_FILE,
  172.                                                       name, lineNumber);
  173.                         }
  174.                         lastDate = leapDay;
  175.                         offsets.add(new OffsetModel(leapDay, offset));

  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. }