/*
 * Copyright 1996-2016 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gsfc.nssdc.cdf.util;

import gsfc.nssdc.cdf.CDFException;
import gsfc.nssdc.cdf.CDF;
import gsfc.nssdc.cdf.util.Epoch;
import gsfc.nssdc.cdf.util.Epoch16;
import java.text.NumberFormat;
import java.text.DecimalFormat;

import java.util.*;
import java.text.*;
import java.io.*;
import java.lang.reflect.*;

/**
 * This class contains the handy utility routines (methods) called by the
 * CDF applications to handle epoch data of CDF's CDF_TIME_TT2000 data type.
 *
 * @version 1.0
 * @version 2.0 01/05/15  Add a new leap second for 2015/07/01.
 * @version 3.0 10/20/16  Add a new leap second for 2016/01/01.
 *
 * @author Mike Liu, Adnet Systems <BR>
 */

public class CDFTT2000 implements gsfc.nssdc.cdf.CDFConstants {

    private static int JulianDateJ2000_12h =    2451545;
    private static int J2000Since0AD12h =       730485 /* 0-1-1 */;
    private static double J2000Since0AD12hSec = 63113904000.0;
    private static double J2000Since0AD12hMilsec = 63113904000000.0;
                                /* 86400000.0 * 730485 */
    private static double J2000LeapSeconds =    32.0;
    private static double dT =                 32.184;
    private static long dTinNanoSecs =         32184000000L;
    private static double MJDbase =             2400000.5;
    private static long SECinNanoSecs =         1000000000L;
    private static double SECinNanoSecsD =      1000000000.0;
    private static long DAYinNanoSecs =         86400000000000L;
    private static long HOURinNanoSecs =        3600000000000L;
    private static long MINUTEinNanoSecs =      60000000000L;
    private static long T12hinNanoSecs =        43200000000000L;
    /* Julian days for 1707-09-22 and 2292-04-11, the valid TT2000 range. */
    private static long JDY17070922 =           2344793;
    private static long JDY22920411 =           2558297;


/* Number of Delta(dAT) expressions before leap seconds were introduced */
    private static int NERA1 = 14;

/* The following session needs to be modified when a leap second is added   */
/* ======================================================================   */
/* A positive second is added for 2017-01-01                       */

    private static int LASTLEAPSECONDDAY = 20170101;

    private static double [][] LTS = new double[][] {
      { 1960,  1,  1,  1.4178180, 37300.0, 0.0012960 },
      { 1961,  1,  1,  1.4228180, 37300.0, 0.0012960 },
      { 1961,  8,  1,  1.3728180, 37300.0, 0.0012960 },
      { 1962,  1,  1,  1.8458580, 37665.0, 0.0011232 },
      { 1963, 11,  1,  1.9458580, 37665.0, 0.0011232 },
      { 1964,  1,  1,  3.2401300, 38761.0, 0.0012960 },
      { 1964,  4,  1,  3.3401300, 38761.0, 0.0012960 },
      { 1964,  9,  1,  3.4401300, 38761.0, 0.0012960 },
      { 1965,  1,  1,  3.5401300, 38761.0, 0.0012960 },
      { 1965,  3,  1,  3.6401300, 38761.0, 0.0012960 },
      { 1965,  7,  1,  3.7401300, 38761.0, 0.0012960 },
      { 1965,  9,  1,  3.8401300, 38761.0, 0.0012960 },
      { 1966,  1,  1,  4.3131700, 39126.0, 0.0025920 },
      { 1968,  2,  1,  4.2131700, 39126.0, 0.0025920 },
      { 1972,  1,  1, 10.0,           0.0, 0.0       },
      { 1972,  7,  1, 11.0,           0.0, 0.0       },
      { 1973,  1,  1, 12.0,           0.0, 0.0       },
      { 1974,  1,  1, 13.0,           0.0, 0.0       },
      { 1975,  1,  1, 14.0,           0.0, 0.0       },
      { 1976,  1,  1, 15.0,           0.0, 0.0       },
      { 1977,  1,  1, 16.0,           0.0, 0.0       },
      { 1978,  1,  1, 17.0,           0.0, 0.0       },
      { 1979,  1,  1, 18.0,           0.0, 0.0       },
      { 1980,  1,  1, 19.0,           0.0, 0.0       },
      { 1981,  7,  1, 20.0,           0.0, 0.0       },
      { 1982,  7,  1, 21.0,           0.0, 0.0       },
      { 1983,  7,  1, 22.0,           0.0, 0.0       },
      { 1985,  7,  1, 23.0,           0.0, 0.0       },
      { 1988,  1,  1, 24.0,           0.0, 0.0       },
      { 1990,  1,  1, 25.0,           0.0, 0.0       },
      { 1991,  1,  1, 26.0,           0.0, 0.0       },
      { 1992,  7,  1, 27.0,           0.0, 0.0       },
      { 1993,  7,  1, 28.0,           0.0, 0.0       },
      { 1994,  7,  1, 29.0,           0.0, 0.0       },
      { 1996,  1,  1, 30.0,           0.0, 0.0       },
      { 1997,  7,  1, 31.0,           0.0, 0.0       },
      { 1999,  1,  1, 32.0,           0.0, 0.0       },
      { 2006,  1,  1, 33.0,           0.0, 0.0       },
      { 2009,  1,  1, 34.0,           0.0, 0.0       },
      { 2012,  7,  1, 35.0,           0.0, 0.0       },
      { 2015,  7,  1, 36.0,           0.0, 0.0       },
      { 2017,  1,  1, 37.0,           0.0, 0.0       }
     };

/* Pre-computed times from CDF_TT2000_from_UTC_parts for days in LTS table  */
/* To add a new leap second entry: Use the old table to generate the        */
/* nanoseconds for the new date and add/substract one second (1000000000LL) */
/* to it, depending on positive/negative leap second.                       */
/* Exp. positive leap second...                                             */
/*      23h 59m 58s => 23h 59m 59s => 23h 59m 60s => 00h 00m 00s            */
/* Exp. negative leap second...                                             */
/*      23h 59m 57s => 23h 59m 58s => 00h 00m 00s => 00h 00m 01s            */

    private static long [] NST2 = {
         0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,
         0L, 0L, 0L, 0L,
         -883655957816000000L, -867931156816000000L, -852033555816000000L,
         -820497554816000000L, -788961553816000000L, -757425552816000000L,
         -725803151816000000L, -694267150816000000L, -662731149816000000L,
         -631195148816000000L, -583934347816000000L, -552398346816000000L,
         -520862345816000000L, -457703944816000000L, -378734343816000000L,
         -315575942816000000L, -284039941816000000L, -236779140816000000L,
         -205243139816000000L, -173707138816000000L, -126273537816000000L,
          -79012736816000000L,  -31579135816000000L,  189345665184000000L,
          284040066184000000L,  394372867184000000L,  488980868184000000L,
          536500869184000000L
    };
/* ====================================================================== */


/* Number of Delta(AT) in static table */
    private static int NDAT = Array.getLength(LTS);
    private static double [][] LTD;
    private static long [] NST;
    private static int [] NSTb;
    private static int entryCnt;
    private static int lastLeapSecondDay;

    private static long doys1[] = {31,59,90,120,151,181,212,243,273,304,334,365};
    private static long doys2[] = {31,60,91,121,152,182,213,244,274,305,335,366};
    private static long daym1[] = {31,28,31,30,31,30,31,31,30,31,30,31};
    private static long daym2[] = {31,29,31,30,31,30,31,31,30,31,30,31};

    private static boolean tableChecked = false;
    private static int openCDF64s = 0;
    private static double toPlus = 0.0;
    private static long currentDay = -1;
    private static double currentLeapSeconds;
    private static double currentJDay;
    private static int MAX_ePART_LEN =  25;
    private static int fromFile;

    private static String MonthToken (long month)
    {
       switch ((int)month) {
         case 1: return "Jan";
         case 2: return "Feb";
         case 3: return "Mar";
         case 4: return "Apr";
         case 5: return "May";
         case 6: return "Jun";
         case 7: return "Jul";
         case 8: return "Aug";
         case 9: return "Sep";
         case 10: return "Oct";
         case 11: return "Nov";
         case 12: return "Dec";
       }
       return "???";
    }

    private static int MonthNumber(String month)
    {
       String tmp = month.toLowerCase();
       if (tmp.equals ("jan")) return 1;
       else if (tmp.equals ("feb")) return 2;
       else if (tmp.equals ("mar")) return 3;
       else if (tmp.equals ("apr")) return 4;
       else if (tmp.equals ("may")) return 5;
       else if (tmp.equals ("jun")) return 6;
       else if (tmp.equals ("jul")) return 7;
       else if (tmp.equals ("aug")) return 8;
       else if (tmp.equals ("sep")) return 9;
       else if (tmp.equals ("oct")) return 10;
       else if (tmp.equals ("nov")) return 11;
       else if (tmp.equals ("dec")) return 12;
       else return 0;
    }


    private static double JulianDay12h (long y, long m, long d)
    {
       if (m == 0) m = 1;
       return (double) (367*y-7*(y+(m+9)/12)/4-3*((y+(m-9)/7)/100+1)/4+275*m/9+d+1721029);
    }

    private static int ValidateYMD (long yy, long mm, long dd)
    {
      double jday;
      if (yy <= 0 || mm < 0 || dd < 0) return 0;
      jday = JulianDay12h(yy, mm, dd);
      /* Y-M-D should be in the 1707-09-23 and 2292-04-9 range. */
      if (jday < JDY17070922 || jday > JDY22920411) return 0;
      return 1;
    }

    private static long[] DatefromJulianDay (double julday)
    {
       long i, j, k, l, n;
       l=68569 + (long) julday;
       n=4*l/146097;
       l=l-(146097*n+3)/4;
       i=4000*(l+1)/1461001;
       l=l-1461*i/4+31;
       j=80*l/2447;
       k=l-2447*j/80;
       l=j/11;
       j=j+2-12*l;
       i=100*(n-49)+i+l;
       long[] day = new long[3];
       day[0] = i;
       day[1] = j;
       day[2] = k;
       return day; 
    }

    private static int scanUTCstring (String tt2000) 
    {
       int len = tt2000.length();
       if ((len == TT2000_3_STRING_LEN) ||
           ((len >= 19) &&
            (tt2000.charAt(10) == 'T' || tt2000.charAt(10) == 't' ||
             tt2000.charAt(10) == ' ' || tt2000.charAt(10) == '/') &&
            (tt2000.charAt(len-1) != 'Z'))) return 3;
       else if ((len <= TT2000_0_STRING_LEN) &&
                (tt2000.charAt(11) == ' ')) return 0;
       else if ((len == TT2000_4_STRING_LEN) ||
                ((len >= 19) &&
                 (tt2000.charAt(10) == 'T' || tt2000.charAt(10) == 't' ||
                  tt2000.charAt(10) == ' ' || tt2000.charAt(10) == '/') &&
                 (tt2000.charAt(len-1) == 'Z'))) return 4;
       else if ((len == TT2000_1_STRING_LEN) ||
                  (len > 9 && tt2000.charAt(8) == '.')) return 1;
       else if (len == TT2000_2_STRING_LEN)  return 2;
       else if (len == (TT2000_0_STRING_LEN+1) && 
                tt2000.charAt(11) == ' ' &&
                (tt2000.endsWith("Z") || tt2000.endsWith("z"))) return 0;
       return -1;
    }

    private static long[] EPOCHbreakdownTT2000 (double epoch)
    {
       long jd,i,j,k,l,n;
       double second_AD, minute_AD, hour_AD, day_AD;
       second_AD = epoch;
       minute_AD = second_AD / 60.0;
       hour_AD = minute_AD / 60.0;
       day_AD = hour_AD / 24.0;

       jd = 1721060 + (long) day_AD;
       l=jd+68569;
       n=4*l/146097;
       l=l-(146097*n+3)/4;
       i=4000*(l+1)/1461001;
       l=l-1461*i/4+31;
       j=80*l/2447;
       k=l-2447*j/80;
       l=j/11;
       j=j+2-12*l;
       i=100*(n-49)+i+l;

       long[] date = new long[6];    
       date[0]= i;
       date[1]= j;
       date[2]= k;
       date[3]= (long) (hour_AD % 24.0);
       date[4]= (long) (minute_AD % 60.0);
       date[5]= (long) (second_AD % 60.0);
       return date;
    }

    private static void appendFractionPart (StringBuffer encoded,
                                            double fraction,
                                            int defaultWidth,
                                            String modifier)
    {                       
        StringBuffer ePart = new StringBuffer(MAX_ePART_LEN+1);
        int width;          
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        NumberFormat df;
        StringBuffer format = new StringBuffer();

        nf.setParseIntegerOnly(true);
        if (!modifier.equals("")) {            // modifier is present
            try {       
                width = nf.parse(modifier).intValue();
                if (width < 1) {
                    encoded.append("?");
                    System.out.println("append ? (3)");
                    return;
                }
            } catch (java.text.ParseException e) {
                encoded.append("?");
                System.out.println("append ? (4)");
                return;
            }
        } else
            width = defaultWidth;           

        for (int i = 0; i< width + 2; i++)
            format.append("0");

        format.append(".");

        for (int i = 0; i < width; i++)
            format.append("0");

        df = new DecimalFormat(format.toString());
        ePart.append(df.format(fraction));

        // If the encoded value was rounded up to 1.000..., then replace 
        // all of the digits after the decimal with `9's before appending.
        if (ePart.charAt(0) == '1') {
            for (int i = 0; i < width; i++) ePart.setCharAt(i+2, '9');
        }
        String ePartStr = ePart.toString();
        char sp = new DecimalFormat().getDecimalFormatSymbols().getDecimalSeparator();
        appendPart(encoded,
                   ePartStr.substring(ePartStr.indexOf(sp)+1),
                   width,false);
    }

