All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yamcs.utils.TaiUtcConverter Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.yamcs.time.Instant;

import com.google.protobuf.Timestamp;

/**
 * Utility class to convert between TAI and UTC. It reads the UTC-TAI.history (available at
 * http://hpiers.obspm.fr/eoppc/bul/bulc/UTC-TAI.history)
 * 

* It only supports added leap seconds not removed ones (negative leap seconds have never happened and probably never * will). *

* It only works correctly with the times after 1972 when the difference between TAI and UTC is an integer number of * seconds. *

* Most of the code is copied or inspired from the TAI C library http://cr.yp.to/libtai.html * * */ public class TaiUtcConverter { static String UTC_TAI_HISTORY_FN = "UTC-TAI.history"; static final int[] times365 = new int[] { 0, 365, 730, 1095 }; static final int[] times36524 = new int[] { 0, 36524, 73048, 109572 }; static final int[] montab = { 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337 }; /* month length after february is (306 * m + 5) / 10 */ static final int[] PREVIOUS_MONTH_END_DAY = { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; static final int[] PREVIOUS_MONTH_END_DAY_LS = { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }; static final long OE9 = 1_000_000_000; static final long OE6 = 1_000_000; // Timestamp for "0001-01-01T00:00:00Z" static final long PROTOBUF_SECONDS_MIN = -62135596800L; // Timestamp for "9999-12-31T23:59:59Z" static final long PROTOBUF_SECONDS_MAX = 253402300799L; long[] timesecs; // TAI time in seconds when leap seconds are added int diffTaiUtc; // the difference between the TAI and UTC at the last interval // Unprocessed input parsed from UTC-TAI.history file. List lines = new ArrayList<>(); public TaiUtcConverter() throws IOException, ParseException { this(TaiUtcConverter.class.getResourceAsStream("/" + UTC_TAI_HISTORY_FN)); } public TaiUtcConverter(InputStream utcTaiHistory) throws IOException, ParseException { BufferedReader reader = new BufferedReader(new InputStreamReader(utcTaiHistory)); String line = null; // 1974 Jan. 1 - 1975 Jan. 1 13s String dp = "\\s?(\\d+)?\\s+(\\w{3})\\.?\\s+(\\d+)"; Pattern p = Pattern.compile(dp + "\\s*\\.?\\-\\s*(" + dp + ")?\\s*(\\d+)s\\s*"); ArrayList tmp1 = new ArrayList<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy MMM dd", Locale.ENGLISH); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); diffTaiUtc = -1; int lineNum = 0; String prevYear = null; while ((line = reader.readLine()) != null) { lineNum++; Matcher m = p.matcher(line); if (m.matches()) { String year = m.group(1); if (year == null) { year = prevYear; } Date d = sdf.parse(year + " " + m.group(2) + " " + m.group(3)); int ls = Integer.valueOf(m.group(8)); if (diffTaiUtc == -1) { // In 1972 there were 10 leap seconds added at once, // we distribute them over them over the previous 10 days // UTC before 1972 is not defined with leap seconds so it's all approximative for (int i = 1; i < ls; i++) { tmp1.add(d.getTime() - 86400_000 * (ls - i)); } } else if (ls != diffTaiUtc + 1) { throw new RuntimeException("Error reading line " + lineNum + " of UTC-TAI.history: only positive leap seconds are supported"); } tmp1.add(d.getTime()); diffTaiUtc = ls; prevYear = year; lines.add(new ValidityLine(d.getTime(), ls)); } } timesecs = new long[tmp1.size()]; for (int i = 0; i < timesecs.length; i++) { timesecs[i] = tmp1.get(i) / 1000 + diffTaiUtc - timesecs.length + i; } } // converts Modified Julian Day to calendar day private void caldateFromMjd(DateTimeComponents cd, long day) { int year; int month; int yday; year = (int) (day / 146097); day %= 146097L; day += 678881L; while (day >= 146097L) { day -= 146097L; ++year; } /* year * 146097 + day - 678881 is MJD; 0 <= day < 146097 */ /* 2000-03-01, MJD 51604, is year 5, day 0 */ year *= 4; if (day == 146096L) { year += 3; day = 36524; } else { year += day / 36524L; day %= 36524L; } year *= 25; year += day / 1461; day %= 1461; year *= 4; yday = (day < 306) ? 1 : 0; if (day == 1460) { year += 3; day = 365; } else { year += day / 365; day %= 365; } yday += day; day *= 10; month = (int) ((day + 5) / 306); day = (day + 5) % 306; day /= 10; if (month >= 10) { yday -= 306; ++year; month -= 10; } else { yday += 59; month += 2; } cd.year = year; cd.month = month + 1; cd.day = (int) (day + 1); cd.doy = yday + 1; } // converts calendar date to Modified Julian Day // doy is ignored private long caldateToMjd(DateTimeComponents dtc) { int y; int m; int d; d = dtc.day - 678882; m = dtc.month - 1; y = dtc.year; d += 146097L * (y / 400); y %= 400; if (m >= 2) { m -= 2; } else { m += 10; --y; } y += (m / 12); m %= 12; if (m < 0) { m += 12; --y; } d += montab[m]; d += 146097L * (y / 400); y %= 400; if (y < 0) { y += 400; d -= 146097L; } d += times365[y & 3]; y >>= 2; d += 1461L * (y % 25); y /= 25; d += times36524[y & 3]; return d; } Timestamp instantToProtobuf(long t) { return instantToProtobuf(Instant.get(t)); } /** * Converts instant to protobuf timestamp by smearing 24 hours around leap seconds. * * This class limits the range of the passed timestamp to the max valid ranges of the protobuf timestamps: * 0001-01-01T00:00:00Z - 9999-12-31T23:59:59Z *

* A value larger that the max value is changed to the max value and a value smaller than that the minimum is set to * the minimum. * * @param t * @return */ Timestamp instantToProtobuf(Instant instant) { Timestamp.Builder timestamp = Timestamp.newBuilder(); long millis = instant.getMillis(); int picos = instant.getPicos(); long u = millis / 1000; int ls = diffTaiUtc; int nanosec = (int) (1_000_000 * (millis - 1000 * u) + picos / 1000); int i; for (i = timesecs.length - 1; i >= 0; i--) { if (u >= timesecs[i]) { break; } ls--; } if ((i >= 0) && (u - timesecs[i] < 43200)) { ls--; long d = 43200 + u - timesecs[i]; // number of seconds since the smearing starts nanosec -= (d * OE9 + nanosec) / 86401; } else if ((i + 1 < timesecs.length) && (timesecs[i + 1] - u < 43200)) { long d = 43200 + u - timesecs[i + 1]; // number of seconds since the smearing starts nanosec -= (d * OE9 + nanosec) / 86401; } if (nanosec < 0) { nanosec += OE9; ls++; } long sec = u - ls; if (sec > PROTOBUF_SECONDS_MAX) { sec = PROTOBUF_SECONDS_MAX; } else if (sec < PROTOBUF_SECONDS_MIN) { sec = PROTOBUF_SECONDS_MIN; } timestamp.setSeconds(sec); timestamp.setNanos(nanosec); return timestamp.build(); } long protobufToInstant(Timestamp ts) { return protobufToHresInstant(ts).getMillis(); } Instant protobufToHresInstant(Timestamp ts) { int nanosec = ts.getNanos(); long u = ts.getSeconds() + diffTaiUtc; int i; for (i = timesecs.length - 1; i >= 0; i--) { if (u > timesecs[i]) { break; } u--; } if ((i >= 0) && (u - timesecs[i] <= 43200)) { u--; long d = 43200 + u - timesecs[i]; nanosec += (d * OE9 + nanosec) / 86400; } else if ((i + 1 < timesecs.length) && (timesecs[i + 1] - u <= 43200)) { long d = 43200 + u - timesecs[i + 1]; nanosec += (d * OE9 + nanosec) / 86400; } return Instant.get(u * 1000, nanosec * 1000l); } DateTimeComponents instantToUtc(long t) { DateTimeComponents dtc = new DateTimeComponents(); long u; int leap; long s; u = t / 1000; int millisec = (int) (t % 1000); if (millisec < 0) { millisec += 1000; u -= 1; } dtc.millisec = millisec; // leap = leapsecs_sub(&t2); leap = 0; int ls = diffTaiUtc; for (int i = timesecs.length - 1; i >= 0; i--) { if (u > timesecs[i]) { break; } if (u == timesecs[i]) { leap = 1; break; } ls--; } u -= ls; s = u % 86400L; if (s < 0) { s += 86400L; u -= 86400L; } dtc.second = (int) ((s % 60) + leap); s /= 60; dtc.minute = (int) (s % 60); s /= 60; dtc.hour = (int) s; u /= 86400L; long mjd = 40587 + u; caldateFromMjd(dtc, mjd); return dtc; } /** * transforms Instant to Unix time expressed in milliseconds since 1970 * * @param t * @return */ long instantToUnix(long t) { long u = t / 1000; int ls = diffTaiUtc; for (int i = timesecs.length - 1; i >= 0; i--) { if (u >= timesecs[i]) { break; } ls--; } return t - ls * 1000; } /** * Converts UTC to instant. WARNING: DOY is ignored. * * @param dtc * @return */ long utcToInstant(DateTimeComponents dtc) { long day = caldateToMjd(dtc); long s = (long) (dtc.hour * 60 + dtc.minute); s = s * 60 + dtc.second + (day - 40587) * 86400L; int ls = diffTaiUtc; for (int i = timesecs.length - 1; i >= 0; i--) { long u = timesecs[i] - ls + 1; if (s > u) { break; } if ((s < u) || (dtc.second == 60)) { ls--; } } s += ls; return dtc.millisec + 1000 * s; } /** * transforms UNIX time expressed in milliseconds since 1970 to instant * * @param t * @return */ long unixToInstant(long t) { long u = t / 1000; int ls = diffTaiUtc; for (int i = timesecs.length - 1; i >= 0; i--) { if (u >= timesecs[i] - ls + 1) { break; } ls--; } return t + ls * 1000; } public static boolean isLeap(final int year) { return ((year % 4) == 0) && (((year % 400) == 0) || ((year % 100) != 0)); } public static class DateTimeComponents { int year; int month; // month starting with 1 int day; int hour; int minute; int second; int millisec; int doy; /** * Constructs a new DateTimeComponents * * @param year * @param month * @param day * @param hour * @param minute * @param second * @param millisec */ public DateTimeComponents(int year, int month, int day, int hour, int minute, int second, int millisec) { this.year = year; this.month = month; this.day = day; this.hour = hour; this.minute = minute; this.second = second; this.millisec = millisec; } private DateTimeComponents() { } public DateTimeComponents(int year, int doy, int hour, int minute, int second, int millisec) { this.year = year; this.doy = doy; this.hour = hour; this.minute = minute; this.second = second; this.millisec = millisec; if (isLeap(year)) { this.month = (doy < 32) ? 1 : (10 * doy + 313) / 306; this.day = doy - PREVIOUS_MONTH_END_DAY_LS[this.month]; } else { this.month = (doy < 32) ? 1 : (10 * doy + 323) / 306; this.day = doy - PREVIOUS_MONTH_END_DAY[this.month]; } } public int getYear() { return year; } public int getDoy() { return doy; } public int getMonth() { return month; } public int getDay() { return day; } public int getHour() { return hour; } public int getMinute() { return minute; } public int getSecond() { return second; } public int getMillisec() { return millisec; } @Override public String toString() { return "DateTimeComponents [year=" + year + ", month=" + month + ", day=" + day + ", hour=" + hour + ", minute=" + minute + ", second=" + second + ", millisec=" + millisec + ", doy=" + doy + "]"; } public String toIso8860String() { return String.format("%04d-%02d-%02dT%02d:%02d:%02d.%03d", year, month, day, hour, minute, second, millisec); } } public static final class ValidityLine { public final long unixMillis; public final int seconds; // TAI - UTC seconds private ValidityLine(long unixMillis, int seconds) { this.unixMillis = unixMillis; this.seconds = seconds; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy