/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.das2.datum; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * TT2000 converter that takes leap seconds into account from us2000. * @author jbf */ public class LeapSecondsConverter extends UnitsConverter { private static Logger logger= LoggerManager.getLogger("das2.datum.uc"); public static final int T1972_LEAP = 10; private static List<Long> leapSeconds; // number of leap seconds is the index, value is in tt2000. private static List<Double> withoutLeapSeconds; private static long lastUpdateMillis=0; // the following six are a cache... private static double us2000_st= -1; private static double us2000_en= -1; private static int us2000_c=-1; private static long tt2000_st= -1; private static long tt2000_en= -1; private static int tt2000_c=-1; /** * This reads in the table of leap seconds. This will need to be updated once every six months, when * a new leap second is announced. * @throws IOException * @throws MalformedURLException * @throws NumberFormatException * @see TimeUtil#tt2000s */ private static void updateLeapSeconds() throws IOException, MalformedURLException, NumberFormatException { URL url = LeapSecondsConverter.class.getResource("/orbits/CDFLeapSeconds.txt"); logger.log(Level.FINE, "try reading leap seconds from {0}", url); InputStream in; try { in= url.openStream(); } catch ( IOException ex ) { logger.log(Level.INFO,"unable to read internal leap seconds file: {0}", url); LoggerManager.getLogger("das2.url").log(Level.FINE, "openStream {0}", url); url= new URL("https://cdf.gsfc.nasa.gov/html/CDFLeapSeconds.txt"); in= url.openStream(); logger.log(Level.FINE, "Using remote copy of leap seconds file at {0}", url); } try (BufferedReader r = new BufferedReader(new InputStreamReader(in))) { String s = ""; leapSeconds = new ArrayList(50); withoutLeapSeconds = new ArrayList(50); String lastLine= s; boolean firstLine= true; while ( true ) { s = r.readLine(); if ( firstLine ) { if ( s==null ) throw new RuntimeException("Leap seconds file is empty: "+url ); if ( !s.startsWith(";") ) throw new RuntimeException( "Leap seconds file should start with semicolon (;): "+url); firstLine= false; } if (s == null) { logger.log( Level.FINE, "Last leap second read from {0} {1}", new Object[]{url, lastLine}); break; } if (s.startsWith(";")) { continue; } String[] ss = s.trim().split("\\s+", -2); if (ss[0].compareTo("1972") < 0) { continue; } int iyear = Integer.parseInt(ss[0]); int imonth = Integer.parseInt(ss[1]); int iday = Integer.parseInt(ss[2]); int ileap = (int) (Double.parseDouble(ss[3])); // I thought these could only be whole numbers double us2000 = TimeUtil.createTimeDatum(iyear, imonth, iday, 0, 0, 0, 0).doubleValue(Units.us2000); leapSeconds.add( ((long) us2000) * 1000L - 43200000000000L + (long) ( ileap+32 ) * 1000000000 ); withoutLeapSeconds.add( us2000 ); } leapSeconds.add( Long.MAX_VALUE ); withoutLeapSeconds.add( Double.MAX_VALUE ); lastUpdateMillis = System.currentTimeMillis(); } } boolean us2000ToTT2000; public LeapSecondsConverter( boolean us2000ToTT2000 ) { this.us2000ToTT2000= us2000ToTT2000; if ( us2000ToTT2000 ) { inverse= new LeapSecondsConverter( !us2000ToTT2000 ); inverse.inverse= this; } } /** * calculate the number of leap seconds in the us2000 time. For example, * <pre> * {@code * print getLeapSecondCountForUs2000( Units.us2000.parse('1972-01-01T00:00Z').doubleValue(Units.us2000) ) # results in 10 * print getLeapSecondCountForUs2000( Units.us2000.parse('2017-01-01T00:00Z').doubleValue(Units.us2000) ) # results in 37 * } * </pre> * This is intended to replicate the table https://cdf.gsfc.nasa.gov/html/CDFLeapSeconds.txt * @param us2000 the time in us2000, which include the leap seconds. * @return the number of leap seconds for the time. * @throws IOException */ public synchronized static int getLeapSecondCountForUs2000( double us2000 ) throws IOException { if ( System.currentTimeMillis()-lastUpdateMillis > 86400000 ) { updateLeapSeconds(); } if ( us2000 < withoutLeapSeconds.get(0) ) { return 0; } for ( int i=0; i<withoutLeapSeconds.size()-1; i++ ) { if ( withoutLeapSeconds.get(i) <= us2000 && ( i==withoutLeapSeconds.size()-1 || us2000 < withoutLeapSeconds.get(i+1) ) ) { us2000_st= withoutLeapSeconds.get(i); us2000_en= withoutLeapSeconds.get(i+1); us2000_c= i+T1972_LEAP; return i+10; } } logger.severe("code shouldn't get to this point: implementation error..."); throw new RuntimeException("code shouldn't get to this point: implementation error..."); } /** * calculate the number of leap seconds in the tt2000 time. For example, * <pre> * {@code * print getLeapSecondCountForTT2000( Units.cdfTT2000.parse('1972-01-01T00:00Z').doubleValue(Units.cdfTT2000) ) # results in 10 * print getLeapSecondCountForTT2000( 0 ) # results in 32 * print getLeapSecondCountForTT2000( Units.cdfTT2000.parse('2017-01-01T00:00Z').doubleValue(Units.cdfTT2000) ) # results in 37 * } * </pre> * This is intended to replicate the table https://cdf.gsfc.nasa.gov/html/CDFLeapSeconds.txt * @param tt2000 the time in tt2000, which include the leap seconds. * @return the number of leap seconds for the time. * @throws IOException */ public synchronized static int getLeapSecondCountForTT2000( long tt2000 ) throws IOException { //System.err.println( "since 2015-06-30T23:58:00 (sec): " + ( ( tt2000 - 488980747184000000L ) / 1e9 ) ); if ( System.currentTimeMillis()-lastUpdateMillis > 86400000 ) { updateLeapSeconds(); } if ( tt2000 < leapSeconds.get(0) ) { return 0; } int i=0; for ( i=0; i<leapSeconds.size()-1; i++ ) { if ( leapSeconds.get(i) <= tt2000 && ( i==leapSeconds.size()-1 || tt2000 < leapSeconds.get(i+1) ) ) { tt2000_st= leapSeconds.get(i); tt2000_en= leapSeconds.get(i+1); tt2000_c= i+10; //System.err.println( "since 2015-06-30T23:58:00 (sec): " + ( ( tt2000 - 488980747184000000L ) / 1e9 ) + " result: "+tt2000_c ); return i+10; } } return i+9; // this is when we receive Long.MAX_VALUE, presumably coming from a valid_max. } @Override public UnitsConverter getInverse() { return inverse; } @Override public synchronized double convert(double value) { try { int leapSec; if ( this.us2000ToTT2000 ) { if ( us2000_st <= value && value<us2000_en ) { leapSec= us2000_c; } else { leapSec= getLeapSecondCountForUs2000( value ); } return ( value * 1000 - 43200000000000L ) + ( leapSec - 32 + 64.184 ) * 1000000000L; } else { if ( tt2000_st <= value && value<tt2000_en ) { // DANGER--rounding... leapSec= tt2000_c; } else { leapSec= getLeapSecondCountForTT2000( (long)value ); } return ( ( value - ( leapSec - 32 + 64.184 ) * 1000000000L ) + 43200000000000L ) / 1000.; // tt = TAI + 32.184 s = UTC + 32 + 32.184 = UTC + 64.184s, see jbf@space.physics.uiowa.edu email at 2012-02-23 12:57 CST } } catch ( IOException ex ) { throw new RuntimeException("LeapSeconds file not available. This should never happen since there is a leapSeconds file within code.",ex); } } }