UTCTAIHistoryFilesLoader.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.time;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.AbstractSelfFeedingLoader;
import org.orekit.data.DataContext;
import org.orekit.data.DataProvidersManager;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
/** Loader for UTC versus TAI history files.
* <p>UTC versus TAI history files contain {@link UTCTAIOffset
* leap seconds} data since.</p>
* <p>The UTC versus TAI history files are recognized thanks to their
* base names, which must match the pattern <code>UTC-TAI.history</code>
* (or <code>UTC-TAI.history.gz</code> for gzip-compressed files)</p>
* <p>Only one history file must be present in the IERS directories
* hierarchy.</p>
* @author Luc Maisonobe
*/
public class UTCTAIHistoryFilesLoader extends AbstractSelfFeedingLoader
implements UTCTAIOffsetsLoader {
/** Supported files name pattern. */
private static final String SUPPORTED_NAMES = "^UTC-TAI\\.history$";
/**
* Build a loader for UTC-TAI history file. This constructor uses the {@link
* DataContext#getDefault() default data context}.
*
* @see #UTCTAIHistoryFilesLoader(DataProvidersManager)
*/
@DefaultDataContext
public UTCTAIHistoryFilesLoader() {
this(DataContext.getDefault().getDataProvidersManager());
}
/**
* Build a loader for UTC-TAI history file.
*
* @param manager provides access to the {@code UTC-TAI.history} file.
*/
public UTCTAIHistoryFilesLoader(final DataProvidersManager manager) {
super(SUPPORTED_NAMES, manager);
}
/** {@inheritDoc} */
@Override
public List<OffsetModel> loadOffsets() {
final UtcTaiOffsetLoader parser = new UtcTaiOffsetLoader(new Parser());
this.feed(parser);
return parser.getOffsets();
}
/** Internal class performing the parsing. */
public static class Parser implements UTCTAIOffsetsLoader.Parser {
/** Regular data lines pattern. */
private Pattern regularPattern;
/** Last line pattern pattern. */
private Pattern lastPattern;
/** Simple constructor.
*/
public Parser() {
// the data lines in the UTC time steps data files have the following form:
// 1966 Jan. 1 - 1968 Feb. 1 4.313 170 0s + (MJD - 39 126) x 0.002 592s
// 1968 Feb. 1 - 1972 Jan. 1 4.213 170 0s + ""
// 1972 Jan. 1 - Jul. 1 10s
// Jul. 1 - 1973 Jan. 1 11s
// 1973 Jan. 1 - 1974 Jan. 1 12s
// ...
// 2006 Jan. 1.- 2009 Jan. 1 33s
// 2009 Jan. 1.- 2012 Jul 1 34s
// 2012 Jul 1 - 35s
// we ignore the non-constant and non integer offsets before 1972-01-01
final String start = "^";
// year group
final String yearField = "\\p{Blank}*((?:\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit})|(?: ))";
// second group: month as a three letters capitalized abbreviation
final StringBuilder builder = new StringBuilder("\\p{Blank}+(");
for (final Month month : Month.values()) {
builder.append(month.getCapitalizedAbbreviation());
builder.append('|');
}
builder.delete(builder.length() - 1, builder.length());
builder.append(")\\.?");
final String monthField = builder.toString();
// day group
final String dayField = "\\p{Blank}+([ 0-9]+)\\.?";
// offset group
final String offsetField = "\\p{Blank}+(\\p{Digit}+)s";
final String separator = "\\p{Blank}*-\\p{Blank}+";
final String finalBlanks = "\\p{Blank}*$";
regularPattern = Pattern.compile(start + yearField + monthField + dayField +
separator + yearField + monthField + dayField +
offsetField + finalBlanks);
lastPattern = Pattern.compile(start + yearField + monthField + dayField +
separator + offsetField + finalBlanks);
}
/** Load UTC-TAI offsets entries read from some file.
*
* {@inheritDoc}
*
* @param input data input stream
* @param name name of the file (or zip entry)
* @exception IOException if data can't be read
*/
@Override
public List<OffsetModel> parse(final InputStream input, final String name)
throws IOException {
final List<OffsetModel> offsets = new ArrayList<>();
final String emptyYear = " ";
int lineNumber = 0;
DateComponents lastDate = null;
String line = null;
int lastLine = 0;
String previousYear = emptyYear;
// set up a reader for line-oriented file
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
// read all file, ignoring not recognized lines
for (line = reader.readLine(); line != null; line = reader.readLine()) {
++lineNumber;
// check matching for regular lines and last line
Matcher matcher = regularPattern.matcher(line);
if (matcher.matches()) {
if (lastLine > 0) {
throw new OrekitException(OrekitMessages.UNEXPECTED_DATA_AFTER_LINE_IN_FILE,
lastLine, name, line);
}
} else {
matcher = lastPattern.matcher(line);
if (matcher.matches()) {
// this is the last line (there is a start date but no end date)
lastLine = lineNumber;
}
}
if (matcher.matches()) {
// build an entry from the extracted fields
String year = matcher.group(1);
if (emptyYear.equals(year)) {
year = previousYear;
}
if (lineNumber != lastLine) {
if (emptyYear.equals(matcher.group(4))) {
previousYear = year;
} else {
previousYear = matcher.group(4);
}
}
final DateComponents leapDay = new DateComponents(Integer.parseInt(year.trim()),
Month.parseMonth(matcher.group(2)),
Integer.parseInt(matcher.group(3).trim()));
final int offset = Integer.parseInt(matcher.group(matcher.groupCount()));
if (lastDate != null && leapDay.compareTo(lastDate) <= 0) {
throw new OrekitException(OrekitMessages.NON_CHRONOLOGICAL_DATES_IN_FILE,
name, lineNumber);
}
lastDate = leapDay;
offsets.add(new OffsetModel(leapDay, offset));
}
}
} catch (NumberFormatException nfe) {
throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
lineNumber, name, line);
}
if (offsets.isEmpty()) {
throw new OrekitException(OrekitMessages.NO_ENTRIES_IN_IERS_UTC_TAI_HISTORY_FILE, name);
}
return offsets;
}
}
}