    /**
     * Will append an integer to encoded.
     */
    private static void appendIntegerPart(StringBuffer encoded,
                                          long integer,
                                          int defaultWidth,
                                          boolean defaultLeading0,
                                          String modifier)
    {
        StringBuffer ePart = new StringBuffer(MAX_ePART_LEN+1);
        char [] modArray = modifier.toCharArray();
        int width;
        boolean leading0;
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
        nf.setParseIntegerOnly(true);
        if (!modifier.equals("")) {
            try {
                width = nf.parse(modifier).intValue();
                if (width < 0) {
                    encoded.append("?");
                    System.out.println("append ? (5)");
                    return;
                }
                leading0 = (modArray[0] == '0');
            } catch (java.text.ParseException e) {
                encoded.append("?");
                System.out.println("append ? (6)");
                return;
            }
        } else {
            width = defaultWidth;
            leading0 = defaultLeading0;
        }
        ePart.append(""+integer);
        appendPart(encoded, ePart.toString(), width, leading0);
    }

    /**
     * Append ePart to encoded.
     *
     * @param encoded The encoded epoch string.
     * @param ePart The part to append to encoded.
     * @param width The string length that the ePart should occupy. A width
     *        of zero indicates that the length of ePart should be used.
     * @param leading0 If true, that pad ePart with leading zeros.
     */            
    private static void appendPart (StringBuffer encoded,
                                    String ePart,
                                    int width,
                                    boolean leading0)
    {
        int i;
        if (width == 0) {                 
            encoded.append(ePart);        
        } else {                          
            int length = ePart.length();  
            if (length > width) {
                for (i = 0; i < width; i++) 
                    encoded.append("*");
            } else {
                int pad = width - length;
                if (pad > 0) {
                    for (i = 0; i < pad; i++)
                        encoded.append((leading0 ? "0" : " "));
                }
                encoded.append(ePart);
            }   
        }           
    }               

    /**
     *
     * Find the environment variable "CDF_LEAPSECONDSTABLE" that is
     * defined for the leap seconds table for CDF.
     *
     * @return the leap seconds environment variable if defined
     *         null if not.
     */

    public static String getLeapSecondsTableEnvVar()
    {
          return System.getenv("CDF_LEAPSECONDSTABLE");
    }

    private static void LoadLeapSecondsTable()
    {
      if (!tableChecked) {
        fromFile = 0;
        String table = null;
        int ix, im, count = 0;
        tableChecked = true;
        try {
          table = CDFTT2000.getLeapSecondsTableEnvVar();
          if (table != null) {
            ArrayList[] myList = new ArrayList[6]; 
            for (ix = 0; ix < 6; ++ix)
              myList[ix] =  new ArrayList();
            FileInputStream fstream = new FileInputStream(table);
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
            String strLine;
            while ((strLine = br.readLine()) != null)   {
              if (strLine.charAt(0) == ';') continue;
              StringTokenizer stt = new StringTokenizer(strLine, " ");
              if (stt.countTokens() != 6) continue;
              im = 0;
              ++count;
              while (stt.hasMoreElements()) {
                myList[im].add((String)stt.nextToken());
                ++im;
              } 
            }
            in.close();
            LTD = new double[count][6];
            for (im = 0; im < count; ++im) {
              for (ix = 0; ix < 6; ++ix) 
                LTD[im][ix] = Double.valueOf((String)(myList[ix].get(im)));
            }
            entryCnt = count;
            fromFile = 1;
          }
        } catch (Exception xx) { }
        if (fromFile == 0) {
          LTD = new double[NDAT][6];
          for (ix = 0; ix < NDAT; ++ix) {
            LTD[ix][0] = LTS[ix][0];
            LTD[ix][1] = LTS[ix][1];
            LTD[ix][2] = LTS[ix][2];
            LTD[ix][3] = LTS[ix][3];
            LTD[ix][4] = LTS[ix][4];
            LTD[ix][5] = LTS[ix][5];
          }
          entryCnt = NDAT;
        }
      }
    }

    private static void LoadLeapNanoSecondsTable ()
    {
       int ix;
       double diffLeap;
       if (LTD == null) LoadLeapSecondsTable ();
       NST = new long[entryCnt];
       NSTb = new int[entryCnt];
       if (fromFile == 0) {
         System.arraycopy(NST2, 0, NST, 0, NST2.length);
         lastLeapSecondDay = LASTLEAPSECONDDAY;
       } else {
         if (entryCnt <= NST2.length &&
             LTD[entryCnt-1][0] == LTS[entryCnt-1][0]) {
           System.arraycopy(NST2, 0, NST, 0, entryCnt /*NST2.length*/);
           lastLeapSecondDay = LASTLEAPSECONDDAY;
         } else {
           try {
             for (ix = NERA1; ix < entryCnt; ++ix) {
               NST[ix] = CDFTT2000.fromUTCparts(LTD[ix][0], LTD[ix][1],
                                                LTD[ix][2], 0.0, 0.0, 0.0,
                                                0.0, 0.0, 0.0);
               if (ix == (entryCnt - 1))
                 lastLeapSecondDay = (int) LTD[ix][0] * 10000 +
                                     (int) LTD[ix][1] * 100 +
                                     (int) LTD[ix][2];
             }
           } catch (Exception ex) {
           }
         }
       }
       for (ix = 0; ix < entryCnt; ++ix)
         NSTb[ix] = 0;
       for (ix = entryCnt-1; ix > 0; --ix) {
         diffLeap = LTD[ix][3] - LTD[ix-1][3];
         if (diffLeap == 1.0) NSTb[ix] = 1;
         else if (diffLeap == -1.0) NSTb[ix] = -1;
       }
    }

    /**
     * 
     * Find the leap seconds from a given, UTC-based year/month/day.
     *
     * @param year the year
     * @param month the month
     * @param day the day
     * @return the leap second value (TAI-UTC)
     */

    public static double LeapSecondsfromYMD (long year, long month, long day)
    {
       int i, j;
       long m, n;
       double da;
       if (LTD == null) LoadLeapSecondsTable ();
       j = -1;
       m = 12 * year + month;
       for (i = entryCnt-1; i >=0; --i) {
         n = (long) (12 * LTD[i][0] + LTD[i][1]);
         if (m >= n) {
           j = i;
           break;
         }
       }
       if (j == -1) return 0.0;
       da = LTD[j][3];
       /* If pre-1972, adjust for drift. */
       if (j < NERA1) {
         double jda;
         jda = JulianDay12h (year, month, day);
         da += ((jda - MJDbase) - LTD[j][4]) * LTD[j][5];
       }
       return da;
    }

    static double[] LeapSecondsfromJ2000 (long nanosecs)
    {
      int i, j;
      double[] da = new double[2];
      da[0] = 0.0;
      da[1] = 0.0;
      if (NST == null) LoadLeapNanoSecondsTable();
      j = -1;
      for (i = entryCnt-1; i >=NERA1; --i) {
        if (nanosecs >= NST[i]) {
          j = i;
          if (i < (entryCnt - 1)) {
            /* Check for time following on the leap second. */
            if ((nanosecs + (long) (NSTb[i+1]*1000000000L)) >= NST[i+1])
              da[1] = (double) NSTb[i+1];
          }
          break;
        }
      }
      if (j == -1) return da; /* Pre 1972 .... do it later.... */
      da[0] = LTD[j][3];
      return da;
    }

    /**
     * 
     * Breaks a TT2000 epoch value down into its full, UTC-based date/time
     * component parts - new name as toUTCparts.
     *
     * @param nanoSecSinceJ2000 the epoch value, in nanoseconds since J2000, 
     *                          to break down
     * @return an array of long containing the epoch parts:
     *  <TABLE SUMMARY="" BORDER="0">
     *    <TR><TD ALIGN="CENTER">Index</TD><TD ALIGN="CENTER">Part</TD></TR>
     *    <TR><TD>0</TD><TD>year</TD></TR>
     *    <TR><TD>1</TD><TD>month</TD></TR>
     *    <TR><TD>2</TD><TD>day</TD></TR>
     *    <TR><TD>3</TD><TD>hour</TD></TR>
     *    <TR><TD>4</TD><TD>minute</TD></TR>
     *    <TR><TD>5</TD><TD>second</TD></TR>
     *    <TR><TD>6</TD><TD>millisecond</TD></TR>
     *    <TR><TD>7</TD><TD>microsecond</TD></TR>
     *    <TR><TD>8</TD><TD>nanosecond</TD></TR>
     *  </TABLE>
     */

    public static long[] breakdownTT2000 (long nanoSecSinceJ2000) 
    {
	return CDFTT2000.toUTCparts (nanoSecSinceJ2000);
    }

    /**
     * 
     * Breaks a TT2000 epoch value down into its full, UTC-based date/time
     * component parts.
     *
     * @param nanoSecSinceJ2000 the epoch value, in nanoseconds since J2000, 
     *                          to break down
     * @return an array of long containing the epoch parts:
     *  <TABLE SUMMARY="" BORDER="0">
     *    <TR><TD ALIGN="CENTER">Index</TD><TD ALIGN="CENTER">Part</TD></TR>
     *    <TR><TD>0</TD><TD>year</TD></TR>
     *    <TR><TD>1</TD><TD>month</TD></TR>
     *    <TR><TD>2</TD><TD>day</TD></TR>
     *    <TR><TD>3</TD><TD>hour</TD></TR>
     *    <TR><TD>4</TD><TD>minute</TD></TR>
     *    <TR><TD>5</TD><TD>second</TD></TR>
     *    <TR><TD>6</TD><TD>millisecond</TD></TR>
     *    <TR><TD>7</TD><TD>microsecond</TD></TR>
     *    <TR><TD>8</TD><TD>nanosecond</TD></TR>
     *  </TABLE>
     */

    public static long[] toUTCparts (long nanoSecSinceJ2000) 
    {
       if (nanoSecSinceJ2000 == FILLED_TT2000_VALUE)
         return new long[] {9999, 12, 31, 23, 59, 59, 999, 999, 999};
       if (nanoSecSinceJ2000 == DEFAULT_TT2000_PADVALUE) 
         return new long[] {0, 1, 1, 0, 0, 0, 0, 0, 0};
       double epoch, tmp1, offsecs;
       long secSinceJ2000, t2, t3;
       long nansec, milsec;
       double ye, ym, da;
       long ye0, mo0, da0, ho0, mi0, se0, ml0;
       long ye1, mo1, da1, ho1, mi1, se1, ml1, ma1, na1;
       long[] date0, date1;
       double dat0;
       double[] datx;
       toPlus = 0.0;
       t3 = nanoSecSinceJ2000;
       datx = LeapSecondsfromJ2000 (nanoSecSinceJ2000);
       if (nanoSecSinceJ2000 > 0) { /* try to avoid overflow (substraction first) */
         secSinceJ2000 = (long) ((double)nanoSecSinceJ2000/SECinNanoSecsD);
         nansec = (long) (nanoSecSinceJ2000 - secSinceJ2000 * SECinNanoSecs);
         secSinceJ2000 -= 32; /* secs portion in dT */
         secSinceJ2000 += 43200; /* secs in 12h */
         nansec -= 184000000L; /* nanosecs portion in dT */
       } else { /* try to avoid underflow (addition first) */
         nanoSecSinceJ2000 += T12hinNanoSecs; /* 12h in nanosecs */
         nanoSecSinceJ2000 -= dTinNanoSecs /* dT in nanosecs */;
         secSinceJ2000 = (long) ((double)nanoSecSinceJ2000/SECinNanoSecsD);
         nansec = (long) (nanoSecSinceJ2000 - secSinceJ2000 * SECinNanoSecs);
       }

       if (nansec < 0) {
         nansec = SECinNanoSecs + nansec;
         --secSinceJ2000;
       }
       t2 = secSinceJ2000 * SECinNanoSecs + nansec;

       if (datx[0] > 0.0) { /* Post-1972.... */
         secSinceJ2000 -= (long) datx[0];
         epoch = J2000Since0AD12hSec + secSinceJ2000;
         if (datx[1] == 0.0 || datx[1] == -1.0)
           date1 = EPOCHbreakdownTT2000 (epoch);
         else {
           epoch -= 1;
           date1 = EPOCHbreakdownTT2000 (epoch);
           date1[5] += 1;
         }
         ye1 = date1[0];
         mo1 = date1[1];
         da1 = date1[2];
         ho1 = date1[3];
         mi1 = date1[4];
         se1 = date1[5];
       } else { /* Pre-1972.... */
         long tmpNanosecs;
         long[] xdate0, xdate1;
         epoch = secSinceJ2000 + J2000Since0AD12hSec;
         /* First guess */
         xdate1 = EPOCHbreakdownTT2000 (epoch);
         try {
           tmpNanosecs = CDFTT2000.fromUTCparts ((double)xdate1[0],
                                                 (double)xdate1[1],
                                                 (double)xdate1[2],
                                                 (double)xdate1[3],
                                                 (double)xdate1[4],
                                                 (double)xdate1[5],
                                                 0.0, 0.0, nansec);
         } catch (CDFException ex) {
           tmpNanosecs = 0L;
         }
         if (tmpNanosecs != t3) {
           long tmpx, tmpy;
           dat0 = LeapSecondsfromYMD (xdate1[0], xdate1[1], xdate1[2]);
           tmpx = t2 - (long) (dat0 * SECinNanoSecs);
           tmpy = (long) ((double)tmpx/SECinNanoSecsD);
           nansec = (long) (tmpx - tmpy * SECinNanoSecs);
           if (nansec < 0) {
             nansec = SECinNanoSecs + nansec;
             --tmpy;
           }
           epoch = (double) tmpy + J2000Since0AD12hSec;
           /* Second guess */
           xdate1 = EPOCHbreakdownTT2000 (epoch);
           try {
             tmpNanosecs = CDFTT2000.fromUTCparts ((double)xdate1[0],
                                                   (double)xdate1[1],
                                                   (double)xdate1[2],
                                                   (double)xdate1[3],
                                                   (double)xdate1[4],
                                                   (double)xdate1[5], 
                                                   0.0, 0.0, nansec);
           } catch (CDFException ex) {
             tmpNanosecs = 0L;
           }
           if (tmpNanosecs != t3) {
             dat0 = LeapSecondsfromYMD (xdate1[0], xdate1[1], xdate1[2]);
             tmpx = t2 - (long) (dat0 * SECinNanoSecs);
             tmpy = (long) ((double)tmpx/SECinNanoSecsD);
             nansec = (long) (tmpx - tmpy * SECinNanoSecs);
             if (nansec < 0) {
               nansec = SECinNanoSecs + nansec;
               --tmpy;
             }
             epoch = (double) tmpy + J2000Since0AD12hSec;
             /* Final determination */
             xdate1 = EPOCHbreakdownTT2000 (epoch);
             try {
               tmpNanosecs = CDFTT2000.fromUTCparts ((double)xdate1[0],
                                                     (double)xdate1[1],
                                                     (double)xdate1[2],
                                                     (double)xdate1[3],
                                                     (double)xdate1[4],
                                                     (double)xdate1[5],
                                                     0.0, 0.0, nansec);
             } catch (CDFException ex) {
             }
           }
         }
         ye1 = xdate1[0];
         mo1 = xdate1[1];
         da1 = xdate1[2];
         ho1 = xdate1[3];
         mi1 = xdate1[4];
         se1 = xdate1[5];
       }
       ml1 = (long) (nansec / 1000000);
       tmp1 = nansec - 1000000 * ml1;
       if (ml1 > 1000) {
         ml1 -= 1000;
         se1 += 1;
       }
       ma1 = (long) (tmp1 / 1000);
       na1 = (long)  (tmp1 - 1000 * ma1);
       long[] datetime = new long[9];
       datetime[0] = ye1;
       datetime[1] = mo1;
       datetime[2] = da1;
       datetime[3] = ho1;
       datetime[4] = mi1;
       datetime[5] = se1;
       datetime[6] = ml1;
       datetime[7] = ma1;
       datetime[8] = na1;
       return datetime;
    }

    /**
     * 
     * Breaks a TT2000 epoch value down into its full, UTC-based date/time
     * component parts.
     *
     * @param nanoSecSinceJ2000 the epoch value, in nanoseconds since J2000, 
     *                          to break down
     * @return an array of long containing the epoch parts:
     *  <TABLE SUMMARY="" BORDER="0">
     *    <TR><TD ALIGN="CENTER">Index</TD><TD ALIGN="CENTER">Part</TD></TR>
     *    <TR><TD>0</TD><TD>year</TD></TR>
     *    <TR><TD>1</TD><TD>month</TD></TR>
     *    <TR><TD>2</TD><TD>day</TD></TR>
     *    <TR><TD>3</TD><TD>hour</TD></TR>
     *    <TR><TD>4</TD><TD>minute</TD></TR>
     *    <TR><TD>5</TD><TD>second</TD></TR>
     *    <TR><TD>6</TD><TD>millisecond</TD></TR>
     *    <TR><TD>7</TD><TD>microsecond</TD></TR>
     *    <TR><TD>8</TD><TD>nanosecond</TD></TR>
     *  </TABLE>
     */

    public static long[] breakdown (long nanoSecSinceJ2000) 
    {
       return toUTCparts (nanoSecSinceJ2000);
    }

    /**
     * 
     * Breaks an array of TT2000 epoch values down into two dimensional array,
     * the first dimension being the count of the epoch values, and the second
     * dimension being their full, UTC-based date/time component parts for
     * each value.
     *
     * @param nanoSecSinceJ2000 an array of epoch values, in nanoseconds since
     *                          J2000, to break down
     * @return a 2-dim array of long, the first dimension being the value
     *         count while the second dimension containing the epoch parts,
     *         as follows:
     *  <TABLE SUMMARY="" BORDER="0">
     *    <TR><TD ALIGN="CENTER">Index</TD><TD ALIGN="CENTER">Part</TD></TR>
     *    <TR><TD>0</TD><TD>year</TD></TR>
     *    <TR><TD>1</TD><TD>month</TD></TR>
     *    <TR><TD>2</TD><TD>day</TD></TR>
     *    <TR><TD>3</TD><TD>hour</TD></TR>
     *    <TR><TD>4</TD><TD>minute</TD></TR>
     *    <TR><TD>5</TD><TD>second</TD></TR>
     *    <TR><TD>6</TD><TD>millisecond</TD></TR>
     *    <TR><TD>7</TD><TD>microsecond</TD></TR>
     *    <TR><TD>8</TD><TD>nanosecond</TD></TR>
     *  </TABLE>
     *  or null if null or an empty input array is detected
     */

    public static long[][] breakdown (long[] nanoSecSinceJ2000) 
    {
       if (nanoSecSinceJ2000 == null) return null;
       int len = nanoSecSinceJ2000.length;
       if (len <= 0) return null;
       long[][] comps = new long[len][9];
       for (int i = 0; i < len; ++i) 
         comps[i] = CDFTT2000.breakdown (nanoSecSinceJ2000[i]);
       return comps;
    }

    /** 
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The day field can contain a 
     * fraction of a day - a new name from fromUTCparts.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. It is in double
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long computeTT2000 (double year, double month, double day)
           throws CDFException
    {
	return CDFTT2000.fromUTCparts (year, month, day);
    }

    /** 
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The day field can contain a 
     * fraction of a day.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. It is in double
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long fromUTCparts (double year, double month, double day)
           throws CDFException
    {
       if ((year - Math.floor(year)) != 0.0 ||
           (month - Math.floor(month)) != 0.0)
          throw new CDFException (TT2000_TIME_ERROR);
       double tmp, hour, minute, second, milsec, micsec, nansec;
       tmp = day;
       day = Math.floor(tmp);
       tmp = (tmp - day) * 24.0;
       hour = Math.floor(tmp);
       tmp = (tmp - hour) * 60.0;
       minute = Math.floor(tmp);
       tmp = (tmp - minute) * 60.0;
       second = Math.floor(tmp);
       tmp = (tmp - second) * 1000.0;
       milsec = Math.floor(tmp);
       tmp = (tmp - milsec) * 1000.0;
       micsec = Math.floor(tmp);
       nansec = (tmp - micsec) * 1000.0;
       return fromUTCparts (year, month, day, hour, minute, second, milsec,
                            micsec, nansec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The hour field can contain a
     * fraction of a hour - a new name from fromUTCparts.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour in double
     * <b>Note:</b> Avoid passing in the time component that extends into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long computeTT2000 (double year, double month, double day,
                                      double hour)
           throws CDFException
    {
	return CDFTT2000.fromUTCparts (year, month, day, hour);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The hour field can contain a
     * fraction of a hour.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour in double
     * <b>Note:</b> Avoid passing in the time component that extends into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long fromUTCparts (double year, double month, double day,
                                     double hour)
           throws CDFException
    {
       if ((year - Math.floor(year)) != 0.0 ||
           (month - Math.floor(month)) != 0.0 ||
           (day - Math.floor(day)) != 0.0)
          throw new CDFException (TT2000_TIME_ERROR);
       double tmp, minute, second, milsec, micsec, nansec;
       tmp = hour;
       hour = Math.floor(tmp);
       tmp = (tmp - hour) * 60.0;
       minute = Math.floor(tmp);
       tmp = (tmp - minute) * 60.0;
       second = Math.floor(tmp);
       tmp = (tmp - second) * 1000.0;
       milsec = Math.floor(tmp);
       tmp = (tmp - milsec) * 1000.0;
       micsec = Math.floor(tmp);
       nansec = (tmp - micsec) * 1000.0;
       return fromUTCparts (year, month, day, hour, minute, second, milsec,
                            micsec, nansec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The minute field can contain a
     * fraction of a minute - a new name from fromUTCparts.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long computeTT2000 (double year, double month, double day,
                                      double hour, double minute)
           throws CDFException
    {
	return CDFTT2000.fromUTCparts (year, month, day, hour, minute);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The minute field can contain a
     * fraction of a minute.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long fromUTCparts (double year, double month, double day,
                                     double hour, double minute)
           throws CDFException
    {
       if ((year - Math.floor(year)) != 0.0 ||
           (month - Math.floor(month)) != 0.0 ||
           (day - Math.floor(day)) != 0.0 ||
           (hour - Math.floor(hour)) != 0.0)
          throw new CDFException (TT2000_TIME_ERROR);
       double tmp, second, milsec, micsec, nansec;
       tmp = minute;
       minute = Math.floor(tmp);
       tmp = (tmp - minute) * 60.0;
       second = Math.floor(tmp);
       tmp = (tmp - second) * 1000.0;
       milsec = Math.floor(tmp);
       tmp = (tmp - milsec) * 1000.0;
       micsec = Math.floor(tmp);
       nansec = (tmp - micsec) * 1000.0;
       return fromUTCparts (year, month, day, hour, minute, second, milsec,
                            micsec, nansec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The second field can contain a
     * fraction of a second - a new name from fromUTCparts.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long computeTT2000 (double year, double month, double day,
                                      double hour, double minute, double second)
           throws CDFException
    {
	return CDFTT2000.fromUTCparts (year, month, day, hour, minute, second);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The second field can contain a
     * fraction of a second.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long fromUTCparts (double year, double month, double day,
                                     double hour, double minute, double second)
           throws CDFException
    {
       if ((year - Math.floor(year)) != 0.0 ||
           (month - Math.floor(month)) != 0.0 ||
           (day - Math.floor(day)) != 0.0 ||
           (hour - Math.floor(hour)) != 0.0 ||
           (minute - Math.floor(minute)) != 0.0)
          throw new CDFException (TT2000_TIME_ERROR);
       double tmp, milsec, micsec, nansec; 
       tmp = second;
       second = Math.floor(tmp);
       tmp = (tmp - second) * 1000.0;
       milsec = Math.floor(tmp);
       tmp = (tmp - milsec) * 1000.0;
       micsec = Math.floor(tmp);
       nansec = (tmp - micsec) * 1000.0;
       return fromUTCparts (year, month, day, hour, minute, second, milsec,
                            micsec, nansec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The millisecond field can contain a
     * fraction of a millisecond - a new name from fromUTCparts.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second, a full second in double
     * @param milsec the millisecond in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long computeTT2000 (double year, double month, double day,
                                      double hour, double minute, double second,
                                      double milsec)
           throws CDFException
    {
	return CDFTT2000.fromUTCparts (year, month, day, hour, minute, second,
				       milsec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The millisecond field can contain a
     * fraction of a millisecond.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second, a full second in double
     * @param milsec the millisecond in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long fromUTCparts (double year, double month, double day,
                                     double hour, double minute, double second,
                                     double milsec)
           throws CDFException
    {
       if ((year - Math.floor(year)) != 0.0 ||
           (month - Math.floor(month)) != 0.0 ||
           (day - Math.floor(day)) != 0.0 ||
           (hour - Math.floor(hour)) != 0.0 ||
           (minute - Math.floor(minute)) != 0.0 ||
           (second - Math.floor(second)) != 0.0)
          throw new CDFException (TT2000_TIME_ERROR);
       double tmp, micsec, nansec; 
       tmp = milsec;
       milsec = Math.floor(tmp);
       tmp = (tmp - milsec) * 1000.0;
       micsec = Math.floor(tmp);
       nansec = (tmp - micsec) * 1000.0;
       return fromUTCparts (year, month, day, hour, minute, second, milsec,
                            micsec, nansec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The microsecond field can contain a
     * fraction, which usually indicates the end of the parameter list - a new
     * name from fromUTCparts.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second, a full second in double
     * @param milsec the millisecond, a full millisecond in double
     * @param micsec the microsecond in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long computeTT2000 (double year, double month, double day,
                                      double hour, double minute, double second,
                                      double milsec, double micsec)
           throws CDFException
    {
	return CDFTT2000.fromUTCparts (year, month, day, hour, minute, second,
				       milsec, micsec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. The microsecond field can contain a
     * fraction, which usually indicates the end of the parameter list.
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second, a full second in double
     * @param milsec the millisecond, a full millisecond in double
     * @param micsec the microsecond in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long fromUTCparts (double year, double month, double day,
                                     double hour, double minute, double second,
                                     double milsec, double micsec)
           throws CDFException
    {
       if ((year - Math.floor(year)) != 0.0 ||
           (month - Math.floor(month)) != 0.0 ||
           (day - Math.floor(day)) != 0.0 ||
           (hour - Math.floor(hour)) != 0.0 ||
           (minute - Math.floor(minute)) != 0.0 ||
           (second - Math.floor(second)) != 0.0 ||
           (milsec - Math.floor(milsec)) != 0.0)
          throw new CDFException (TT2000_TIME_ERROR);
       double tmp, nansec;
       tmp = micsec;
       micsec = Math.floor(tmp);
       nansec = (tmp - micsec) * 1000.0;
       return fromUTCparts (year, month, day, hour, minute, second, milsec,
                            micsec, nansec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts - a new name from fromUTCparts. 
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1), otherwise an 
     *            exception is thrown. A full day in double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second, a full second in double
     * @param msec the millisecond, a full millisecond in double
     * @param usec the microsecond, a full microsecond in double
     * @param nsec the nanosecond, a full nanosecond in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long computeTT2000 (double year, double month, double day, 
                                      double hour, double minute, double second,
                                      double msec, double usec, double nsec) 
           throws CDFException 
    {
	return CDFTT2000.fromUTCparts (year, month, day, hour, minute, second,
				       msec, usec, nsec);
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. 
     *
     * @param year the year, a full year in double
     * @param month the month, a full month in double
     * @param day the day, either the day of the month or day of the year
     *            (DOY). For DOY, month has to be one (1). A full day in
     *            double
     * @param hour the hour, a full hour in double
     * @param minute the minute, a full minute in double
     * @param second the second, a full second in double
     * @param msec the millisecond, a full millisecond in double
     * @param usec the microsecond, a full microsecond in double
     * @param nsec the nanosecond, a full nanosecond in double
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long fromUTCparts (double year, double month, double day, 
                                     double hour, double minute, double second,
                                     double msec, double usec, double nsec) 
           throws CDFException 
    {
       double jd;
       long subDayinNanoSecs, nanoSecSinceJ2000;
       long t2, iy;
       long[] date;
       double tmp;
       long lyl, lml, ldl, lhl, lnl, lsl, lll, lul, lal, ymd;
       if (year < 0.0 || month < 0.0 || day < 0.0 || hour < 0.0 ||
           minute < 0.0 || second < 0.0 || msec < 0.0 || usec < 0.0 ||
           nsec < 0.0)
         throw new CDFException(TT2000_TIME_ERROR);
       lyl = (long) Math.floor(year);
       if (month == 0.0) month = 1.0;
       lml = (long) Math.floor(month);
       ldl = (long) Math.floor(day);
       lhl = (long) Math.floor(hour);
       lnl = (long) Math.floor(minute);
       lsl = (long) Math.floor(second);
       lll = (long) Math.floor(msec);
       lul = (long) Math.floor(usec);
       if ((year-lyl) != 0.0 || (month-lml) != 0.0 || (day-ldl) != 0.0 ||
           (hour-lhl) != 0.0 || (minute-lnl) != 0.0 || (second-lsl) != 0.0 ||
           (msec-lll) != 0.0 || (usec-lul) != 0.0)
         throw new CDFException(TT2000_TIME_ERROR);
       lyl = lml = -999;
       if (nsec >= 1000.0) {
         double ad, ah, am, as, al, au;
         ad = Math.floor(nsec / 86400000000000.0);
         nsec = nsec % 86400000000000.0;
         ah = Math.floor(nsec /3600000000000.0);
         nsec = nsec % 3600000000000.0;
         am = Math.floor(nsec / 60000000000.0);
         nsec = nsec % 60000000000.0;
         as = Math.floor(nsec / 1000000000.0);
         nsec = nsec % 1000000000.0;
         al = Math.floor(nsec /1000000.0);
         nsec = nsec % 1000000.0;
         au = Math.floor(nsec /1000.0);
         nsec = nsec % 1000.0;
         day += ad;
         hour += ah;
         minute += am;
         second += as;
         msec += al;
         usec += au;
         tmp = JulianDay12h ((long) year, (long) month, (long) day);
         date = DatefromJulianDay (tmp);
         lyl = date[0];
         lml = date[1];
         ldl = date[2];
       }
       if (usec >= 1000.0) {
         double ad, ah, am, as, al;
         ad = Math.floor(usec / 86400000000.0);
         usec = usec % 86400000000.0;
         ah = Math.floor(usec /3600000000.0);
         usec = usec % 3600000000.0;
         am = Math.floor(usec / 60000000);
         usec = usec % 60000000;
         as = Math.floor(usec / 1000000);
         usec = usec % 1000000;
         al = Math.floor(usec /1000);
         usec = usec % 1000;
         day += ad;
         hour += ah;
         minute += am;
         second += as;
         msec += al;
         tmp = JulianDay12h ((long) year, (long) month, (long) day);
         date = DatefromJulianDay (tmp);
         lyl = date[0];
         lml = date[1];
         ldl = date[2];
       }
       if (msec >= 1000.0) {
         double ad, ah, am, as;
         ad = Math.floor(msec / 86400000);
         msec = msec % 86400000;
         ah = Math.floor(msec /3600000);
         msec = msec % 3600000;
         am = Math.floor(msec / 60000);
         msec = msec % 60000;
         as = Math.floor(msec / 1000);
         msec = msec % 1000;
         day += ad;
         hour += ah;
         minute += am;
         second += as;
         tmp = JulianDay12h ((long) year, (long) month, (long) day);
         date = DatefromJulianDay (tmp);
         lyl = date[0];
         lml = date[1];
         ldl = date[2];
       }
       tmp = JulianDay12h ((long) year, (long) month, (long) day);
       date = DatefromJulianDay (tmp+1);
       toPlus = LeapSecondsfromYMD(date[0], date[1], date[2]) - 
                LeapSecondsfromYMD((long)year, (long)month, (long)day);
       toPlus = Math.floor(toPlus);
       if (second >= (60.0+toPlus)) {
         double ad, ah, am;
         ad = Math.floor(second / (86400+toPlus));
         second = second % (86400+toPlus);
         ah = Math.floor(second /(3600+toPlus));
         second = second % (3600+toPlus);
         am = Math.floor(second / (60+toPlus));
         second = second % (60+toPlus);
         day += ad;
         hour += ah;
         minute += am;
         tmp = JulianDay12h ((long) year, (long) month, (long) day);
         date = DatefromJulianDay (tmp);
         lyl = date[0];
         lml = date[1];
         ldl = date[2];
       }
       if (minute >= 60.0) {
         double ad, ah;
         ad = Math.floor(minute / 1440);
         minute = minute % 1440;
         ah = Math.floor(minute / 60);
         minute = minute % 60;
         day += ad;
         hour += ah;
         tmp = JulianDay12h ((long) year, (long) month, (long) day);
         date = DatefromJulianDay (tmp);
         lyl = date[0];
         lml = date[1];
         ldl = date[2];
       }
       if (hour >= 24.0) {
         double ad;
         ad = Math.floor(hour / 24.0); 
         hour = hour % 24.0;
         day += ad;
         tmp = JulianDay12h ((long) year, (long) month, (long) day);
         date = DatefromJulianDay (tmp);
         lyl = date[0];
         lml = date[1];
         ldl = date[2];
       }
       if (lyl == -999 && lml == -999) {
         lyl = (long) year;
         lml = (long) month;
         ldl = (long) day;
       }
       lhl = (long) hour;
       lnl = (long) minute;
       lsl = (long) second;
       lll = (long) msec;
       lul = (long) usec;
       lal = (long) nsec;
       if (lyl == 9999 && lml == 12 && ldl == 31 && lhl == 23 && lnl == 59 &&
           lsl == 59 && lll == 999 && lul == 999 && lal == 999)
         return FILLED_TT2000_VALUE; 
       else if (lyl == 0 && lml == 1 && ldl == 1 && lhl == 0 && lnl == 0 &&
                lsl == 0 && lll == 0 && lul == 0 && lal == 0)
         return DEFAULT_TT2000_PADVALUE;
       if ((lyl<1708 || lyl > 2291) && (ValidateYMD(lyl,lml,ldl) == 0))
         throw new CDFException(TT2000_TIME_ERROR);
       boolean lyear = (lyl & 3) == 0 && ((lyl % 25) != 0 || (lyl & 15) == 0);
       if ((!lyear && ldl > 365) || (lyear && ldl > 366))
         throw new CDFException(TT2000_TIME_ERROR);
       if ((!lyear && lml > 1 && ldl > daym1[(int)lml-1]) ||
           (lyear && lml > 1 && ldl > daym2[(int)lml-1]))
         throw new CDFException(TT2000_TIME_ERROR);
       if (lml <= 1 && ldl > 31) {
          /* DOY is passed in. */
          int ix;
          if (lml == 0) lml = 1;
          for (ix = 0; ix < 12; ++ix) {
             if ((!lyear && ldl <= doys1[ix]) || (lyear && ldl <= doys2[ix])) {
               if (ix == 0) break;
               else {
                 lml = ix+1;
                 if (!lyear) ldl = ldl - doys1[ix-1];
                 else ldl = ldl - doys2[ix-1];
                 break;
               }
             }
          }
       }
       iy = 10000000 * lml + 10000 * ldl + lyl;
       if (iy != currentDay) {
         currentDay = iy;
         currentLeapSeconds = LeapSecondsfromYMD(lyl, lml, ldl);
         currentJDay = JulianDay12h(lyl,lml,ldl);
       }
       jd = currentJDay;
       jd = jd - JulianDateJ2000_12h;
       subDayinNanoSecs = lhl * HOURinNanoSecs + lnl * MINUTEinNanoSecs +
                          lsl * SECinNanoSecs + lll * 1000000 + lul * 1000 + lal;
       nanoSecSinceJ2000 = (long) jd * DAYinNanoSecs + subDayinNanoSecs;
       t2 = (long) (currentLeapSeconds * SECinNanoSecs);
       if (nanoSecSinceJ2000 < 0) {
         nanoSecSinceJ2000 += t2;
         nanoSecSinceJ2000 += dTinNanoSecs;
         nanoSecSinceJ2000 -= T12hinNanoSecs;
       } else {
         nanoSecSinceJ2000 -= T12hinNanoSecs;
         nanoSecSinceJ2000 += t2;
         nanoSecSinceJ2000 += dTinNanoSecs;
       }
       return nanoSecSinceJ2000;
    }

    /**
     * 
     * Computes a TT2000 epoch, nanoseconds since J2000, based on its 
     * UTC-based date/time component parts. 
     *
     * @param year the year, a full year in long
     * @param month the month, a full month in long
     * @param day the day, a full day in long
     * @param hour the hour, a full hour in long
     * @param minute the minute, a full minute in long
     * @param second the second, a full second in long
     * @param msec the millisecond, a full millisecond in long
     * @param usec the microsecond, a full microsecond in long
     * @param nsec the nanosecond, a full nanosecond in long
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     *
     * @return the TT2000 epoch value in long
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long compute (long year, long month, long day, 
                                long hour, long minute, long second,
                                long msec, long usec, long nsec) 
           throws CDFException 
    {
       return fromUTCparts ((double) year, (double) month, (double) day,
                            (double) hour, (double) minute, (double) second,
                            (double) msec, (double) usec, (double) nsec);
    }

    /**
     * 
     * Computes an array of TT2000 epochs, nanoseconds since J2000, based on
     * its UTC-based date/time component parts. 
     *
     * @param year an array of years, a full year in long
     * @param month an array of months, a full month in long
     * @param day an array of days, a full day in long
     * 
     * @return the TT2000 epoch values in long or null if an invalid input
     *         array(s) is detected
     * 
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long[] compute (long[] year, long[] month, long[] day)
           throws CDFException
    {
       if (year == null || month == null || day == null) return null;
       int len = year.length;
       if (len != month.length || len != day.length) return null;
       long[] tt2000s = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000s[i] = CDFTT2000.compute (year[i], month[i], day[i], 0L,
                                         0L, 0L, 0L, 0L, 0L);
       return tt2000s;
    }

    /**
     * 
     * Computes an array of TT2000 epochs, nanoseconds since J2000, based on
     * its UTC-based date/time component parts. 
     *
     * @param year an array of years, a full year in long
     * @param month an array of months, a full month in long
     * @param day an array of days, a full day in long
     * @param hour an array of hours, a full hour in long
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     * 
     * @return the TT2000 epoch value in long or null if an invalid input
     *         array(s) is detected
     * 
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long[] compute (long[] year, long[] month, long[] day,
                                  long[] hour)
           throws CDFException
    {
       if (year == null || month == null || day == null ||
           hour == null) return null;
       int len = year.length;
       if (len != month.length || len != day.length || len != hour.length)
         return null;
       long[] tt2000s = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000s[i] = CDFTT2000.compute (year[i], month[i], day[i], hour[i],
                                         0L, 0L, 0L, 0L, 0L);
       return tt2000s;
    }

    /**
     * 
     * Computes an array of TT2000 epochs, nanoseconds since J2000, based on
     * its * UTC-based date/time component parts. 
     *
     * @param year an array of years, a full year in long
     * @param month an array of months, a full month in long
     * @param day an array of days, a full day in long
     * @param hour an array of hours, a full hour in long
     * @param minute an array of minutes, a full minute in long
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     * 
     * @return the TT2000 epoch values in long or null if an invalid input
     *         array(s) is detected
     * 
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long[] compute (long[] year, long[] month, long[] day,
                                  long[] hour, long[] minute)
           throws CDFException
    {
       if (year == null || month == null || day == null ||
           hour == null || minute == null) return null;
       int len = year.length;
       if (len != month.length || len != day.length || len != hour.length ||
           len != minute.length) return null;
       long[] tt2000s = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000s[i] = CDFTT2000.compute (year[i], month[i], day[i], hour[i],
                                         minute[i], 0L, 0L, 0L, 0L);
       return tt2000s;
    }

    /**
     * 
     * Computes an array of TT2000 epochs, nanoseconds since J2000, based on
     * its * UTC-based date/time component parts. 
     *
     * @param year an array of years, a full year in long
     * @param month an array of months, a full month in long
     * @param day an array of days, a full day in long
     * @param hour an array of hours, a full hour in long
     * @param minute an array of minutes, a full minute in long
     * @param second an array of seconds, a full second in long
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     * 
     * @return the TT2000 epoch value in long or null if an invalid input
     *         array(s) is detected
     * 
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long[] compute (long[] year, long[] month, long[] day,
                                  long[] hour, long[] minute, long[] second)
           throws CDFException
    {
       if (year == null || month == null || day == null ||
           hour == null || minute == null || second == null) return null;
       int len = year.length;
       if (len != month.length || len != day.length || len != hour.length ||
           len != minute.length || len != second.length) return null;
       long[] tt2000s = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000s[i] = CDFTT2000.compute (year[i], month[i], day[i], hour[i],
                                         minute[i], second[i], 0L, 0L, 0L);
       return tt2000s;
    }

    /**
     * 
     * Computes an array of TT2000 epochs, nanoseconds since J2000, based on
     * its * UTC-based date/time component parts. 
     *
     * @param year an array of years, a full year in long
     * @param month an array of months, a full month in long
     * @param day an array of days, a full day in long
     * @param hour an array of hours, a full hour in long
     * @param minute an array of minutes, a full minute in long
     * @param second an array of seconds, a full second in long
     * @param msec an array of milliseconds, a full millisecond in long
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     * 
     * @return the TT2000 epoch values in long or null if an invalid input
     *         array(s) is detected
     * 
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long[] compute (long[] year, long[] month, long[] day,
                                  long[] hour, long[] minute, long[] second,
                                  long[] msec)
           throws CDFException
    {
       if (year == null || month == null || day == null ||
           hour == null || minute == null || second == null ||
           msec == null) return null;
       int len = year.length;
       if (len != month.length || len != day.length || len != hour.length ||
           len != minute.length || len != second.length || len != msec.length)
         return null;
       long[] tt2000s = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000s[i] = CDFTT2000.compute (year[i], month[i], day[i], hour[i],
                                         minute[i], second[i], msec[i], 0L, 0L);
       return tt2000s;
    }

    /**
     * 
     * Computes an array of TT2000 epochs, nanoseconds since J2000, based on
     * its * UTC-based date/time component parts. 
     *
     * @param year an array of years, a full year in long
     * @param month an array of months, a full month in long
     * @param day an array of days, a full day in long
     * @param hour an array of hours, a full hour in long
     * @param minute an array of minutes, a full minute in long
     * @param second an array of seconds, a full second in long
     * @param msec an array of milliseconds, a full millisecond in long
     * @param usec an array of microseconds, a full microsecond in long
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     * 
     * @return the TT2000 epoch value in long or null if an invalid input
     *          array(s) is detected
     * 
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long[] compute (long[] year, long[] month, long[] day,
                                  long[] hour, long[] minute, long[] second,
                                  long[] msec, long[] usec)
           throws CDFException
    {
       if (year == null || month == null || day == null ||
           hour == null || minute == null || second == null ||
           msec == null || usec == null) return null;
       int len = year.length;
       if (len != month.length || len != day.length || len != hour.length ||
           len != minute.length || len != second.length || len != msec.length ||
           len != usec.length) return null;
       long[] tt2000s = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000s[i] = CDFTT2000.compute (year[i], month[i], day[i], hour[i],
                                         minute[i], second[i], msec[i],
                                         usec[i], 0L);
       return tt2000s;
    }

    /**
     * 
     * Computes an array of TT2000 epochs, nanoseconds since J2000, based on
     * its * UTC-based date/time component parts. 
     *
     * @param year an array of years, a full year in long
     * @param month an array of months, a full month in long
     * @param day an array of days, a full day in long
     * @param hour an array of hours, a full hour in long
     * @param minute an array of minutes, a full minute in long
     * @param second an array of seconds, a full second in long
     * @param msec an array of milliseconds, a full millisecond in long
     * @param usec an array of microseconds, a full microsecond in long
     * @param nsec an array of nanoseconds, a full nanosecond in long
     * <b>Note:</b> Avoid passing in the time components that extend into the 
     *              next day as that could present a potential problem. 
     * 
     * @return the TT2000 epoch values in long or null if an invalid input
     *         array(s) is detected
     * 
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */

    public static long[] compute (long[] year, long[] month, long[] day,
                                  long[] hour, long[] minute, long[] second,
                                  long[] msec, long[] usec, long[] nsec)
           throws CDFException
    {
       if (year == null || month == null || day == null ||
           hour == null || minute == null || second == null ||
           msec == null || usec == null || nsec == null) return null;
       int len = year.length;
       if (len != month.length || len != day.length || len != hour.length ||
           len != minute.length || len != second.length || len != msec.length ||           len != usec.length || len != nsec.length) return null;
       long[] tt2000s = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000s[i] = CDFTT2000.compute (year[i], month[i], day[i], hour[i],
                                         minute[i], second[i], msec[i],
                                         usec[i], nsec[i]);
       return tt2000s;
    }

    /**
     * 
     * Convert an epoch value in TT2000 type to CDF_EPOCH type.
     *
     * @param nanoSecSinceJ2000 the nanoseconds since J2000
     *
     * @return the epoch value
     *
     * @exception CDFException an ILLEGAL_EPOCH_FIELD if an illegal 
     *                         component value is detected.
     */

    public static double toUTCEPOCH (long nanoSecSinceJ2000) throws CDFException
    {
       if (nanoSecSinceJ2000 == FILLED_TT2000_VALUE) return -1.0E31;
       if (nanoSecSinceJ2000 == DEFAULT_TT2000_PADVALUE) return 0.0;
       if (nanoSecSinceJ2000 == ILLEGAL_TT2000_VALUE) return 0.0;
       long [] dateTime = toUTCparts (nanoSecSinceJ2000);
       double epoch;
       try {
         epoch = Epoch.compute (dateTime[0], dateTime[1],
                                dateTime[2], dateTime[3],
                                dateTime[4], dateTime[5],
                                dateTime[6]);
       } catch (Exception ex) {
         epoch = ILLEGAL_EPOCH_VALUE;
         throw new CDFException(ILLEGAL_EPOCH_FIELD);
       }
       return epoch;
    }

    /**
     * Convert an epoch value in CDF_EPOCH type to TT2000 type.
     * Reset the predefined CDF_EPOCH value of -1.0E31, -1.0E-31, or the
     * default 0.0 or -0.0 (all are invalid for TT2000) to 0. 
     *
     * @param epoch the CDF_EPOCH value
     *
     * @return the TT2000 epoch in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if date is out of the
     *                         valid range for TT2000.
     *
     */

    public static long fromUTCEPOCH (double epoch) throws CDFException
    {
       if (epoch == -1.0E31 || epoch == -1.0E-31)
         return FILLED_TT2000_VALUE;
       if (epoch == 0.0 || epoch == -0.0) return DEFAULT_TT2000_PADVALUE;
       long [] dateTime = Epoch.breakdown (epoch);
       if ((dateTime[0]<1708 || dateTime[0] > 2291) && 
           (ValidateYMD(dateTime[0],dateTime[1],dateTime[2]) == 0))
         return ILLEGAL_TT2000_VALUE;
       return fromUTCparts ((double)dateTime[0], (double)dateTime[1],
                            (double)dateTime[2], (double)dateTime[3],
                            (double)dateTime[4], (double)dateTime[5],
                            (double)dateTime[6], 0.0, 0.0);
    }

    /**
     * Convert an epoch value in TT2000 type to CDF_EPOCH16 type.
     *
     * @param nanoSecSinceJ2000 the nanoseconds since J2000
     * @param epoch the returned CDF_EPOCH16 value, a double[2] object
     *
     * @return the status
     *
     * @exception CDFException an ILLEGAL_EPOCH_FIELD if an illegal 
     *                         component value is detected.
     */

    public static double toUTCEPOCH16 (long nanoSecSinceJ2000, double[] epoch) 
                         throws CDFException
    {
       if (nanoSecSinceJ2000 == FILLED_TT2000_VALUE) {
         epoch[0] = -1.0E31;
         epoch[1] = -1.0E31;
         return 0.0;
       } else if (nanoSecSinceJ2000 == DEFAULT_TT2000_PADVALUE||
                  nanoSecSinceJ2000 == ILLEGAL_TT2000_VALUE) {
         epoch[0] = 0.0;
         epoch[1] = 0.0;
         return 0.0;
       }
       double tmp;
       long [] dateTime = toUTCparts (nanoSecSinceJ2000);
       try {
         tmp = Epoch16.compute (dateTime[0], dateTime[1],
                                dateTime[2], dateTime[3],
                                dateTime[4], dateTime[5],
                                dateTime[6], dateTime[7],
                                dateTime[8], 0, epoch);
       } catch (Exception ex) {
         tmp = ILLEGAL_EPOCH_VALUE;
         throw new CDFException(ILLEGAL_EPOCH_FIELD);
       }
       return tmp;
    }

    /**
     * Convert an epoch data in CDF_EPOCH16 type to TT2000 type.
     * Reset the predefined CDF_EPOCH value of -1.0E31, -1.0E-31, or the
     * default 0.0 or -0.0 (all are invalid for TT2000) to 0. 
     *
     * @param epoch the CDF_EPOCH16 value, a double[2] object
     *
     * @return the TT2000 epoch in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if date is out of the
     *                         valid range for TT2000.
     *
     */

    public static long fromUTCEPOCH16 (double[] epoch) throws CDFException
    {
       if ((epoch[0] == -1.0E31 && epoch[1] == -1.0E31) ||
           (epoch[0] == -1.0E-31 && epoch[1] == -1.0E-31))
         return FILLED_TT2000_VALUE;
       if ((epoch[0] == 0.0 && epoch[1] == 0.0) ||
           (epoch[0] == -0.0 && epoch[1] == -0.0))
         return DEFAULT_TT2000_PADVALUE;
       long[] dateTime = Epoch16.breakdown (epoch);
       if ((dateTime[0]<1708 || dateTime[0] > 2291) && 
           (ValidateYMD(dateTime[0],dateTime[1],dateTime[2]) == 0))
         throw new CDFException(TT2000_TIME_ERROR);
       return fromUTCparts ((double)dateTime[0], (double)dateTime[1],
                            (double)dateTime[2], (double)dateTime[3],
                            (double)dateTime[4], (double)dateTime[5],
                            (double)dateTime[6], (double)dateTime[7],
                            (double)dateTime[8]);
    }

    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based 
     * date/time string of ISO 8601 style.
     *
     * <PRE>
     *
     *            yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in Long object
     *
     * @return A string representation of the epoch in ISO 8601
     */
    public static String toUTCstring (Long nanoSecSinceJ2000)
    {
       return toUTCstring (nanoSecSinceJ2000.longValue(), 3);
    }
 
    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based
     * date/time string of ISO 8601 style.
     *
     * <PRE>
     *            yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in long
     *
     * @return A string representation of the epoch
     */
    public static String toUTCstring (long nanoSecSinceJ2000)
    {
       return toUTCstring (nanoSecSinceJ2000, 3);
    }
 
    /**
     * 
     * Converts an iarray of epoch values in TT2000 type into readable,
     * UTC-based date/time strings of ISO 8601 style.
     *
     * <PRE>
     *            yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch values, nanoseconds since
     *                          J2000, in long
     *
     * @return A string array representation of the epochs
     */
    public static String[] toUTCstring (long[] nanoSecSinceJ2000)
    {
       return toUTCstring (nanoSecSinceJ2000, 3);
    }
 
    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based
     * date/time string of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in Long object
     * @param style the style (from 0 to 4, 3 being the default), an optional
     *
     * @return A string representation of the epoch
     */
    public static String toUTCstring (Long nanoSecSinceJ2000, int style)
    {
       return toUTCstring (nanoSecSinceJ2000.longValue(), style);
    }
 
    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based
     * date/time string of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in Long object
     * @param style the style (from 0 to 4, 3 being the default), an optional
     *
     * @return A string representation of the epoch
     */
    public static String toEncode (Long nanoSecSinceJ2000, int style)
    {
       return CDFTT2000.toEncode (nanoSecSinceJ2000.longValue(), style);
    }
 
    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based 
     * date/time string of ISO 8601 forme.
     *
     * <PRE>
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in long
     *
     * @return A string representation of the epoch
     */
    public static String toEncode (long nanoSecSinceJ2000)
    {
       return CDFTT2000.toUTCstring (nanoSecSinceJ2000, 3);
    }

    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based 
     * date/time string of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in long
     * @param style the style (from 0 to 4, 3 being the default), an optional
     *
     * @return A string representation of the epoch
     */
    public static String toEncode (long nanoSecSinceJ2000, int style)
    {
       return CDFTT2000.toUTCstring (nanoSecSinceJ2000, style);
    }

    /**
     * 
     * Converts an epoch object in TT2000 type into a scalar or array(s) of
     * readable, UTC-based date/time string of ISO8601 style (style 3).
     * Each string is presented in the following form:
     *
     * <PRE>
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
     * @param epoch the TT2000 epoch object, nanoseconds since J2000, in long
     *
     * @return A string or array(s) of strings representation of the epoch
     */
    public static Object toEncode (Object epoch)
    {
       if (epoch == null) return null;
       int nDims = CDFUtils.getNumDimensions(epoch);
       if (nDims > 1) {
         Object encodedArray = null;
         String className = "java.lang.String";
         long[] dimSizes = CDFUtils.getDimensionSizes(epoch);
         int[] tempSizes = new int[nDims];
         for (int j = 0; j < nDims; ++j) tempSizes[j] = (int) dimSizes[j];
         Class arrayClass;
         try {
           arrayClass = Class.forName(className);
           encodedArray = Array.newInstance(arrayClass, tempSizes);
         } catch (ClassNotFoundException e) {
           System.out.println("**** error BAD_ARGUMENT");
           System.exit(0);
         }
         int arrayLength = ((Object[])epoch).length;
         int i;
         Object subObj;
         for (i = 0; i < arrayLength; ++i) {
           ((Object[])encodedArray)[i] = toEncode (((Object[])epoch)[i]);
         }
         return encodedArray;
       } else {
         String sig = CDFUtils.getSignature (epoch);
         if (sig.charAt(0) != '[') { /* scalar */
            return CDFTT2000.toUTCstring((Long)epoch, 3);
         } else { /* 1-D */
            return CDFTT2000.toUTCstring((long[])epoch, 3);
         } 
       }
    }

    /**
     * 
     * Converts an epoch value in TT2000 type into a readable,
     * UTC-based date/time string of ISO8601 style (style 3).
     *
     * <PRE>
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
    /**
     * 
     * Converts an array of epoch values in TT2000 type into readable,
     * UTC-based date/time strings of ISO8601 style.
     *
     * <PRE>
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch values, nanoseconds since
     *                          J2000, in long
     *
     * @return A string array representation of the epochs
     */
    public static String[] toEncode (long[] nanoSecSinceJ2000)
    {
       return CDFTT2000.toUTCstring (nanoSecSinceJ2000, 3);
    }

    /**
     * 
     * Converts an array of epoch values in TT2000 type into readable,
     * UTC-based date/time strings of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch values, nanoseconds since
     *                          J2000, in long
     * @param style the style (from 0 to 4, 3 being the default), an optional
     *
     * @return A string array representation of the epoch
     */
    public static String[] toEncode (long[] nanoSecSinceJ2000, int style)
    {
       return CDFTT2000.toUTCstring (nanoSecSinceJ2000, style);
    }

    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based 
     * date/time string of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in long
     * @param style the style (from 0 to 4, 3 being the default), an optional
     *
     * @return A string representation of the epoch
     */
    public static String toUTCstring (long nanoSecSinceJ2000, int style)
    {
       if (style < 0 || style > 4) style = 3;
       if (nanoSecSinceJ2000 == FILLED_TT2000_VALUE ||
           nanoSecSinceJ2000 == ILLEGAL_TT2000_VALUE) {
         if (style == 0) return new String("31-Dec-9999 23:59:59.999999999");
         if (style == 1) return new String("99991231.9999999999");
         if (style == 2) return new String("99991231235959");
         if (style == 3) return new String("9999-12-31T23:59:59.999999999");
         if (style == 4) return new String("9999-12-31T23:59:59.999999999Z");
       }
       if (nanoSecSinceJ2000 == DEFAULT_TT2000_PADVALUE) {
         if (style == 0) return new String("01-Jan-0000 00:00:00.000000000");
         if (style == 1) return new String("00000101.0000000000");
         if (style == 2) return new String("00000101000000");
         if (style == 3) return new String("0000-01-01T00:00:00.000000000");
         if (style == 4) return new String("0000-01-01T00:00:00.000000000Z");
       }
       long ly, lm, ld, lh, ln, ls, ll, lu, la;
       if (style < 0 || style > 4) style = 3;
       long[] datetime = toUTCparts(nanoSecSinceJ2000);
       ly = datetime[0];
       lm = datetime[1];
       ld = datetime[2];
       lh = datetime[3];
       ln = datetime[4];
       ls = datetime[5];
       ll = datetime[6];
       lu = datetime[7];
       la = datetime[8];
       StringBuffer encoded = new StringBuffer();
       if (style == 0) {
         /* dd-mmm-yyyy hh:mm:ss.mmmuuunnn */
         appendIntegerPart(encoded, ld, 2, true, "02");
         encoded.append("-").append(MonthToken(lm)).append("-");
         appendIntegerPart(encoded, ly, 4, true, "04");
         encoded.append(" ");
         appendIntegerPart(encoded, lh, 2, true, "02");
         encoded.append(":");
         appendIntegerPart(encoded, ln, 2, true, "02");
         encoded.append(":");
         appendIntegerPart(encoded, ls, 2, true, "02");
         encoded.append(".");
         appendIntegerPart(encoded, ll, 3, true, "03");
         appendIntegerPart(encoded, lu, 3, true, "03");
         appendIntegerPart(encoded, la, 3, true, "03");
         return encoded.toString();
       } else if (style == 1) {
         /* yyyymmdd.tttttttttt */
         long milsecs, nansecs;
         double subday;
         milsecs = 3600000 * lh + 60000 * ln + 1000 * ls + ll;
         nansecs = 1000 * lu + la;
         subday = (1000000.0 * milsecs + nansecs) / 
                  (86400.0 * SECinNanoSecsD);
         appendIntegerPart(encoded, ly, 4, true, "04");
         appendIntegerPart(encoded, lm, 2, true, "02");
         appendIntegerPart(encoded, ld, 2, true, "02");
         encoded.append(".");
         appendFractionPart(encoded, subday, 10, "");
         return encoded.toString();
       } else if (style == 2) {
         /* yyyymmddhhmmss */
         appendIntegerPart(encoded, ly, 4, true, "04");
         appendIntegerPart(encoded, lm, 2, true, "02");
         appendIntegerPart(encoded, ld, 2, true, "02");
         appendIntegerPart(encoded, lh, 2, true, "02");
         appendIntegerPart(encoded, ln, 2, true, "02");
         appendIntegerPart(encoded, ls, 2, true, "02");
         return encoded.toString();
       } else if (style == 3 || style == 4) {
         /* yyyy-mm-ddThh:mm:ss.mmmuuunnn */
         appendIntegerPart(encoded, ly, 4, true, "04");
         encoded.append("-");
         appendIntegerPart(encoded, lm, 2, true, "02");
         encoded.append("-");
         appendIntegerPart(encoded, ld, 2, true, "02");
         encoded.append("T");
         appendIntegerPart(encoded, lh, 2, true, "02");
         encoded.append(":");
         appendIntegerPart(encoded, ln, 2, true, "02");
         encoded.append(":");
         appendIntegerPart(encoded, ls, 2, true, "02");
         encoded.append(".");
         appendIntegerPart(encoded, ll, 3, true, "03");
         appendIntegerPart(encoded, lu, 3, true, "03");
         appendIntegerPart(encoded, la, 3, true, "03");
         if (style == 4) 
           encoded.append("Z");
         return encoded.toString();
       }
       return "";
    }

    /**
     * 
     * Converts an array of epoch values in TT2000 type into readable,
     * UTC-based date/time strings of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch values, nanoseconds since
     *                          J2000, in long
     * @param style the style (from 0 to 3, as the default), an optional
     *
     * @return A string array representation of the epochs
     */
    public static String[] toUTCstring (long[] nanoSecSinceJ2000, int style)
    {
        if (nanoSecSinceJ2000 == null) return null;
        int len = nanoSecSinceJ2000.length;
        String[] strings = new String[len];
        for (int i = 0; i < len; ++i)
          strings[i] = toUTCstring (nanoSecSinceJ2000[i], style);
        return strings;
    }

    /**
     * 
     * Converts an array of epoch values in TT2000 type into readable,
     * UTC-based date/time strings of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch values, nanoseconds since
     *                          J2000, in long
     * @param style the style (from 0 to 3, as the default), an optional
     *
     * @return A string array representation of the epochs
     */
    public static String[] encode (long[] nanoSecSinceJ2000, int style)
    {
        if (nanoSecSinceJ2000 == null) return null;
        int len = nanoSecSinceJ2000.length;
        String[] strings = new String[len];
        for (int i = 0; i < len; ++i)
          strings[i] = toUTCstring (nanoSecSinceJ2000[i], style);
        return strings;
    }

    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based 
     * date/time string in ISO 8601 style.
     *
     * <PRE>
     *            yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in long
     * @return A string representation of the epoch
     */
    public static String encode (long nanoSecSinceJ2000)
    {
       return toUTCstring (nanoSecSinceJ2000, 3);
    }
 
    /**
     * 
     * Converts an array of epoch values in TT2000 type into an array of
     * readable, UTC-based date/time strings in ISO 8601 style.
     *
     * <PRE>
     *            yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * </PRE>
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in long
     *
     * @return strings representing the epochs or null if null or an empty
     *         input array is detected
     */
    public static String[] encode (long[] nanoSecSinceJ2000)
    {
       if (nanoSecSinceJ2000 == null) return null;
       int len = nanoSecSinceJ2000.length;
       if (len <= 0) return null;
       String[] tt2000 = new String[len];
       for (int i = 0; i < len; ++i)
         tt2000[i] = CDFTT2000.encode (nanoSecSinceJ2000[i]);
       return tt2000;
    }
 
    /**
     * 
     * Converts an epoch value in TT2000 type into a readable, UTC-based 
     * date/time string of chosen style.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * This is the default, ISO 8601, output.
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those expected by fromUTCstring.
     *
     * @param nanoSecSinceJ2000 the TT2000 epoch value, nanoseconds since
     *                          J2000, in long
     * @param style the style (from 0 to 3, as the default), an optional
     *
     * @return A string representation of the epoch
     */
    public static String encode (long nanoSecSinceJ2000, int style)
    {
          return toUTCstring (nanoSecSinceJ2000, style);
    }

    /**
     * 
     * This method parses an input, UTC-based, date/time string and returns a
     * TT2000 epoch value, nanoseconds since J2000.  The string must be in
     * one of the styles as shown below. Month abbreviations may be in any
     * case and are always the first three letters of the month - a new name
     * from fromUTCstring.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     * 
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * 
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those created by toUTCstring.
     *
     * @param string the epoch in string representation
     *
     * @return A TT2000 epoch the epoch value in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */
    public static long parseTT2000 (String string) throws CDFException
    {
	return CDFTT2000.fromUTCstring (string);
    }

    /**
     * 
     * This method parses an input, UTC-based, date/time string and returns a
     * TT2000 epoch value, nanoseconds since J2000.  The string must be in
     * one of the styles as shown below. Month abbreviations may be in any
     * case and are always the first three letters of the month - a new name
     * from fromUTCstring.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     * 
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * 
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those created by toUTCstring.
     *
     * @param string the epoch in string representation
     *
     * @return A TT2000 epoch the epoch value in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */
    public static long toParse (String string) throws CDFException
    {
	return CDFTT2000.fromUTCstring (string);
    }

    /**
     * 
     * This method parses an input, UTC-based, date/time strings and returns a
     * TT2000 epoch value array, nanoseconds since J2000.  The string must be in
     * one of the styles as shown below. Month abbreviations may be in any
     * case and are always the first three letters of the month - a new name
     * from fromUTCstring.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     * 
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * 
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those created by toUTCstring.
     *
     * @param strings the epoch array in string representation
     *
     * @return A TT2000 epoch the epoch values in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */
    public static long[] parseTT2000 (String[] strings) throws CDFException
    {
        if (strings == null) return null;
        int len = strings.length;
        long[] epochs = new long[len];
        for (int i = 0; i < len; ++i)
          epochs[i] = fromUTCstring (strings[i]);
        return epochs;
    }

    /**
     * 
     * This method parses an input, UTC-based, date/time strings and returns a
     * TT2000 epoch value array, nanoseconds since J2000.  The string must be in
     * one of the styles as shown below. Month abbreviations may be in any
     * case and are always the first three letters of the month - a new name
     * from fromUTCstring.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     * 
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * 
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those created by toUTCstring.
     *
     * @param strings the epoch array in string representation
     *
     * @return A TT2000 epoch the epoch values in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */
    public static long[] toParse (String[] strings) throws CDFException
    {
        return CDFTT2000.parseTT2000 (strings);
    }

    /**
     * 
     * This method parses an input, UTC-based, date/time string and returns a
     * TT2000 epoch value, nanoseconds since J2000.  The string must be in
     * one of the styles as shown below. Month abbreviations may be in any
     * case and are always the first three letters of the month.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     * 
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * 
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those created by toUTCstring.
     *
     * @param string the epoch in string representation
     *
     * @return A TT2000 epoch the epoch value in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */
    public static long fromUTCstring (String string) throws CDFException
    {
       double fraction;
       long ly, lm, ld, lh, ln, ls, ll, lu, la, ymd;
       int len, style;

       ll = lu = la = 0L;
       string = string.trim();
       style = scanUTCstring(string);
       if (style == 0) { /* %2ld-%c%c%c-%4ld %2ld:%2ld:%2ld.%9ld */
         String moString, tmpStr;
         long tmp;
         if (string.endsWith("Z") || string.endsWith("z"))
           string = string.substring(0, string.length() - 1);
         try {
           ld = Long.valueOf(string.substring(0,2)).longValue();
           String mon = string.substring(3,6);
           lm = (long) MonthNumber(mon);
           ly = Long.valueOf(string.substring(7,11)).longValue();
           lh = Long.valueOf(string.substring(12,14)).longValue();
           ln = Long.valueOf(string.substring(15,17)).longValue();
           ls = Long.valueOf(string.substring(18,20)).longValue();
           tmpStr = string.substring(21);
           tmp = Long.valueOf(tmpStr).longValue();
           if (ly == 9999 && lm == 12 && ld == 31 && lh == 23 &&
               ln == 59 && ls == 59 && tmp == 999999999)
             return FILLED_TT2000_VALUE;
           if (ly == 0 && lm == 1 && ld == 1 && lh == 0 &&
               ln == 0 && ls == 0 && tmp == 0)
             return DEFAULT_TT2000_PADVALUE;
           if (tmp == 0) {
             ll = lu = la = 0;
           } else {
             len = (int) tmpStr.length();
             if (len < 9) tmp = tmp * (long) Math.pow (10.0, 9-len);
             ll = (long) (tmp / 1000000);
             tmp = tmp - ll * 1000000;
             lu = (long) (tmp / 1000);
             la = (long) (tmp - lu * 1000);
           }
           return fromUTCparts ((double) ly, (double) lm, (double) ld,
                                (double) lh, (double) ln, (double) ls,
                                (double) ll, (double) lu, (double) la);
         } catch (Exception ix0) {
           return ILLEGAL_TT2000_VALUE;
         }
       } else if (style == 1) { /* %4ld%2ld%2ld.%lld */
         long tmp;
         try {
           ly = Long.valueOf(string.substring(0,4)).longValue();
           lm = Long.valueOf(string.substring(4,6)).longValue();
           ld = Long.valueOf(string.substring(6,8)).longValue();
           tmp = Long.valueOf(string.substring(9)).longValue();
           if (ly == 9999 && lm == 12 && ld == 31 && tmp == 9999999999L) 
             return FILLED_TT2000_VALUE;
           if (ly == 0 && lm == 1 && ld == 1 && tmp == 0) 
             return DEFAULT_TT2000_PADVALUE;
           if (tmp != 0) {
             int digits = Long.valueOf(tmp).toString().length();
             fraction = ld + (double) tmp / Math.pow(10.0, digits);
           } else 
             fraction = (double) ld;
           return fromUTCparts ((double) ly, (double) lm, fraction);
         } catch (Exception ix1) {
           return ILLEGAL_TT2000_VALUE;
         }
       } else if (style == 2) { /* %4ld%2ld%2ld%2ld%2ld%2ld */
         try {
           ly = Long.valueOf(string.substring(0,4)).longValue();
           lm = Long.valueOf(string.substring(4,6)).longValue();
           ld = Long.valueOf(string.substring(6,8)).longValue();
           lh = Long.valueOf(string.substring(8,10)).longValue();
           ln = Long.valueOf(string.substring(10,12)).longValue();
           ls = Long.valueOf(string.substring(12)).longValue();
           if (ly == 9999 && lm == 12 && ld == 31 && lh == 23 &&
               ln == 59 && ls == 59)  return FILLED_TT2000_VALUE;
           if (ly == 0 && lm == 1 && ld == 1 && lh == 0 &&
               ln == 0 && ls == 0)  return DEFAULT_TT2000_PADVALUE;
           return fromUTCparts ((double) ly, (double) lm, (double) ld,
                                (double) lh, (double) ln, (double) ls,
                                0.0, 0.0, 0.0);
         } catch (Exception ix2) {
           return ILLEGAL_TT2000_VALUE;
         }
       } else if (style == 3 || style == 4) {
         /* %4ld-%2ld-%2ldT%2ld:%2ld:%2ld[.%9ld] */
         /* or, %4ld-%2ld-%2ldT%2ld:%2ld:%2ld[.%9ld]Z */
         long tmp; int len0;
         String tmpStr;
         try {
           ly = Long.valueOf(string.substring(0,4)).longValue();
           lm = Long.valueOf(string.substring(5,7)).longValue();
           ld = Long.valueOf(string.substring(8,10)).longValue();
           lh = Long.valueOf(string.substring(11,13)).longValue();
           ln = Long.valueOf(string.substring(14,16)).longValue();
           ls = Long.valueOf(string.substring(17,19)).longValue();
           len0 = string.length();
           len = 0;
           if (len0 > 20) {
             tmpStr = string.substring(20);
             int ix;
             ix = tmpStr.indexOf("Z");
             len = (int) tmpStr.length();
             if (ix == -1)
               tmp = Long.valueOf(tmpStr).longValue();
             else {
               if (len > 1)
                 tmp = Long.valueOf(tmpStr.substring(0, ix)).longValue();
               else
                 tmp = 0;
               --len;
             }
           } else {
             tmp = 0;
           }
           if (ly == 9999 && lm == 12 && ld == 31 && lh == 23 &&
               ln == 59 && ls == 59 && tmp == 999999999)
             return FILLED_TT2000_VALUE;
           if (ly == 0 && lm == 1 && ld == 1 && lh == 0 &&
               ln == 0 && ls == 0 && tmp == 0)
             return DEFAULT_TT2000_PADVALUE;
           if (tmp == 0) {
             ll = lu = la = 0;
           } else {
             if (len < 9) tmp = tmp * (long) Math.pow (10.0, 9-len);
             ll = (long) (tmp / 1000000);
             tmp = tmp - ll * 1000000;
             lu = (long) (tmp / 1000);
             la = (long) (tmp - lu * 1000);
           }
           return fromUTCparts ((double) ly, (double) lm, (double) ld,
                                (double) lh, (double) ln, (double) ls,
                                (double) ll, (double) lu, (double) la);
         } catch (Exception ix3) {
           return ILLEGAL_TT2000_VALUE;
         }
       } else
         throw new CDFException (TT2000_TIME_ERROR);
    }

    /**
     * 
     * This method parses an input, UTC-based, date/time string in ISO 8601
     * and returns their date/time components.
     * String has to be in ISO 8601 form:
     *    yyyy-mm-ddThh:mm:ss.ccccccccc
     *    1990-04-01T03:05:02.000000000
     *    1993-10-10T23:45:49.777888999
     *
     * @param string the epoch in string representation
     *
     * @return UTC date/time components
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */
    public static long[] fromUTCISO8601string (String string) throws CDFException
    {
       long[] tt2000 = new long[9];
       int style;

       style = scanUTCstring(string);
       if (style == 3 || style == 4) {
         /* %4ld-%2ld-%2ldT%2ld:%2ld:%2ld[.%9ld] */
         /* or */
         /* %4ld-%2ld-%2ldT%2ld:%2ld:%2ld[.%9ld]Z */
         long tmp;
         String tmpStr;
         try {
           tt2000[0] = Long.valueOf(string.substring(0,4)).longValue();
           tt2000[1] = Long.valueOf(string.substring(5,7)).longValue();
           tt2000[2] = Long.valueOf(string.substring(8,10)).longValue();
           tt2000[3] = Long.valueOf(string.substring(11,13)).longValue();
           tt2000[4] = Long.valueOf(string.substring(14,16)).longValue();
           tt2000[5] = Long.valueOf(string.substring(17,19)).longValue();
           tmpStr = string.substring(20);
           int ix, len, len0;
           len0 = string.length();
           len = 0;
           if (len0 > 20) {
             tmpStr = string.substring(20);
             ix = tmpStr.indexOf("Z");
             len = (int) tmpStr.length();
             if (ix == -1)
               tmp = Long.valueOf(tmpStr).longValue();
             else {
               if (len > 1)
                 tmp = Long.valueOf(tmpStr.substring(0, ix)).longValue();
               else
                 tmp = 0;
               --len;
             }
           } else {
             tmp = 0;
           }
           if (tmp == 0) {
             tt2000[6] = tt2000[7] = tt2000[8] = 0;
           } else {
             if (len < 9) tmp = tmp * (long) Math.pow (10.0, 9-len);
             tt2000[6] = (long) (tmp / 1000000);
             tmp = tmp - tt2000[6] * 1000000;
             tt2000[7] = (long) (tmp / 1000);
             tt2000[8] = (long) (tmp - tt2000[7] * 1000);
           }
           return tt2000;
         } catch (Exception ix3) {
           return new long[] {0L, 1L, 1L, 0L, 0L, 0L, 0L, 0L, 0L};
         }
       } else
         throw new CDFException (TT2000_TIME_ERROR);
    }

    /**
     *
     * This method parses an input date/time string and returns a TT2000
     * epoch value, nanoseconds since J2000.  The string must be in one of the
     * styles as shown below. Month abbreviations may be in any case and are
     * always the first three letters of the month.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     *
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     *
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those created by toUTCstring.
     *
     * @param string the epoch in string representation
     *
     * @return A TT2000 epoch the epoch value in nanoseconds since J2000
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal
     *                         component value is detected.
     */
    public static long parse (String string) throws CDFException
    {
       return fromUTCstring (string);
    }

    /**
     * 
     * This method parses an array of input date/time strings and returns an
     * array of  TT2000 epoch values, nanoseconds since J2000.
     * The string must be in one of the 
     * styles as shown below. Month abbreviations may be in any case and are
     * always the first three letters of the month.
     *
     * <PRE>
     * Style:0    dd-mmm-yyyy hh:mm:ss.ccccccccc
     * Examples:  01-Apr-1990 03:05:02.000000000
     *            10-Oct-1993 23:45:49.777888999
     *
     * Style:1    yyyymmdd.cccccccccc
     * Examples:  19900401.1234567890
     *            19931010.9998887776
     *
     * Style:2    yyyymmddhhmmss
     * Examples:  19900401030502
     *            19931010234549
     * 
     * Style:3    yyyy-mm-ddThh:mm:ss.ccccccccc
     * Examples:  1990-04-01T03:05:02.000000000
     *            1993-10-10T23:45:49.777888999
     * 
     * Style:4    yyyy-mm-ddThh:mm:ss.cccccccccZ
     * Examples:  1990-04-01T03:05:02.000000000Z
     *            1993-10-10T23:45:49.777888999Z
     * </PRE>
     *
     * These styles are the same as those created by toUTCstring.
     *
     * @param strings an array of epochs in string representation
     *
     * @return TT2000 epoch values in nanoseconds since J2000 or null if null
     *         or an empty input array is detected
     *
     * @exception CDFException an TT2000_TIME_ERROR if an illegal 
     *                         component value is detected.
     */
    public static long[] parse (String[] strings) throws CDFException
    {
       if (strings == null) return null;
       int len = strings.length;
       if (len <= 0) return null;
       long[] tt2000 = new long[len];
       for (int i = 0; i < len; ++i)
         tt2000[i] = CDFTT2000.parse (strings[i]);
       return tt2000;
    }

    /**
    /**
     * 
     * This method returns the last UTC date that a leap second was added in
     * the leap second table used in the class. This can be used to check
     * whether the table is up-to-date.
     *
     * @return the date (year, month, day) in a long array
     */
    public static long[] CDFgetLastDateinLeapSecondsTable ()
    {
       if (LTD == null) LoadLeapSecondsTable();
       long [] date = new long[3];
       date[0] = (long) LTD[entryCnt-1][0];
       date[1] = (long) LTD[entryCnt-1][1];
       date[2] = (long) LTD[entryCnt-1][2];
       return date;
    }

    /**
     * 
     * This method converts the UTC-based date/time in a GregorianCalendar
     * class object to TT2000 time.
     *
     * @param  gc the Gregorian calendar object
     *
     * @return the TT2000 time
     */
    public static long fromGregorianTime (GregorianCalendar gc)
    {
       long year = (long) gc.get(Calendar.YEAR);
       long month = (long) gc.get(Calendar.MONTH) + 1;
       long day = (long) gc.get(Calendar.DAY_OF_MONTH);
       long hour = (long) gc.get(Calendar.HOUR_OF_DAY);
       long minute = (long) gc.get(Calendar.MINUTE);
       long second = (long) gc.get(Calendar.SECOND);
       long milsec = (long) gc.get(Calendar.MILLISECOND);
       long theDate = 10000*year + 100 * month + day;
       if ((year<1708 || year > 2291) && (ValidateYMD(year, month, day) == 0))
         return ILLEGAL_TT2000_VALUE;
       else {
         try {
           return CDFTT2000.fromUTCparts((double) year, (double) month,
                                         (double) day, (double) hour,
                                         (double) minute, (double) second,
                                         (double) milsec, 0.0, 0.0);
         } catch (Exception es) {
           return ILLEGAL_TT2000_VALUE;
         }
       }
    }

    /**
     * 
     * This method converts the UTC-based date/time in TT2000 type to a
     * GregorianCalendar class object in default, local time zone.
     *
     * @param  tt2000 the TT2000 data value
     *
     * @return a GregorianCalendar object
     */
    public static GregorianCalendar toGregorianTime (long tt2000)
    {
       return CDFTT2000.toGregorianTime (tt2000,
                                         new GregorianCalendar().getTimeZone());
    }

    /**
     * 
     * This method converts the UTC-based date/time in TT2000 type to a
     * GregorianCalendar class object in specified time zone.
     *
     * @param  tt2000 the TT2000 data value
     * @param  tz the time zone
     *
     * @return a GregorianCalendar object
     */
    public static GregorianCalendar toGregorianTime (long tt2000, TimeZone tz)
    {
       long[] date = CDFTT2000.toUTCparts(tt2000);
       GregorianCalendar gc = new GregorianCalendar();
       gc.setTimeZone(tz);
       gc.set((int)date[0], (int)date[1]-1, (int)date[2], (int)date[3],
              (int)date[4], (int)date[5]);
       gc.set(Calendar.MILLISECOND, (int)date[6]);
       return gc;
    }

    /**
     *
     * This method returns the status code reflecting whether the leap
     * seconds are from a external file, defined by an environment variable,
     * or the leap seconds are based on the hard-coded table in the class.
     *
     * @return status 1 if form a file, 0 hard-coded
     */
    public static int CDFgetLeapSecondsTableStatus ()
    {
       if (LTD == null) LoadLeapSecondsTable();
       return CDFTT2000.fromFile;
    }


    /**
     *
     * This method returns the leap seconds table.
     *
     * @return table The table contents of the leap seconds
     */
    public static double[][] CDFgetLeapSecondsTable ()
    {
       if (LTD == null) LoadLeapSecondsTable();
       return LTD;
    }


    /**
     *
     * This method returns the number of entries in the leap seconds table.
     *
     * @return enrtyCnt The entry count in the leap seconds table
     */
    public static int CDFgetRowsinLeapSecondsTable ()
    {
       if (LTD == null) LoadLeapSecondsTable();
       return entryCnt;
    }

    /**
     * This method returns the TT2000 time in nanoseconds based on the last
     * leap second day.
     *
     * @param yy the year, a full year in long
     * @param mm the month, a full month in long
     * @param dd the day, a full day in long
     * @param hh the hour, a full hour in long
     * @param mn the minute, a full minute in long
     * @param ss the second, a full second in long
     * @param ms the millisecond, a full millisecond in long
     * @param us the microsecond, a full microsecond in long
     * @param ns the nanosecond, a full nanosecond in long
     * @param yymmdd the last leap second day (in YYYYMMDD) the UTC date is
     *               based upon, an int value 
     *
     * @return TT2000 time in nanoseconds
    */

    public static long computeTT2000withBasedLeapDay (long yy, long mm,
                                                      long dd, long hh,
                                                      long mn, long ss,
                                                      long ms, long us,
                                                      long ns, int yymmdd)
    {
	return computeTT2000withBasedLeapDay ((double) yy, (double)mm, 
					      (double) dd, (double)hh,
					      (double) mn, (double)ss,
					      (double) ms, (double)us,
					      (double) ns, yymmdd);
    }

    /**
     * This method returns the TT2000 time in nanoseconds based on the last
     * leap second day. 
     * This function works similarly to fromUTCparts,
     * with an adjustment if necessary. It returns the TT2000 from UTC date/time
     * based on the passed last leap second day, while fromUTCparts
     * function returns based on the day from the last entry in the current leap
     * second table (LST). 
     * If the based leap second day is not a zero (0) or if the UTC date/time
     * is extended over the next leap second day in the LST, one second(s) will
     * be adjusted (deduction), due to the newly added leap second(s) in the
     * LST. Otherwise, it returns the same value as fromUTCparts.
     *
     * @param yy the year, a full year in double
     * @param mm the month, a full month in double
     * @param dd the day, a full day in double
     * @param hh the hour, a full hour in double
     * @param mn the minute, a full minute in double
     * @param ss the second, a full second in double
     * @param ms the millisecond, a full millisecond in double
     * @param us the microsecond, a full microsecond in double
     * @param ns the nanosecond, a full nanosecond in double
     * @param yymmdd the last leap second day (in YYYYMMDD) the UTC date is
     *               based upon, an int value 
     *
     * @return TT2000 time in nanoseconds
    */

    public static long computeTT2000withBasedLeapDay (double yy, double mm,
                                                      double dd, double hh,
                                                      double mn, double ss,
                                                      double ms, double us,
                                                      double ns, int yymmdd)
    {
      long nanoSecSinceJ2000;
      if (yy < 0.0 || mm < 0.0 || dd < 0.0 || hh < 0.0 || mn < 0.0 ||
          ss < 0.0 || ms < 0.0 || us < 0.0 || ns < 0.0)
        return ILLEGAL_TT2000_VALUE;
      if ((dd-Math.floor(dd)) != 0.0 || (mm-Math.floor(mm)) != 0.0 ||
          (dd-Math.floor(dd)) != 0.0 || (hh-Math.floor(hh)) != 0.0 ||
          (mn-Math.floor(mn)) != 0.0 || (ss-Math.floor(ss)) != 0.0 ||
          (ms-Math.floor(ms)) != 0.0 || (us-Math.floor(us)) != 0.0 ||
          (ns-Math.floor(ns)) != 0.0)
        return ILLEGAL_TT2000_VALUE;
      try {
        nanoSecSinceJ2000 = fromUTCparts (yy, mm, dd, hh, mn, ss, ms, us, ns);
      } catch (CDFException ex) {
        return ILLEGAL_TT2000_VALUE;
      }
      if (yymmdd <= 0 ||
          ((long)yy*10000+(long)mm*100+(long)dd) < (long)yymmdd ||
          (((long)yy*10000+(long)mm*100+(long)dd) == (long)yymmdd &&
           ((long)hh*10000+(long)mn*100+(long)ss) < 235960))
        return nanoSecSinceJ2000;
      else {
        int ix, iy;
        ix = leapSecondLastUpdatedinTable (yymmdd);
        int ymd = (int) ((long)yy*10000 + (long)mm*100 + (long)dd);
        iy = leapSecondLastUpdatedinTable (ymd);
        return (nanoSecSinceJ2000 - (long)(iy-ix)*1000000000L);
      } 

    }

    /**
     * breakdownTT2000withBasedLeapDay. 
     * This method is similar to toUTCparts (aka breakdown),
     * with an adjustment if necessary. It returns UTC date/time from TT2000 
     * value based on the passed last leap second day, while toUTCparts
     * method is based on the last leap second updated day in the current leap
     * second table (LST). 
     * If the based leap second day is zero (0), this method functions just like
     * toUTCparts. If the TT2000 value was computed based on the LST that was
     * not up-to-date, one second(s) adjustment (addition) might be necessay
     * if its value crosses over the next new leap second day in the LST.
     *
     * @param tt2000 the nanoseconds since J2000
     * @param yymmdd the last leap second day (in YYYYMMDD) the UTC date is
     *               based upon, an int value 
     *
     * @return TT2000 time in nanoseconds
    */

    public static long[] breakdownTT2000withBasedLeapDay (long tt2000,
                                                          int yymmdd)
    {
      long baseTT2000;
      int off, ix, iy;
      if (yymmdd <= 0) return toUTCparts (tt2000);
      ix = leapSecondLastUpdatedinTable (yymmdd);
      off = entryCnt - ix - 1;
      if (off == 0)
        return toUTCparts (tt2000);
      else {
        long[] tmpTT2000s = new long[off];
        for (iy = 0; iy < off; ++iy)
          tmpTT2000s[iy] = computeTT2000withBasedLeapDay ((long)LTD[ix+1+iy][0],
                                                          (long)LTD[ix+1+iy][1],
                                                          (long)LTD[ix+1+iy][2],
                                                          0L, 0L, 0L, 0L, 0L, 0L,
                                                          yymmdd);
        for (iy = off; iy > 0; --iy) {
          if (tt2000 >= tmpTT2000s[iy-1]) {
            return toUTCparts (tt2000+(long)iy*1000000000L);
          }
        }
        return toUTCparts (tt2000);
      }
    }

    /**
     * encodeTT2000withBasedLeapDay.
     * This method is similar to toUTCstring (aka encodeTT2000),
     * with an adjustment if necessary. It encodes TT2000 to UTC string in ISO
     * 8601 form based on the passed last leap second day, while toUTC_string
     * method is based on the last leap second updated day in the current leap
     * second table (LST). 
     * If the based leap second day is zero (0), this method functions just like
     * toUTCstring. If the TT2000 value was computed based on the LST that was
     * not up-to-date, one second(s) adjustment (addition) might be necessay 
     * if its value crosses over the next new leap second day in the LST.

     * @param tt2000 the nanoseconds since J2000 with leap seconds
     *               second day
     * @param yymmdd the last leap second day (in YYYYMMDD) the UTC date is
     *               based upon, an int value 
     *
     * @return TT2000 an ISO 8601 UTC string
    */

    public static String encodeTT2000withBasedLeapDay (long tt2000,
                                                       int yymmdd)
    {
	int off, ix, iy;
	if (yymmdd <= 0) return toUTCstring (tt2000);
	ix = leapSecondLastUpdatedinTable (yymmdd);
	off = entryCnt - ix - 1;
	if (off == 0) return toUTCstring (tt2000);
	long[] tmpTT2000s = new long[off];
	for (iy = 0; iy < off; ++iy)
	  tmpTT2000s[iy] = computeTT2000withBasedLeapDay (LTD[ix+1+iy][0],
	                                                  LTD[ix+1+iy][1],
	                                                  LTD[ix+1+iy][2],
	                                                  0.0, 0.0, 0.0, 0.0,
	                                                  0.0, 0.0, yymmdd);
	for (iy = off; iy > 0; --iy) {
	  if (tt2000 >= tmpTT2000s[iy-1]) {
	    return toUTCstring (tt2000+(long)iy*1000000000L);
	  }
	}
	return toUTCstring (tt2000);
    }

    /**
     * parseTT2000withBasedLeapDay.
     * This method is similar to fromUTCstring (aka parseTT2000),
     * with an adjustment if necessary. It returns a TT2000 value from the UTC
     * string in ISO 8601 form based on the passed last leap second day, while
     * fromUTCstring method is based on the last leap second updated
     * day in the current leap second table (LST). 
     * If the based leap second day is zero (0), this method functions just like
     * fromUTCstring. If the UTC string was encoded based on the LST that was
     * not up-to-date, one second(s) adjustment (addition) might be necessary
     * if its day crosses over the next new leap second day in the LST.

     * @param tt2000 the UTC string (in ISO 8601 form) based on the last leap
     *               second day
     * @param yymmdd the last leap second day (in YYYYMMDD) the UTC date is
     *               based upon, an int value 
     *
     * @return TT2000 time in nanoseconds
    */

    public static long parseTT2000withBasedLeapDay (String tt2000,
                                                    int yymmdd)
    {
      try {
        long[] datetime = fromUTCISO8601string (tt2000);
        return computeTT2000withBasedLeapDay (datetime[0], datetime[1],
					      datetime[2], datetime[3],
					      datetime[4], datetime[5],
					      datetime[6], datetime[7],
					      datetime[8], yymmdd);
      } catch (CDFException xx) {
	return ILLEGAL_TT2000_VALUE;
      }
    }

    /**
    * leapSecondLastUpdatedinTable.
    * Returns the index in the leap second table (LST) for the passed leap 
    * second last updated day (in YYYYMMDD). If the day is not in the LST, the 
    * nearest index from the table is returned, which has the day less than 
    * the passed day. 
    */

    private static int leapSecondLastUpdatedinTable (int leapSecondLastUpdated)
    {
      int i, last;
      if (LTD == null) LoadLeapSecondsTable ();
      last = entryCnt - 1;
      for (i = last; i >= 0; --i) {
        if (leapSecondLastUpdated >= (int) (10000*LTD[i][0] + 100*LTD[i][1] +
                                            LTD[i][2])) return i;
      }
      return 0;
    }

    /**
     * 
     * Converts a TT2000 value to a unix time of a double value (seconds from 
     * 1970-01-01 00:00:00 UTC). The unix time will have a time resolution of 
     * microseconds at its fraction part.
     *
     * Note: As unix time does not have leap seconds, the converted time might
     *       not be properly presented. Also, TT2000 have a higher time
     *       resolution than the unix time. The conversion might not precisely
     *       preserve the microseconds due to the floating point presentation.
     *
     * @param epoch the epoch value
     *
     * @return A unix time value
     */
    public static double TT2000toUnixTime (long epoch)
    {
        try {
          long[] dateTime = breakdownTT2000 (epoch);
          double tmp = Epoch.compute(dateTime[0], dateTime[1], dateTime[2],
                                     dateTime[3], dateTime[4], dateTime[5],
                                     dateTime[6]);
          long micsecs = dateTime[7];
          if (dateTime[8] > 500) micsecs += 1;
          return (tmp - BeginUnixTimeEPOCH)/1000.0+micsecs/Math.pow(10.0,6.0);
        } catch (Exception ex) {
          return 0.0;
        }
    }

    /**
     * 
     * Converts an array of TT2000 values to an array of unix time of a double
     * value (seconds from 1970-01-01 00:00:00 UTC). The unix time will have a
     * time resolution of microseconds at its fraction part.
     *
     * Note: As unix time does not have leap seconds, the converted time might
     *       not be properly presented. Also, TT2000 have a higher time
     *       resolution than the unix time. The conversion might not precisely
     *       preserve the microseconds due to the floating point presentation.
     *
     * @param epoch the epoch values
     *
     * @return Array of unix time values
     */
    public static double[] TT2000toUnixTime (long[] epoch)
    {
        double[] unixTimes = new double[epoch.length];
        for (int i = 0; i < epoch.length; ++i)
          unixTimes[i] = TT2000toUnixTime (epoch[i]);
        return unixTimes;
    }

    /**
     * 
     * Converts a Unix time of a double value (seconds from 
     * 1970-01-01 00:00:00 UTC) to a TT2000 time. The unix time can have a 
     * time resolution of milliseconds or microseconds at its fraction part.
     *
     * Note: As unix time does not have leap seconds, the converted time might
     *       not be properly presented. Also, TT2000 have a higher time
     *       resolution than the unix time. The conversion might not precisely
     *       preserve the microseconds due to the floating point presentation. 
     *
     * @param unixTime the unix time value
     *
     * @return An TT2000 value
     */
    public static long UnixTimetoTT2000 (double unixTime)
    {
        try {
          double milsecs = unixTime * 1000.0;
          double submils = milsecs - (long) milsecs;
          double submics = submils * 1000.0;
          long   microsecs = (long) submics;
          if ((submics - microsecs) > 0.5) microsecs += 1;
          long[] dateTime = Epoch.breakdown ((long) milsecs + BeginUnixTimeEPOCH);
          return compute (dateTime[0], dateTime[1], dateTime[2], dateTime[3], 
                          dateTime[4], dateTime[5], dateTime[6], microsecs, 0L);
        } catch (Exception ex) {
          return 0;
        }
    }

    /**
     * 
     * Converts an array of Unix time of double values (seconds from 
     * 1970-01-01 00:00:00 UTC) to an array of TT2000 times. The unix time can
     * have a time resolution of milliseconds or microseconds at its fraction
     * part.
     *
     * Note: As unix time does not have leap seconds, the converted time might
     *       not be properly presented. Also, TT2000 may have a higher time
     *       resolution than the unix time. The conversion might not precisely
     *       preserve the microseconds due to the floating point presentation.
     *
     * @param unixTimes the unix time values
     *
     * @return An array of TT2000 values
     */
    public static long[] UnixTimetoTT2000 (double[] unixTimes)
    {
        long[] epoch = new long[unixTimes.length];
        for (int i = 0; i < unixTimes.length; ++i)
          epoch[i] = UnixTimetoTT2000 (unixTimes[i]);
        return epoch;
    }

}
