package nxm.sys.test.junit.lib;

import nxm.sys.lib.*;
import nxm.sys.test.junit.NMTestCase;

import java.util.*;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

/** This will test the Time class.
    @author  J. Czechowski IV / Neon Ngo
 */
public final class TimeTest extends NMTestCase {
  /** Tolerance: Exact (i.e. 0.0) */
  private static final Double TOL_NONE = Double.valueOf(0.0);
  /** Tolerance: Accurate to 0.001 (milliseconds) = 1e-3 */
  private static final Double TOL_MS   = Double.valueOf(0.0009);
  /** Tolerance: Accurate to 0.000001 (microseconds) = 1e-6 */
  private static final Double TOL_MuS   = Double.valueOf(0.0000009);
  /** Tolerance: Accurate to 0.000000001 (nanoseconds) = 1e-9 */
  private static final Double TOL_NS   = Double.valueOf(0.0000000009);
  /** Tolerance: Accurate to 0.000000000001 (picoseconds) = 1e-12 */
  private static final Double TOL_PS   = Double.valueOf(0.0000000000009);
  /** Tolerance: Accurate to 0.000000000000001 (femtoseconds) = 1e-15 */
  private static final Double TOL_FS   = Double.valueOf(0.0000000000000009);
  /** Tolerance: default (1e-7) */
  private static final double TOLERANCE = 0.0000001;

  //***********************************************************************************************************
  //      TIME VALUES and TIME TESTS for Main group of testing
  //***********************************************************************************************************
  //
  // Time Values:
  //   TIME_VALUES[i*2+0]=wsec, TIME_VALUES[i*2+1]
  //
  // Time Conversions:
  //   TIME_TESTS[i*5+0] = Format Name
  //   TIME_TESTS[i*5+1] = String->Time Tolerance (wsec)
  //   TIME_TESTS[i*5+2] = String->Time Tolerance (fsec)
  //   TIME_TESTS[i*5+3] = String[] (String Values)
  //   TIME_TESTS[i*5+4] = double[] (wsec,fsec Values)
  //
  //   TIME_VALUES[i*2+0],TIME_VALUES[i*2+1] --> TIME_STRING_<fmt>[i]
  //   TIME_STRING_<fmt>[i] --> TIME_PARSED_<fmt>[i*2+0],TIME_PARSED_<fmt>[i*2+1]
  // ****  TODO: by 2050 there will need to be a modification in Time class and here: see Jira issue nxm-2914
  private static final double[] TIME_VALUES
    = { 0.0,           0.000, // 00:00:00.000 (time only, fract_of_day=0.0000000000000000)
        51936.0,       0.543, // 14:25:36.543 (time only, fract_of_day=0.6011173958333333)
        86400.0,       0.000, // 24:00:00.000 (time only, special case, see Bug 1250)
        1.812650405E9, 0.867, // 2007:06:10::18:00:05.867 (doy=160, soy=13888805.867, fract=0.7500679050925926)
        1.420115696E9, 0.789, // 1995:01:01::12:34:56.789 (doy=0,   soy=45296.789,    fract=0.5242683912037037)
        1.735734896E9, 0.789, // 2005:01:01::12:34:56.789 (doy=0,   soy=45296.789,    fract=0.5242683912037037)
        1.861919999E9, 0.000, // 2008:12:31::23:59:59.000 (doy=365, soy=31622399,     fract=0.99998843...)
        1.861920001E9, 0.500, // 2009:01:01::00:00:01.500 (doy=0,   soy=1.5,          fract=0.000017361111111)
        1.893455999E9, 0.000, // 2009:12:31::23:59:59.000 (doy=364, soy=31535999,     fract=0.999988425925926)
        1.988150399E9, 0.000, // 2012:12:31::23:59:59.000 (doy=365, soy=31622399,     fract=0.999988425925926)
        1.988063999E9, 0.000, // 2012:12:30::23:59:59.000 (doy=364, soy=31535999,     fract=0.999988425925926)
        1.987977599E9, 0.000, // 2012:12:29::23:59:59.000 (doy=363, soy=31449599,     fract=0.999988425925926)
        1.988150401E9, 0.500 // 2013:01:01::00:00:01.500 (doy=0,   soy=1.5,          fract=0.000017361111111)
    };

  // TIME_DATE[N+0] = year
  // TIME_DATE[N+1] = month
  // TIME_DATE[N+2] = day
  // TIME_DATE[N+3] = hour
  // TIME_DATE[N+4] = min
  // TIME_DATE[N+5] = sec (no fraction)
  // TIME_DATE[N+6] = day of year
  // TIME_DATE[N+7] = day of week
  private static final int[] TIME_DATE
    = { 1950,  1,  1,  0,  0,  0,      0, 1,
        1950,  1,  1, 14, 25, 36,      0, 1,
        1950,  1,  2,  0,  0,  0,      1, 2,
        2007,  6, 10, 18, 00,  5,    160, 1,
        1995,  1,  1, 12, 34, 56,      0, 1,
        2005,  1,  1, 12, 34, 56,      0, 7,
        2008, 12, 31, 23, 59, 59,    365, 4,
        2009,  1,  1, 00, 00, 01,      0, 5,
        2009, 12, 31, 23, 59, 59,    364, 5,
        2012, 12, 31, 23, 59, 59,    365, 2,
        2012, 12, 30, 23, 59, 59,    364, 1,
        2012, 12, 29, 23, 59, 59,    363, 7,
        2013,  1,  1, 00, 00, 01,      0, 3,
    };

  /** Seconds-of-Year for TIME_VALUES */
  private static final double[] TIME_SOY
    = { 0.0,
        51936.543,
        86400.0,
        13888805.867,
        45296.789,
        45296.789,
        31622399,
        1.5,
        31535999,
        31622399,
        31535999,
        31449599,
        1.5,
      };

  private static final Object[] TIME_TESTS
    = { "STD", TOL_NONE, TOL_MS,
               new String[] { "00:00:00.000",              // <-- Special Case: HMS Only
                              "14:25:36.543",              // <-- Special Case: HMS Only
                              "24:00:00.000",              // <-- Special Case: HMS Only (Bug 1250)
                              "2007:06:10::18:00:05.867",
                              "1995:01:01::12:34:56.789",
                              "2005:01:01::12:34:56.789",
                              "2008:12:31::23:59:59.000",
                              "2009:01:01::00:00:01.500",
                              "2009:12:31::23:59:59.000",
                              "2012:12:31::23:59:59.000",
                              "2012:12:30::23:59:59.000",
                              "2012:12:29::23:59:59.000",
                              "2013:01:01::00:00:01.500",},
               TIME_VALUES,

        "ISO8601", TOL_NONE, TOL_MS,
               new String[] { "1950-01-01T00:00:00.000Z",  // since 3.5.4 for BUG 2912 (always output "FULL" date & time)
                              "1950-01-01T14:25:36.543Z",  // since 3.5.4 for BUG 2912 (always output "FULL" date & time)
                              "1950-01-02T00:00:00.000Z",  // since 3.5.4 for BUG 2912 (always output "FULL" date & time) Special Case (Bug 1250)
                              "2007-06-10T18:00:05.867Z",
                              "1995-01-01T12:34:56.789Z",
                              "2005-01-01T12:34:56.789Z",
                              "2008-12-31T23:59:59.000Z",
                              "2009-01-01T00:00:01.500Z",
                              "2009-12-31T23:59:59.000Z",
                              "2012-12-31T23:59:59.000Z",
                              "2012-12-30T23:59:59.000Z",
                              "2012-12-29T23:59:59.000Z",
                              "2013-01-01T00:00:01.500Z"},
               TIME_VALUES,

        "ACQ", TOL_NONE, TOL_MS,
               new String[] { "00:00:00.000",              // <-- Special Case: HMS Only
                              "14:25:36.543",              // <-- Special Case: HMS Only
                              "24:00:00.000",              // <-- Special Case: HMS Only (Bug 1250)
                              "07.161:18:00:05.867",
                              "95.1:12:34:56.789",
                              "05.1:12:34:56.789",
                              "08.366:23:59:59.000",
                              "09.1:00:00:01.500",
                              "09.365:23:59:59.000",
                              "12.366:23:59:59.000",
                              "12.365:23:59:59.000",
                              "12.364:23:59:59.000",
                              "13.1:00:00:01.500"},
               TIME_VALUES,
        "EPOCH", TOL_NONE, TOL_MS,
               new String[] { "0.000",                    // <-- Special Case: Sec Only
                              "51936.543",                // <-- Special Case: Sec Only
                              "86400.000",                // <-- Special Case: Sec Only (Bug 1250)
                              "2007:13888805.867",
                              "1995:45296.789",
                              "2005:45296.789",
                              "2008:31622399.000",
                              "2009:1.500",
                              "2009:31535999.000",
                              "2012:31622399.000",
                              "2012:31535999.000",
                              "2012:31449599.000",
                              "2013:1.500"},
               TIME_VALUES,
        "FILENAME", TOL_NONE, TOL_MS,
               new String[] { "19500101_000000000",
                              "19500101_142536543",
                              "19500102_000000000",
                              "20070610_180005867",
                              "19950101_123456789",
                              "20050101_123456789",
                              "20081231_235959000",
                              "20090101_000001500",
                              "20091231_235959000",
                              "20121231_235959000",
                              "20121230_235959000",
                              "20121229_235959000",
                              "20130101_000001500"},
               TIME_VALUES,
        "NORAD", TOL_NONE, TOL_MS,
               new String[] { "50001.00000000",
                              "50001.60111740",
                              "50002.00000000",
                              "07161.75006791",
                              "95001.52426839",
                              "05001.52426839",
                              "08366.99998843",
                              "09001.00001736",
                              "09365.99998843",
                              "12366.99998843",
                              "12365.99998843",
                              "12364.99998843",
                              "13001.00001736"},
               TIME_VALUES,
        "TCR", TOL_NONE, TOL_MS,
               new String[] { "1:00:00:00.000",
                              "1:14:25:36.543",
                              "2:00:00:00.000",
                              "161:18:00:05.867",
                              "1:12:34:56.789",
                              "1:12:34:56.789",
                              "366:23:59:59.000",
                              "1:00:00:01.500",
                              "365:23:59:59.000",
                              "366:23:59:59.000",
                              "365:23:59:59.000",
                              "364:23:59:59.000",
                              "1:00:00:01.500"},
               toThisYear(TIME_VALUES),
        "VAX", TOL_NONE, TOL_MS,
               new String[] { "00:00:00.000",              // <-- Special Case: HMS Only
                              "14:25:36.543",              // <-- Special Case: HMS Only
                              "24:00:00.000",              // <-- Special Case: HMS Only (Bug 1250)
                              "10-JUN-2007:18:00:05.867",
                              "01-JAN-1995:12:34:56.789",
                              "01-JAN-2005:12:34:56.789",
                              "31-DEC-2008:23:59:59.000",
                              "01-JAN-2009:00:00:01.500",
                              "31-DEC-2009:23:59:59.000",
                              "31-DEC-2012:23:59:59.000",
                              "30-DEC-2012:23:59:59.000",
                              "29-DEC-2012:23:59:59.000",
                              "01-JAN-2013:00:00:01.500"},
               TIME_VALUES,
        "HMS", TOL_NONE, TOL_MS,
               new String[] { "00:00:00.000",
                              "14:25:36.543",
                              "24:00:00.000",              // <-- Special Case: Show as 24:00:00 (Bug 1250)
                              "18:00:05.867",
                              "12:34:56.789",
                              "12:34:56.789",
                              "23:59:59.000",
                              "00:00:01.500",
                              "23:59:59.000",
                              "23:59:59.000",
                              "23:59:59.000",
                              "23:59:59.000",
                              "00:00:01.500"},
               hmsOnly(TIME_VALUES),
        "YMD", TOL_NONE, TOL_MS,
               new String[] { "1950:01:01",
                              "1950:01:01",
                              "1950:01:02",
                              "2007:06:10",
                              "1995:01:01",
                              "2005:01:01",
                              "2008:12:31",
                              "2009:01:01",
                              "2009:12:31",
                              "2012:12:31",
                              "2012:12:30",
                              "2012:12:29",
                              "2013:01:01"},
               ymdOnly(TIME_VALUES),
        "FULL_STD", TOL_NONE, TOL_MS,                                  // Since 2.5.2  Bug 2864
               new String[] { "1950:01:01::00:00:00.000",
                              "1950:01:01::14:25:36.543",
                              "1950:01:02::00:00:00.000",
                              "2007:06:10::18:00:05.867",
                              "1995:01:01::12:34:56.789",
                              "2005:01:01::12:34:56.789",
                              "2008:12:31::23:59:59.000",
                              "2009:01:01::00:00:01.500",
                              "2009:12:31::23:59:59.000",
                              "2012:12:31::23:59:59.000",
                              "2012:12:30::23:59:59.000",
                              "2012:12:29::23:59:59.000",
                              "2013:01:01::00:00:01.500",},
               TIME_VALUES,
    };

  private static final String[] ISO8601_TZ = { "-11","-10","-09","-08","-07","-06","-05","-04","-03","-02","-01",
                                               "-00","Z", "","+00",
                                               "+01","+02","+03","+04","+05","+06","+07","+08","+09","+10","+11" };
  private static final int[]    ISO8601_DT = { -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1,
                                               0, 0, 0, 0,
                                               1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

  //************************************************************************************************
  //    TIME VALUES for testing the special case of "Whole Seconds" with a fractional part and
  //    "Fractional Seconds" with a whole part
  //************************************************************************************************
  //
  // More Time Values to test (wsec,fsec) vs expect2012:12:29::23:59:59.000ed sec, wsec, fsec;
  // I.e. test cases for Bug 1635 - integer part of fsec should be moved to wsec
  // and fractional part of wsec should be moved to fsec
  // Also test that the code, that accommodates fsec with whole parts & wsec with fractional parts,
  // handles fractional parts that total to just less than 1 properly (Bug 2759)
  //   MORE_TIME_VALUES[i*5+0] = (IN) wsec
  //   MORE_TIME_VALUES[i*5+1] = (IN) fsec
  //   MORE_TIME_VALUES[i*5+2] = Expected whole & fractions seconds (wsec+fsec)
  //   MORE_TIME_VALUES[i*5+3] = Expected whole seconds
  //   MORE_TIME_VALUES[i*5+4] = Expected fractional seconds
  private static final double[] MORE_TIME_VALUES
    //       in wsec, in fsec, expected sec, expected wsec, expected fsec
    = { 1854642006.1,   2.500, 1854642008.6,  1854642008.0, 0.6,
        1854642006.5,   3.500, 1854642010.0,  1854642010.0, 0.0, // make sure fractional seconds are carried over
               120.8,   4.9,          125.7,         125.0, 0.7,
              123.5,  -1.9,          121.6,         121.0, 0.6, // test negative time values
               123.5,  -1.2,          122.3,         122.0, 0.3,
              -123.0,   1.2,         -121.8,        -122.0, 0.2, // note: wsec is floor(sec) = -122
              -123.9,   0.3,         -123.6,        -124.0, 0.4, // note: wsec is floor(sec) = -124
              -122.9,  -2.3,         -125.2,        -126.0, 0.8, // note: wsec is floor(sec) = -126
        // make sure fsec do not get rounded up to add to wsec, as well as still counting as fsec
        1988150401.0, 0.9999999, 1988150401.9999999, 1988150401.0, 0.9999999
      };

  //*************************************************************************************************************
  // TIME VALUES and TIME TESTS for Testing fromString when there are excessive digits of precision in the time String
  //
  //       Tests that the time value is properly rounded to the highest precision supported (bug 2827)
  //*************************************************************************************************************
  //
  // Time Values:
  //   TIME_VALUES_EXCESSIVE_PREC[i*2+0]=wsec, TIME_VALUES_EXCESSIVE_PREC[i*2+1]=fsec
  //
  // Time Conversions:
  //   TIME_TESTS_EXCESSIVE_PREC[i*5+0] = Format Name
  //   TIME_TESTS_EXCESSIVE_PREC[i*5+1] = String->Time Tolerance (wsec)
  //   TIME_TESTS_EXCESSIVE_PREC[i*5+2] = String->Time Tolerance (fsec)
  //   TIME_TESTS_EXCESSIVE_PREC[i*5+3] = String[] (String Values)
  //   TIME_TESTS_EXCESSIVE_PREC[i*5+4] = double[] (wsec,fsec Values)
  //
  private static final double[] TIME_VALUES_EXCESSIVE_PREC
    = { 2.027506271E9, 0.000, // 2014:04:01::12:11:10.9999999999999999999 should round to:
                              // 2014:04:01::12:11:11.0 (doy=90, soy=7819871.0, fract of day =0.50776620370372633)
        2.0197296E9, 0.12345678901234568 // 2014:01:01::12:00:00.1234567890123456789 should round to:
                                         // 2014:01:01::12:00:00.12345678901234568
                                         //  (doy=90, soy= 43200.12345678901, fract of day=0.5000014288980209762)
  };

  private static final Object[] TIME_TESTS_EXCESSIVE_PREC
    = { "STD", TOL_NONE, TOL_FS,
               new String[] { "2014:04:01::12:11:10.9999999999999999999",
                              "2014:01:01::12:00:00.1234567890123456789"},
               TIME_VALUES_EXCESSIVE_PREC,

        "ISO8601", TOL_NONE, TOL_FS,
               new String[] { "2014-04-01T12:11:10.9999999999999999999",
                              "2014-01-01T12:00:00.1234567890123456789"},
               TIME_VALUES_EXCESSIVE_PREC,

        "ACQ", TOL_NONE, TOL_FS,
               new String[] { "14.91:12:11:10.9999999999999999999",
                              "14.01:12:00:00.1234567890123456789"},
               TIME_VALUES_EXCESSIVE_PREC,
        "EPOCH", TOL_NONE, TOL_FS,
               new String[] { "2014:7819870.9999999999999999999",
                              "2014:43200.1234567890123456789"},
               TIME_VALUES_EXCESSIVE_PREC,
        "FILENAME", TOL_NONE, TOL_FS,
               new String[] {"20140401_1211109999999999999999999",
                             "20140101_1200001234567890123456789"},
               TIME_VALUES_EXCESSIVE_PREC,
        "NORAD", TOL_NONE, TOL_MS,
                 new String[] { "14091.50776620370372633",
                                "14001.5000014288980209762"},
               TIME_VALUES_EXCESSIVE_PREC,
        "TCR", TOL_NONE, TOL_FS,
               new String[] { "91:12:11:10.9999999999999999999",
                              "01:12:00:00.1234567890123456789"},
               toThisYear(TIME_VALUES_EXCESSIVE_PREC),
        "VAX", TOL_NONE, TOL_FS,
               new String[] { "01-APR-2014:12:11:10.9999999999999999999",
                              "01-JAN-2014:12:00:00.1234567890123456789" },
               TIME_VALUES_EXCESSIVE_PREC,
        "HMS", TOL_NONE, TOL_FS,
               new String[] { "12:11:10.9999999999999999999",
                              "12:00:00.1234567890123456789"},
               hmsOnly(TIME_VALUES_EXCESSIVE_PREC),
        "YMD", TOL_NONE, TOL_FS,
               new String[] { "2014:04:01",
                              "2014:01:01"},
               ymdOnly(TIME_VALUES_EXCESSIVE_PREC)
    };


  /** public constructor to allow running this test case directly with JUnit. */
  public TimeTest (String name) { this(name, null); }

  /** Creates a new instance of the class.
      This constructor is required by NeXtMidas Unit Testing framework.
      @param methodName The name of the method being called.
      @param midas      The midas context testTo use when running the tests.
   */
  public TimeTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  public void testSleep ()                { } // No test available
  public void testUsleep ()               { } // No test available
  public void testPause ()                { } // No test available
  public void testCurrent ()              { } // No test available
  public void testFormat ()               { } // Tested in testToString()
  public void testGetWSec ()              { } // tested in testFromJ1950
  public void testGetFSec ()              { } // tested in testFromJ1950
  public void testGetSec ()               { } // tested in testToJ1950
  public void testGetMJS ()               { } // tested in testToJ1950
  public void testGetYear ()              { _testLotsOfStuff(); }
  public void testGetDay ()               { } // tested in testLotsOfStuff()
  public void testGetHour ()              { } // tested in testLotsOfStuff()
  public void testGetMinute ()            { } // tested in testLotsOfStuff()
  public void testGetSecond ()            { } // tested in testLotsOfStuff()
  public void testGetDoY ()               { } // tested in testLotsOfStuff()
  public void testGetSoY ()               { } // tested in testLotsOfStuff()
  public void testGetSoD ()               { } // tested in testLotsOfStuff()
  public void testGetFoD ()               { } // tested in testLotsOfStuff()
  public void testGetYiS ()               { } // tested in testLotsOfStuff()
  public void testGetMonthName ()         { } // tested in testGetMonth()
  public void testGetMonthTrigraph ()     { } // tested in testGetMonth()
  public void testHashCode ()             { } // Tested in testEquals()
  public void testGetDayOfWeekName ()     { } // tested in testGetDayOfWeek()
  public void testGetDayOfWeekTrigraph () { } // tested in testGetDayOfWeek()
  public void testRAGW ()                 { } // Tested in testGetGHA()

  public void testGetDayOfWeek () {
    String[] DATES = { "2007:08:05::00:00:00", "2007:08:06::00:00:00", "2007:08:07::00:00:00",
                       "2007:08:08::00:00:00", "2007:08:09::00:00:00", "2007:08:10::00:00:00",
                       "2007:08:11::00:00:00" };
    int[]    DoW   = { 1, 2, 3, 4, 5, 6, 7 };
    String[] NAMES = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
    String[] TRI   = { "SUN",    "MON",    "TUE",     "WED",       "THU",      "FRI",    "SAT"      };

    for (int i = 0; i < DATES.length; i++) {
      Time t = new Time(DATES[i]);
      assertEquals("Time.getDayOfWeek         t="+t, DoW[i],   t.getDayOfWeek());
      assertEquals("Time.getDayOfWeekName     t="+t, NAMES[i], t.getDayOfWeekName());
      assertEquals("Time.getDayOfWeekTrigraph t="+t, TRI[i],   t.getDayOfWeekTrigraph());
    }
  }

  public void testGetGHA () {
    // This set of tests checks to see that NeXtMidas matches X-Midas. The following host
    // primitive was used in X-Midas (it takes in 0+ parameters):
    //        program GHA
    //          implicit none
    //          include 'primitive.inc'
    //          real*8 wsec,fsec,sec
    //          real*8 year,second,rag
    //          integer*4 x,n,params
    //
    //          call M$CHECKIN
    //          params = M$NUMBER_PARAMETERS()
    //          do n = 1, params
    //            x = M$TPICK(n,wsec,fsec)
    //            sec = wsec + fsec
    //            call M$EPOCH_HOUR_ANGLE(sec,year,second,rag)
    //            type*,year,second,rag
    //          enddo
    //          call M$RETURN
    //        end
    //
    // It is then run with:
    // X-Midas> gha "2008:01:01::00:00:00.000","2008:01:01::12:00:00.000",...

    String[] TESTS = { "2008:01:01::00:00:00.000","2008:01:01::12:00:00.000",
                       "1981:02:14::14:12:00.000","1981:03:20::12:34:56.789",
                       "2008:02:14::14:12:00.000","2008:03:20::12:34:56.789" };

    double[] EXPECTED = { 2008.000000000000,      0.0000000000000000,       1.745836272890770,
                          2008.000000000000,       43200.00000000000,      -1.387154984866250,
                          1981.000000000000,       3852720.000000000,     -0.4342140794516069E-01,
                          1981.000000000000,       6784496.789000034,      0.1168382127061136,
                          2008.000000000000,       3852720.000000000,     -0.5269657584137377E-01,
                          2008.000000000000,       6870896.789000033,      0.1247658398629525 };

    for (int i = 0; i < TESTS.length; i++) {
      Time t = new Time(TESTS[i]);

      assertEquals("Time.getYear("+t+")", EXPECTED[i*3+0], t.getYear(), 0.000);
      assertEquals("Time.getSoY("+t+")",  EXPECTED[i*3+1], t.getSoY(),  0.001);
      assertEquals("Time.getGHA("+t+")",  EXPECTED[i*3+2], t.getGHA(),  0.000001);
    }
  }

  public void testGetMonth () {
    Time t = new Time();

    // Multiple times to test leap years and non-leap years
    for (int yr = 2000; yr <= 2099; yr++) {
      for (int mon = 1; mon <= 12; mon++) {
        String name = null;
        String tri  = null;

        switch (mon) {
          case  1: tri = "JAN"; name = "January";   break;
          case  2: tri = "FEB"; name = "February";  break;
          case  3: tri = "MAR"; name = "March";     break;
          case  4: tri = "APR"; name = "April";     break;
          case  5: tri = "MAY"; name = "May";       break;
          case  6: tri = "JUN"; name = "June";      break;
          case  7: tri = "JUL"; name = "July";      break;
          case  8: tri = "AUG"; name = "August";    break;
          case  9: tri = "SEP"; name = "September"; break;
          case 10: tri = "OCT"; name = "October";   break;
          case 11: tri = "NOV"; name = "November";  break;
          case 12: tri = "DEC"; name = "December";  break;
        }

        t.fromString(yr+":"+mon+":01::00:00:00");
        assertEquals("Time.getYear          "+yr+"/"+mon+"/1 --> t="+t, yr,   t.getYear());
        assertEquals("Time.getMonth         "+yr+"/"+mon+"/1 --> t="+t, mon,  t.getMonth());
        assertEquals("Time.getMonthName     "+yr+"/"+mon+"/1 --> t="+t, name, t.getMonthName());
        assertEquals("Time.getMonthTrigraph "+yr+"/"+mon+"/1 --> t="+t, tri,  t.getMonthTrigraph());
        assertEquals("Time.getDay           "+yr+"/"+mon+"/1 --> t="+t, 1,    t.getDay());
      }
    }
  }

  public void testToString() {
    String formatted;

    for (int i = 0; i < TIME_TESTS.length; i+=5) {
      String   format  = TIME_TESTS[i+0].toString();
      String[] strings = (String[])TIME_TESTS[i+3];
      int      fmt     = Parser.find(Time.formatList,format.toString(), -1);

      for (int j = 0; j < strings.length; j++) {
        Time inTime = new Time(TIME_VALUES[j*2+0], TIME_VALUES[j*2+1]);
        formatted = inTime.toString(fmt,-1);
        assertEquals("Time.toString() fmt="+format+" wsec="+TIME_VALUES[j*2+0]+" fsec="+TIME_VALUES[j*2+1], strings[j], formatted);
      }
    }

    // Test rounding of fractional seconds (Bug 2308)
    double secs     = 1.8154796431983879E9;  // known time
    String expected = "2007:07:13::11:54:03.20";   // secs as a formatted Time

    formatted       = Time.toString(secs, Time.STD, 2);
    assertEquals("Time.toString(double,int,int) Time= "+formatted, expected, formatted);

    formatted = Time.toString(secs, "STD", 2);
    assertEquals("Time.toString(double,String,int) Time= "+formatted, expected, formatted);

    toString_SimpleDateFormat();
    toString_DATE_TIME_ACQ();
  }

  private static void toString_SimpleDateFormat () {
    //***************************************************************************
    // Test new toString (String simpleDateFormatPattern) and new format Full_Std Since 3.5.2 Bug 2864
    //***************************************************************************
    //
    // Note: simpleDateFormat does not support andy finer resolution than milliseconds
    String formatted;

    Time smallTime = new Time(5025); // 1 hour 23 minutes 45.678 seconds
    smallTime.addSec(0.678912345678912);
    Time largeTime = new Time(1.987977599E9); // 2012:12:29::23:59:59.000
    largeTime.addSec(0.123456789012345678);

    // Note: simpleDateFormats should truncate
    formatted      = smallTime.toString("'T:'yyyy:MM:dd::HH:mm:ss.SSS");
    assertEquals("toString(simpleDateFormatPattern\") small time", "T:1950:01:01::01:23:45.678", formatted);
    formatted      = smallTime.toString("yyyyMMddHHmmssSSS");
    assertEquals("toString(simpleDateFormatPattern\") small time", "19500101012345678", formatted);
    formatted      = smallTime.toString("yyyy.MM.dd.HH.mm.ss.SSSSSSSSSSSSSSS");
    assertEquals("toString(simpleDateFormatPattern\") small time femtoseconds", "1950.01.01.01.23.45.678912345678912", formatted);
    formatted      = smallTime.toString("HH:mm:ss.SSSS HH.mm.ss.SSSS");
    assertEquals("toString(simpleDateFormatPattern\") small time twice", "01:23:45.6789 01.23.45.6789", formatted);

    formatted      = largeTime.toString("yyyy-MM-dd::HH:mm:ss.S");
    assertEquals("toString(simpleDateFormatPattern\") large time", "2012-12-29::23:59:59.123", formatted);
    formatted      = largeTime.toString("h:mm a");
    assertEquals("toString(simpleDateFormatPattern\") large time", "11:59 PM", formatted);
    formatted      = largeTime.toString("EEE, d MMM yyyy HH:mm:ss.SSSS" );
    assertEquals("toString(simpleDateFormatPattern\") large time", "Sat, 29 Dec 2012 23:59:59.1234", formatted);
    formatted      = largeTime.toString("d MMM yyyy HH:mm:ss.SSSSSSSSSSSSSSSSSS, EEE" );
    assertEquals("toString(simpleDateFormatPattern\") truncated to femtoseconds", "29 Dec 2012 23:59:59.123456789012345, Sat", formatted);

    // Regular Time Formats should round
    formatted = smallTime.toString("Std");
    assertEquals("toString(simpleDateFormatPattern\") fmt=\"Std\"", "01:23:45.679", formatted);
    formatted = smallTime.toString("Full_Std");
    assertEquals("toString(simpleDateFormatPattern\") fmt=\"Full_Std\"", "1950:01:01::01:23:45.679", formatted);
  }

  private static void toString_DATE_TIME_ACQ () { // added in 3.9.3 for NXM-3139
    String formatted;
    String fmt;
    String expected;
    Time inTime = new Time("2020:01:27:02:52:14.345");

    // DATE_ACQ tests
    fmt = "ACQDATE";
    expected = "20.027";
    formatted = inTime.toString(fmt,-1);
    assertEquals("Time.toString(int,int) Time= "+formatted, expected, formatted);

    // Test rounding other toString methods
    double secs     = inTime.getSec();  // known time

    formatted       = Time.toString(secs, Time.ACQDATE, 2);
    assertEquals("Time.toString(double,int,int) Time= "+formatted, expected, formatted);

    formatted = Time.toString(secs, fmt, 2);
    assertEquals("Time.toString(double,String,int) Time= "+formatted, expected, formatted);
  }

  public void testFromBytes() {
    byte[] bytes = { 9, 123, 66, 5,
                    (byte)'2',(byte)'0',(byte)'0',(byte)'7',(byte)':',
                    (byte)'0',(byte)'6',(byte)':',
                    (byte)'1',(byte)'0',(byte)':',
                    (byte)':',
                    (byte)'1',(byte)'8',(byte)':',
                    (byte)'0',(byte)'0',(byte)':',
                    (byte)'0',(byte)'5',(byte)'.',(byte)'8',(byte)'6',(byte)'7',
                    22, 6, 113 };
    double sec = 1.812650405E9 + 0.867;

    Time t = new Time();
    t.fromBytes(bytes, 4, bytes.length-3);
    assertEquals("Time.fromBytes(..) (wsec)", 1.812650405E9, t.getWSec(), 0.0000000);
    assertEquals("Time.fromBytes(..) (fsec)", 0.867,         t.getFSec(), 0.0000001);
  }

  public void testParse() {
    Time t = new Time();
    for (int i = 0; i < 1; i+=5) {
      String   format  = TIME_TESTS[i+0].toString();
      double   wsecTol = ((Number)TIME_TESTS[i+1]).doubleValue();
      double   fsecTol = ((Number)TIME_TESTS[i+2]).doubleValue();
      String[] strings = (String[])TIME_TESTS[i+3];
      double[] times   = (double[])TIME_TESTS[i+4];
      int      fmt     = Parser.find(Time.formatList, format, -1);

      for (int j = 0; j < strings.length; j++) {
        double sec = Time.parseTime(strings[j],fmt);
        t.fromString(strings[j],fmt);
        assertEquals("Time.parse(..) fmt="+format+" t="+strings[j], t.getSec(), sec, fsecTol);
      }
    }
  }

  /**
     Test isSTDTimeString which has tighter requirement for being a STD time
     than fromString( ,Time.STD).  It must have the full 4 digit year and the
     month, day, hours, minutes, and seconds must fall into a generally valid
     range (i.e. just 12 months in a year and no more than 31 days in a month)
     @since NeXtMidas 3.5.3   Bug 2876  2883
   */
  public void testIsSTDTimeString() {
    // Actual/Expected (were expected is "" if false
    String[][] testStrings = {
        {"2014:13:24::01:02:03.234",""},
        {"2015-1-2::23:12:34","2015:01:02::23:12:34.000"},
        {"1950:12:32::12:24:23.4",""},
        {"2012:5:7::9:7:8.234","2012:05:07::09:07:08.234"},
        {"2013:03:05::24:00:00",""},
        {"1950-1-1::0:0:60","00:01:00.000"},
        {"1999:12:31::23:59:59.999","1999:12:31::23:59:59.999"},
        {"2000:07:01::20:60:00",""},
        {"1991:11:11::12:40:61",""}
    };
    for (int i=0; i<testStrings.length; i++) {
      String timeString = testStrings[i][0];
      String expectString = testStrings[i][1];
      boolean isSTD = !expectString.isEmpty();
      assertEquals("Time.isSTDTimeString "+timeString, isSTD, Time.isSTDTimeString(timeString));
      // make sure all that test as valid convert to Time
      if (isSTD){
        Time t = new Time(timeString);
        assertEquals("Time.isSTDTimeString valid time:"+timeString, expectString, t.toString());
      }
    }
  }

  public void testFromString() {
    Time t = new Time();

    // BASIC TESTS
    double sec = 1.812650405E9 + 0.867;
    String[] list = { "2007:06:10::18:00:05.867", Double.toString(sec) };
    for (int i = 0; i < list.length; i++) {
      t.fromString(list[i]);
      assertEquals("Time.fromString('"+list[i]+"')", sec, t.getSec(), 0.0000001);
      t.fromString("\"" + list[i] + "\""); // check with double quotes around time string
      assertEquals("Time.fromString('\""+list[i]+"\"')", sec, t.getSec(), 0.0000001);
    }

    // TESTS WITH VARIOUS FORMATS
    for (int i = 0; i < TIME_TESTS.length; i+=5) {
      String   format  = TIME_TESTS[i+0].toString();
      double   wsecTol = ((Number)TIME_TESTS[i+1]).doubleValue();
      double   fsecTol = ((Number)TIME_TESTS[i+2]).doubleValue();
      String[] strings = (String[])TIME_TESTS[i+3];
      double[] times   = (double[])TIME_TESTS[i+4];
      int      fmt     = Parser.find(Time.formatList, format, -1);

      for (int j = 0; j < strings.length; j++) {
        t.fromString(strings[j],fmt);
        assertEquals("Time.parse(..) fmt="+format+" t="+strings[j]+" (wsec)", times[j*2+0], t.getWSec(), wsecTol);
        assertEquals("Time.parse(..) fmt="+format+" t="+strings[j]+" (fsec)", times[j*2+1], t.getFSec(), fsecTol);
      }
    }

    // TEST THAT ALTERNATE STD FORMAT "YYYY-MM-DD::HH:MM:SS" IS ACCEPTED
    // (also check that time Strings are trimmed - space as beginning & end ignored)
    String[] altFormat = {"2014-05-04::11:12:13",
                          "2012-03-30::12:20:30.333",
                          " 2012-03-30::12:20:30.123 "};
    String[] stdFormat = {"2014:05:04::11:12:13.000",
                          "2012:03:30::12:20:30.333",
                          "2012:03:30::12:20:30.123"};
    for (int i = 0; i < altFormat.length; i++){
      t.fromString(altFormat[i]);
      assertEquals(altFormat[i] + " treated as alternate STD format", stdFormat[i], t.toString());
    }

    fromStringExcessiveDigits();

    fromString_ACQ();

    fromString_STD();

    fromString_HMS();

    fromString_TCR();

    fromString_EPOCH();

    fromString_ISO(altFormat);

    fromString_SimpleDateFormat();

    fromStringACQDATE();

    fromStringACQTIME();

    fromStringBadTimeString();
  }

  private void fromStringExcessiveDigits () {
    Time t = new Time();
    // TESTS WITH VARIOUS FORMATS WITH EXCESSIVE DIGITS OF PRECISION (bug 2827)
    // (makes sure time is rounded to highest supported precision)
    for (int i = 0; i < TIME_TESTS_EXCESSIVE_PREC.length; i+=5) {
      String   format  = TIME_TESTS_EXCESSIVE_PREC[i+0].toString();
      double   wsecTol = ((Number)TIME_TESTS_EXCESSIVE_PREC[i+1]).doubleValue();
      double   fsecTol = ((Number)TIME_TESTS_EXCESSIVE_PREC[i+2]).doubleValue();
      String[] strings = (String[])TIME_TESTS_EXCESSIVE_PREC[i+3];
      double[] times   = (double[])TIME_TESTS_EXCESSIVE_PREC[i+4];
      int      fmt     = Parser.find(Time.formatList, format, -1);

      for (int j = 0; j < strings.length; j++) {
        t.fromString(strings[j],fmt);
        assertEquals("Time.parse(..) fmt="+format+" t="+strings[j]+" (wsec)", times[j*2+0], t.getWSec(), wsecTol);
        assertEquals("Time.parse(..) fmt="+format+" t="+strings[j]+" (fsec)", times[j*2+1], t.getFSec(), fsecTol);
      }
    }
  }

  private static void fromString_ACQ () {
    Time t = new Time();

    // TESTS to ensure ACQ format strings with only one "dot" are still properly read.
    t.fromString("1999.333:12:34:56", Time.ACQ);
    assertEquals("Time.fromString(\"1999.333:12:34:56\", ACQ)","1999:11:29::12:34:56.000", t.toString());
    t.fromString("99.333:12:34:56", Time.ACQ);
    assertEquals("Time.fromString(\"99.333:12:34:56\", ACQ)","1999:11:29::12:34:56.000", t.toString());

    // TEST Supported ACQ Time String Formats
    // ACQ Formats supported
    // 1)[YY]YY.DDD:HH:MM:SS[.SSS]  <-- original format - no adjustments needed
    // 2)[:]HH:MM:SS[.SSS]          <-- only adjustment is stripping any leading colon
    // 3)[YY]YY.DDD[:]              <-- adjust to start with day "field" (4)
    // 4)[YY]YYMMDD:HH:MM:SS[.SSS]  <-- grab out year, month, & day & leave HH:MM:SS to process
    // 5)[YY]YYMMDD[:]              <-- grab out year, month, & day
    // Including double or single digit day-of-year (DDD), and single digit hour , minute
    // , and seconds
    String[][] supportedACQStrings = {
        // {Examples of format 1, matching STD format}
        {"2014.234:16:47:58.123", "2014:08:22::16:47:58.123"},
        {"1967.234:16:47:58", "1967:08:22::16:47:58.000"},
        {"14.234:16:47:58.123", "2014:08:22::16:47:58.123"},
        {"2014.12:3:4:5.123","2014:01:12::03:04:05.123"},
        {"2014.1:3:4:5", "2014:01:01::03:04:05.000"},
        {"2014.234:16:47:58.1234567890123456789", "2014:08:22::16:47:58.123"},
        {"2014.234:16:47:58.", "2014:08:22::16:47:58.000"}, //Bug 2862 - allowed, but not officially supported
        // {Examples of format 2, matching STD format}
        {"16:47:58.123", "16:47:58.123"},
        {"16:47:58", "16:47:58.000"},
        {"4:5:6.123", "04:05:06.123"},
        {"3:4:6", "03:04:06.000"},
        //Support added in 3.5.0 for the following AQQ formats
        // {Examples of format 2, matching STD format}
        {":23:36:47.123", "23:36:47.123"},
        {":23:36:47", "23:36:47.000"},
        {":4:5:6.123", "04:05:06.123"},
        {":2:3:4", "02:03:04.000"},
        // {Examples of format 3, matching STD format}
        {"2014.123", "2014:05:03::00:00:00.000"},
        {"14.123", "2014:05:03::00:00:00.000"},
        {"2014.123:", "2014:05:03::00:00:00.000"},
        {"14.123:","2014:05:03::00:00:00.000"},
        // {Examples of format 4, matching STD format}
        {"20140213:23:36:47.123", "2014:02:13::23:36:47.123"},
        {"20141203:23:36:47", "2014:12:03::23:36:47.000"},
        {"141213:23:36:47.123", "2014:12:13::23:36:47.123"},
        {"140203:23:36:47", "2014:02:03::23:36:47.000"},
        {"20141213:2:3:4.123", "2014:12:13::02:03:04.123"},
        {"20141213:2:3:4", "2014:12:13::02:03:04.000"},
        // {Examples of format 5, matching STD format}
        {"20141213", "2014:12:13::00:00:00.000"},
        {"141213", "2014:12:13::00:00:00.000"},
        {"20141213:", "2014:12:13::00:00:00.000"},
        {"141213:", "2014:12:13::00:00:00.000"}
    };
    for (String[] acqStrPair: supportedACQStrings){
      t.fromString(acqStrPair[0],Time.ACQ);
      assertEquals("Time.fromString(" + acqStrPair[0] +", ACQ)",acqStrPair[1],t.toString());
    }
  }

  private static void fromString_STD () {
    Time t = new Time();
    // TEST Supported STD Time String Formats
    // 1)[YY]YY:MM:DD::HH:MM:SS[.SSS]
    // 2)HH:MM:SS[.SSS]
    // 3)[YY]YY-MM-DD::HH:MM:SS[.SSS]
    // 4)T:YYYY:MM:DD::HH:MM:SS[.SSS]          Since 3.5.3 Bug 2876 2883
    // 5)T:YYYY-MM-DD:::MM:DD::HH:MM:SS[.SSS]  Since 3.5.3 Bug 2876 2883
    // Including single digit months, days, hours , minute, and seconds
    String[][] supportedSTDStrings = {
        // {Examples of format 1, matching standard STD format}
        {"2014:12:13::16:47:58.123", "2014:12:13::16:47:58.123"},
        {"1978:12:13::16:47:58",  "1978:12:13::16:47:58.000"},
        {"14:12:13::16:47:58.123" , "2014:12:13::16:47:58.123" },
        {"78:12:13::16:47:58","1978:12:13::16:47:58.000"},
        {"2014:1:2::3:4:5.123", "2014:01:02::03:04:05.123"},
        {"2014:1:2::3:4:5", "2014:01:02::03:04:05.000"},
        {"2014:12:13::16:47:58.1234567890123456789", "2014:12:13::16:47:58.123"},
        {"2014:12:13::16:47:58.", "2014:12:13::16:47:58.000"}, //Bug 2862 - allowed, but not officially supported
        // {Examples of format 2, matching STD format}
        {"16:47:58.123", "16:47:58.123"},
        {"16:47:58", "16:47:58.000"},
        {"4:5:6.123", "04:05:06.123"},
        {"3:4:6", "03:04:06.000"},
        // {Examples of format 3, matching STD format}
        {"2014-12-13::16:47:58.123", "2014:12:13::16:47:58.123"},
        {"1978-12-13::16:47:58", "1978:12:13::16:47:58.000"},
        {"14-12-24::16:47:58.456", "2014:12:24::16:47:58.456"},
        {"78-12-13::16:47:58", "1978:12:13::16:47:58.000"},
        {"2014-1-2::3:4:5.123", "2014:01:02::03:04:05.123"},
        {"14-1-2::3:4:5", "2014:01:02::03:04:05.000"},
        // {Example of format 4, matching STD format}
        {"T:2015:3:14::9:26:53.589", "2015:03:14::09:26:53.589"},
        // {Example of relative time up to year STD format}
        {"T:1950:05:19::9:15:0", "138::09:15:00.000"}
    };
    for (String[] stdStrPair: supportedSTDStrings){
      t.fromString(stdStrPair[0],Time.STD);
      assertEquals("Time.fromString(" + stdStrPair[0] +", STD)",stdStrPair[1],t.toString());
    }
  }

  private static void fromString_HMS () {
    Time t = new Time();
    // TEST Supported HMS Time String Formats
    // HH:MM:SS[.SSS]
    // Including single hours , minute, and seconds
    String[][] supportedHMSStrings = {
        // {Examples of HMS format, matching standard STD format}
        {"16:47:58.123", "16:47:58.123"},
        {"16:47:58", "16:47:58.000"},
        {"4:5:6.123", "04:05:06.123"},
        {"3:4:6", "03:04:06.000"},
        {"16:47:58.1234567890123456789", "16:47:58.123"},
        {"16:47:58.", "16:47:58.000"} //Bug 2862 - allowed, but not officially supported
    };
    for (String[] hmsStrPair: supportedHMSStrings){
      t.fromString(hmsStrPair[0],Time.HMS);
      assertEquals("Time.fromString(" + hmsStrPair[0] +", HMS)",hmsStrPair[1],t.toString());
    }

    // TEST Supported YMD Time String Formats
    // [YY]YY:MM:DD
    // Including double or single digit months and days
    String[][] supportedYMDStrings = {
        // {Examples of YMD format, matching standard STD format}
        {"2014:12:13",  "2014:12:13::00:00:00.000"},
        {"14:12:13", "2014:12:13::00:00:00.000"},
        {"78:12:27",  "1978:12:27::00:00:00.000"}
    };
    for (String[] ymdStrPair: supportedYMDStrings){
      t.fromString(ymdStrPair[0],Time.YMD);
      assertEquals("Time.fromString(" + ymdStrPair[0] +", YMD)",ymdStrPair[1],t.toString());
    }
  }

  private static void fromString_TCR () {
    Time t = new Time();
    // TEST Supported TCR Time String Formats
    // DDD:HH:MM:SS[.SSS]  <-- original format - no adjustments needed
    // Including double or single digit day, hour , minute, and seconds
    String[][] supportedTCRStrings = {
        // {Examples of TCR format, matching standard STD format} - fixed expected Jan 3, 2015
        // original format, expected non-leap year, expected leap year
        {"123:12:34:56.789" ,  ":05:03::12:34:56.789",":05:02::12:34:56.789" },
        {"012:12:34:56", ":01:12::12:34:56.000", ":01:12::12:34:56.000"},
        {"78:2:4:6",  ":03:19::02:04:06.000", ":03:18::02:04:06.000"},
        {"9:2:3:4.567", ":01:09::02:03:04.567", ":01:09::02:03:04.567"},
        {"30:02:33:04.1234567890123456789", ":01:30::02:33:04.123", ":01:30::02:33:04.123"},
        {"30:02:33:04.", ":01:30::02:33:04.000", ":01:30::02:33:04.000"} //Bug 2862 - allowed, but not officially supported
    };
    int year = Time.currentTime().getYear();
    // if this year is a leap year then compare supportedTCRString[0] to supportedTCRString[2]
    int expIndex = Time.isLeapYear(year)?2:1;
    for (String[] tcrStrPair: supportedTCRStrings){
      t.fromString(tcrStrPair[0],Time.TCR);
      assertEquals("Time.fromString(" + tcrStrPair[0] +", TCR)", year + tcrStrPair[expIndex], t.toString());
    }

    // TEST Supported VAX Time String Formats
    // DD-MMM-YYYY:HH:MM:SS[.SSS]  <-- original format - no adjustments needed
    // Including double or single digit day, hour , minute, and seconds
    // Note: 2 digit year is deprecated
    String[][] supportedVAXStrings = {
        // {Examples of VAX format, matching standard STD format}
        {"23-AUG-1989:12:34:56.789" ,  "1989:08:23::12:34:56.789"},
        {"4-DEC-2013:2:4:6.789", "2013:12:04::02:04:06.789"},
        {"4-DEC-1989:2:4:6", "1989:12:04::02:04:06.000"},
        {"4-DEC-1989:2:4:6.", "1989:12:04::02:04:06.000"} //Bug 2862 - allowed, but not officially supported
    };
    for (String[] vaxStrPair: supportedVAXStrings){
      t.fromString(vaxStrPair[0],Time.VAX);
      assertEquals("Time.fromString(" + vaxStrPair[0] +", VAX)",vaxStrPair[1],t.toString());
    }
  }

  private static void fromString_EPOCH () {
    Time t = new Time();
    // TEST Supported EPOCH Time String Formats
    // yyyy:sec_in_year
    // Note: 2 digit year is deprecated
    String[][] supportedEPOCHStrings = {
        // {Examples of EPOCH format, matching standard STD format}
        {"2013:12345678.987" , "2013:05:23::21:21:18.987"},
        {"1987:12345678", "1987:05:23::21:21:18.000"},
        {"1987:12345678.", "1987:05:23::21:21:18.000"} //Bug 2862 - allowed, but not officially supported
    };
    for (String[] epochStrPair: supportedEPOCHStrings){
      t.fromString(epochStrPair[0],Time.EPOCH);
      assertEquals("Time.fromString(" + epochStrPair[0] +", EPOCH)",epochStrPair[1],t.toString());
    }

    // HIGH PRECISION TESTS (Since 2.9.3)
    String[] timeStrs = { "2011:06:29::12:49:01.123",             // up to milliseconds precision
                          "2011:06:29::12:49:01.654321",          // up to microseconds precision
                          "2011:06:29::12:49:01.123456789",       // up to nanoseconds precision
                          "2011:06:29::12:49:01.123456789012",    // up to picoseconds precision
                          "2011:06:29::12:49:01.123456789012345", // up to femtoseconds precision
                        };
    double[] expVals  = { // wholeSecs,   fracSecs,            fracTolerance (precision)
                          1.940503741E9,  0.123,               TOL_MS,
                          1.940503741E9,  0.654321,            TOL_MuS,
                          1.940503741E9,  0.123456789,         TOL_NS,
                          1.940503741E9,  0.123456789012,      TOL_PS,
                          1.940503741E9,  0.123456789012345,   TOL_FS,
                        };
    int ii = 0;
    assert(timeStrs.length == (expVals.length / 3));
    assertEquals("Invalid test data: expVals.size ("+expVals.length+") MUST BE (3 * timeStrs.size)",
        timeStrs.length*3, expVals.length);
    for (String str : timeStrs) {
      final double expWSec = expVals[ii];
      final double expFSec = expVals[ii+1];
      final double fsecTol = expVals[ii+2];
      t.fromString(str, Time.STD); // parse from string into current time object
      assertEquals("Time.fromString('"+str+"') wsec", expWSec,   t.getWSec());
      assertEquals("Time.fromString('"+str+"') fsec", expFSec,   t.getFSec(), fsecTol);
      assertEquals("Time.fromString('"+str+"') sec ", expWSec + expFSec, t.getSec(), fsecTol);
      ii += 3; // expVals[] are sets of 3 doubles
    }

  }

  private static void fromString_ISO (String[] altFormat) {
    Time t = new Time();
    // ISO 8601 SPECIAL FORMATS (SINCE NEXTMIDAS 3.3.0)
    String[] weekNumbers = {
      // Columns A&B taken from http://en.wikipedia.org/wiki/ISO_week_date
      //    A      =      B      =      C
      "2005-01-01", "2004-W53-6", "2005-001",
      "2005-01-02", "2004-W53-7", "2005-002",
      "2005-12-31", "2005-W52-6", "2005-365",
      "2007-01-01", "2007-W01-1", "2007-001",
      "2007-12-30", "2007-W52-7", "2007-364",
      "2007-12-31", "2008-W01-1", "2007-365",
      "2008-01-01", "2008-W01-2", "2008-001",
      "2008-12-29", "2009-W01-1", "2008-364",
      "2008-12-31", "2009-W01-3", "2008-366",
      "2009-01-01", "2009-W01-4", "2009-001",
      "2009-12-31", "2009-W53-4", "2009-365",
      "2010-01-03", "2009-W53-7", "2010-003",
      "2009-12-31", "2009-W53-4", "2009-365",
      "2010-01-01", "2009-W53-5", "2010-001",
      "2010-01-02", "2009-W53-6", "2010-002",
      "2010-01-03", "2009-W53-7", "2010-003",
      "2008-12-28", "2008-W52-7", "2008-363",
      "2008-12-29", "2009-W01-1", "2008-364",
      "2008-12-30", "2009-W01-2", "2008-365",
      "2008-12-31", "2009-W01-3", "2008-366",
      "2009-01-01", "2009-W01-4", "2009-001",
      "2016-01-07", "2016-W01-4", "2016-007", // <-- Not on Wikipedia, 1 Jan on Friday
    };
    Time t0 = new Time();
    Time t1 = new Time();
    Time t2 = new Time();
    for (int i = 0; i < weekNumbers.length; i+=3) {
      t0.fromString(weekNumbers[i+0], Time.ISO8601);
      t1.fromString(weekNumbers[i+1], Time.ISO8601);
      t2.fromString(weekNumbers[i+2], Time.ISO8601);
      assertEquals("Time.fromString('"+weekNumbers[i+1]+"', ISO8601)", t0, t1);
      assertEquals("Time.fromString('"+weekNumbers[i+2]+"', ISO8601)", t0, t2);

      t0.fromString(weekNumbers[i+0]+"T12:34:56.789", Time.ISO8601);
      t1.fromString(weekNumbers[i+1]+"T12:34:56.789", Time.ISO8601);
      t2.fromString(weekNumbers[i+2]+"T12:34:56.789", Time.ISO8601);
      assertEquals("Time.fromString('"+weekNumbers[i+1]+"T12:34:56.789', ISO8601)", t0, t1);
      assertEquals("Time.fromString('"+weekNumbers[i+2]+"T12:34:56.789', ISO8601)", t0, t2);

      t0.fromString(weekNumbers[i+0]+"T123456.789", Time.ISO8601);
      t1.fromString(weekNumbers[i+1]+"T123456.789", Time.ISO8601);
      t2.fromString(weekNumbers[i+2]+"T123456.789", Time.ISO8601);
      assertEquals("Time.fromString('"+weekNumbers[i+1]+"T123456.789', ISO8601)", t0, t1);
      assertEquals("Time.fromString('"+weekNumbers[i+2]+"T123456.789', ISO8601)", t0, t2);

      // ISO 8601 permits ',' in place of '.'
      t0.fromString(weekNumbers[i+0]+"T12:34:56,789", Time.ISO8601);
      t1.fromString(weekNumbers[i+1]+"T12:34:56,789", Time.ISO8601);
      t2.fromString(weekNumbers[i+2]+"T12:34:56,789", Time.ISO8601);
      assertEquals("Time.fromString('"+weekNumbers[i+1]+"T12:34:56,789', ISO8601)", t0, t1);
      assertEquals("Time.fromString('"+weekNumbers[i+2]+"T12:34:56,789', ISO8601)", t0, t2);

      t0.fromString(weekNumbers[i+0]+"T123456,789", Time.ISO8601);
      t1.fromString(weekNumbers[i+1]+"T123456,789", Time.ISO8601);
      t2.fromString(weekNumbers[i+2]+"T123456,789", Time.ISO8601);
      assertEquals("Time.fromString('"+weekNumbers[i+1]+"T123456,789', ISO8601)", t0, t1);
      assertEquals("Time.fromString('"+weekNumbers[i+2]+"T123456,789', ISO8601)", t0, t2);
    }

    for (int i = 0; i < weekNumbers.length; i+=1) {
      for (int j = 0; j < ISO8601_TZ.length; j++) {
        // MIDNIGHT (SAME DAY) -------------------------------------------------
        t.fromString(weekNumbers[i], Time.ISO8601);
        t.addSec(ISO8601_DT[j]*3600);

        checkTimeFromString(t, weekNumbers[i]+"T00:00:00"    +ISO8601_TZ[j], Time.ISO8601);
        checkTimeFromString(t, weekNumbers[i]+"T00:00:00.000"+ISO8601_TZ[j], Time.ISO8601);
        checkTimeFromString(t, weekNumbers[i]+"T000000"      +ISO8601_TZ[j], Time.ISO8601);
        checkTimeFromString(t, weekNumbers[i]+"T000000.000"  +ISO8601_TZ[j], Time.ISO8601);

        // MIDNIGHT (NEXT DAY) -------------------------------------------------
        t.fromString(weekNumbers[i], Time.ISO8601);
        t.addSec(86400);
        t.addSec(ISO8601_DT[j]*3600);

        checkTimeFromString(t, weekNumbers[i]+"T24:00:00"    +ISO8601_TZ[j], Time.ISO8601);
        checkTimeFromString(t, weekNumbers[i]+"T24:00:00.000"+ISO8601_TZ[j], Time.ISO8601);
        checkTimeFromString(t, weekNumbers[i]+"T240000"      +ISO8601_TZ[j], Time.ISO8601);
        checkTimeFromString(t, weekNumbers[i]+"T240000.000"  +ISO8601_TZ[j], Time.ISO8601);

        // AFTERNOON -----------------------------------------------------------
        t.fromString(weekNumbers[i], Time.ISO8601);
        t.addSec(Time.parseTime("12:34:56"));
        t.addSec(ISO8601_DT[j]*3600);

        checkTimeFromString(t, weekNumbers[i]+"T12:34:56"    +ISO8601_TZ[j], Time.ISO8601, TOL_MS);
        checkTimeFromString(t, weekNumbers[i]+"T12:34:56.000"+ISO8601_TZ[j], Time.ISO8601, TOL_MS);
        checkTimeFromString(t, weekNumbers[i]+"T123456"      +ISO8601_TZ[j], Time.ISO8601, TOL_MS);
        checkTimeFromString(t, weekNumbers[i]+"T123456.000"  +ISO8601_TZ[j], Time.ISO8601, TOL_MS);

        t.fromString(weekNumbers[i], Time.ISO8601);
        t.addSec(Time.parseTime("12:34:56.789"));
        t.addSec(ISO8601_DT[j]*3600);

        checkTimeFromString(t, weekNumbers[i]+"T12:34:56.789"+ISO8601_TZ[j], Time.ISO8601, TOL_MS);
        checkTimeFromString(t, weekNumbers[i]+"T123456.789"  +ISO8601_TZ[j], Time.ISO8601, TOL_MS);
      }
    }
    // TEST THAT ALTERNATE ISO 8601 FORMAT "YYYY-MM-DD HH:MM:SS" IS ACCEPTED
    // (also check that time Strings are trimmed - space as beginning & end ignored)
    String[] altISOFormat = {"2014-05-04 11:12:13",
                             "2012-03-30 12:20:30.333",
                             " 2012-03-30 12:20:30.123 "};
    String[] isoFormat =    {"2014-05-04T11:12:13.000Z",
                             "2012-03-30T12:20:30.333Z",
                             "2012-03-30T12:20:30.123Z"};
    for (int i = 0; i < altFormat.length; i++){
      t.fromString(altISOFormat[i]);
      assertEquals(altFormat[i] + " treated as alternate ISO format", isoFormat[i], t.toString(Time.ISO8601, 3));
    }

    // Test that filename format without fractional seconds is handled properly - Bug 2862
    t.fromString("19930225_144257",Time.FILENAME);
    assertEquals("Time.fromString FMT=FILENAME & no fsec", "1993:02:25::14:42:57.000", t.toString());
    // Test that ISO 8601 format ending in . without fractional seconds is handled properly - Bug 2862
    // (this format is allowed, but not officially supported)
    t.fromString("1993-02-25T14:42:57.",Time.ISO8601);
    assertEquals("Time.fromString FMT=ISO8601 ending in . & no fsec", "1993:02:25::14:42:57.000", t.toString());

    // since 3.5.4: check that fromString (parser) for ISO 8601 supports "24:00:00.000Z" since BUG 2912 change it to always output FULL date & time
    t.fromJ1950(86400.0);
    checkTimeFromString(t, "24:00:00Z",     Time.ISO8601);
    checkTimeFromString(t, "24:00:00.000Z", Time.ISO8601);
    checkTimeFromString(t, "1950-01-01T24:00:00.000Z", Time.ISO8601);
    // test invalid time string starting with 'T' character
    final String invalidTimeStr = "T12:34:56.000Z";
    assertException("Time.fromString('"+invalidTimeStr+"', ISO8601) invalid time string",
        MidasException.class, t, "fromString", new Object[] { invalidTimeStr, Time.ISO8601 } );
    // end since 3.5.4 test cases (BUG 2912)
  }

  private static void fromString_SimpleDateFormat () {
    Time t = new Time();
    // since 3.5.4: This test the new Time.fromString(String str, String simpleDateFmt), where
    // str is a time string and simpleDateFormat gives the format of the time string that is to
    // be converted into a NeXtMidas Time. Note: This functionality was previously only in Timex,
    // but it has moved into Time for greater availability (i.e. Timex now uses this Time method).
    String[][] timeString__expectedSTD_SimpleDateFormat_Array = {
        // {input, parsed output, simple date format, dp (decimal precision)}
        {"2007-08-06 10:30:14.745456789012345", "2007:08:06::10:30:14.745456789012345", "yyyy-MM-dd hh:mm:ss.SSSSSSSSSSSSSSS", "15"},
        {"WED JAN 01 2014 01:02:03.99999", "2014:01:01::01:02:03.99999", "EEE MMM dd yyyy hh:mm:ss.SSSSS", "5"},
        {"29 Dec 2012 23:59:59.1234, SAT", "2012:12:29::23:59:59.1234", "dd MMM yyyy HH:mm:ss.SSSS, EEE", "4"},
        {"T:2015-3-14::9:26:53.589793238462", "2015:03:14::09:26:53.589793238462", "STD", "12"}
    };

    int dp = 0;
    String timeString = null;
    String expandedSimpleDateFormat = null;
    for (String[] timeString_SimpleDateFormatGroup: timeString__expectedSTD_SimpleDateFormat_Array) {
      dp = Integer.parseInt(timeString_SimpleDateFormatGroup[3]);
      timeString = timeString_SimpleDateFormatGroup[0];
      expandedSimpleDateFormat = timeString_SimpleDateFormatGroup[2];
      t.fromString(timeString, expandedSimpleDateFormat);
      assertEquals("Time.fromString("+timeString+", "+expandedSimpleDateFormat+")", timeString_SimpleDateFormatGroup[1], t.toString(dp));
    }
  }

  private static void fromStringACQDATE () {  // added in 3.9.3 for NXM-3139
    Time t = new Time();

    // TESTS to ensure ACQ format strings with only one "dot" are still properly read.
    t.fromString("1999.333", Time.ACQDATE);
    assertEquals("Time.fromString(\"1999.333\", DATE_ACQ)","1999:11:29::00:00:00.000", t.toString());
    t.fromString("99.333", Time.ACQDATE);
    assertEquals("Time.fromString(\"99.333\", DATE_ACQ)","1999:11:29::00:00:00.000", t.toString());

    // TEST Supported DATE_ACQ Time String Formats
    // [YY]YY.DDD              <-- adjust to start with day "field" (4)
    String[][] supportedACQStrings = {
        // {Examples of DATE_ACQ format, matching STD format}
        {"2014.123", "2014:05:03::00:00:00.000"},
        {"14.123", "2014:05:03::00:00:00.000"},
        {"2014.123:", "2014:05:03::00:00:00.000"},
        {"14.123:","2014:05:03::00:00:00.000"},
    };
    for (String[] acqStrPair: supportedACQStrings){
      t.fromString(acqStrPair[0],Time.ACQDATE);
      assertEquals("Time.fromString(" + acqStrPair[0] +", DATE_ACQ)",acqStrPair[1],t.toString());
    }
  }

  private static void fromStringACQTIME () {  // added in 3.9.3 for NXM-3139
    Time t = new Time();
    t.fromString("16:47:58",Time.ACQTIME);
    assertEquals("Time.fromString(16:47:58, TIME_ACQ)","16:47:58.000",t.toString());
  }

  private static void  fromStringBadTimeString() {
    Time t = new Time();
    // TEST ERRONEOUS TIME STRINGS TO MAKE SURE THE ERRORS ARE CAUGHT
    Object[] errTimeStrs =
        { "STD",      new String[] { "20g4:14:11::12:13:14.99",
                                     "2014:g4:11::12:11:14.99",
                                     "2014:14:g1::12:11:14.99",
                                     "2014:14:11::1g:11:14.99",
                                     "2014:14:g1::12:g1:14.99",
                                     "2014:14:11::12:11:g4.99",
                                     "2014:14:g1::12:11:14.9g",
                                     "2014:14:g1::12:11:14.9.9"},
          "ISO8601",  new String[] { "2g14-14-11T12:11:14.99",
                                     "2014-1g-11T12:11:14.99",
                                     "2014-14-g1T12:11:14.99",
                                     "2014-14-11T1g:11:14.99",
                                     "2014-14-11T12:g1:14.99",
                                     "2014-14-11T12:11:1g.99",
                                     "2014-14-11T12:11:14.g9",
                                       "2014-14-11T12:11:14.9.9"},
          "ACQ",      new String[] { "1g.91:12:11:14.99",
                                     "14.g1:12:11:14.99",
                                     "14.91:1g:11:14.99",
                                     "14.91:12:g1:14.99",
                                     "14.91:12:11:1g.99",
                                     "14.91:12:11:14.g9",
                                     "14.91:12:11:14.9.9"},
          "EPOCH",    new String[] { "g014:7819870.99",
                                     "2014:781987g.99",
                                     "2014:7819870.9g",
                                     "2014:7819870.9.9"},
          "FILENAME", new String[] { "20140g01_12111099",
                                     "20140401_g2111099",
                                     "20140401_121g1099",
                                     "20140401_12111g99",
                                     "20140401_1211109g"},
          "NORAD",    new String[] { "14g91.50776620370372633",
                                     "14091.507766203g0372633",
                                     "14091.50776620.370372633"},
          "TCR",      new String[] { "g1:12:11:10.99",
                                     "91:1g:11:10.99",
                                     "91:12:g1:10.99",
                                     "91:12:11:1g.99",
                                     "91:12:11:10.g9",
                                     "91:12:11:10.9.9"},
          "VAX",      new String[] { "1g-APR-2014:12:11:10.99",
                                     "11-APR-2g14:12:11:10.99",
                                     "11-APR-2014:1g:11:10.99",
                                     "11-APR-2014:12:g1:10.99",
                                     "11-APR-2014:12:11:1g.99",
                                     "11-APR-2014:12:11:10.g9",
                                     "11-APR-2014:12:11:10.9.9"},
          "HMS",      new String[] { "g2:11:10.99",
                                     "12:1g:10.99",
                                     "12:11:1g.99",
                                     "12:11:10.g9",
                                     "12:11:10.9.9"},
          "YMD",      new String[] { "201g:04:01",
                                     "2014:g4:01",
                                     "2014:04:1g"}
      };
      boolean allErrorsCaught = true;
      for (int i = 0; i < errTimeStrs.length; i+=2) {
        String   format  = errTimeStrs[i+0].toString();
        String[] strings = (String[])errTimeStrs[i+1];
        int      fmt     = Parser.find(Time.formatList, format, -1);

        for (int j = 0; j < strings.length; j++) {
          try {
            t.fromString(strings[j],fmt);
            allErrorsCaught = false;
            Shell.warning("The error in time string " + strings[j] + " format: " + format + " was not caught");
          }
          catch (MidasException me){
            // good - error was caught
          }
        }
      } // end for each type format
      assertTrue("All Time Strings With Errors Were Caught: ", allErrorsCaught);
    }

  private static void checkTimeFromString (Time exp, String timestr, int fmt) {
    Time act = new Time();
    act.fromString(timestr, fmt);
    assertEquals("Time.fromString('"+timestr+"', "+Time.getFormat(fmt)+")", exp, act);
  }

  private static void checkTimeFromString (Time exp, String timestr, int fmt, double tol) {
    Time act = new Time();
    act.fromString(timestr, fmt);
    assertEquals("Time.fromString('"+timestr+"', "+Time.getFormat(fmt)+")", exp.getSec(), act.getSec(), tol);
  }

  public void testParseTime() {
    String[] strings = (String[])TIME_TESTS[0+3];
    double[] times   = (double[])TIME_TESTS[0+4];

    for (int i = 0; i < strings.length; i++) {
      double sec = times[i*2+0] + times[i*2+1];
      assertEquals("Time.parseTime("+strings[i]+")", sec, Time.parseTime(strings[i]));
    }
  }

  public void testTod2sec() {
    String[] strings = (String[])TIME_TESTS[0+3];
    double[] times   = (double[])TIME_TESTS[0+4];

    for (int i = 0; i < strings.length; i++) {
      double sec = times[i*2+0] + times[i*2+1];
      assertEquals("Time.tod2sec("+strings[i]+")", sec, Time.tod2sec(strings[i]));
    }
  }

  public void testFromJ1950 () {
    // Tests for an even number of days are covered in testManyThings().
    Time t = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      double sec = TIME_VALUES[i+0] + TIME_VALUES[i+1];

      t.fromJ1950(TIME_VALUES[i+0], TIME_VALUES[i+1]);
      assertEquals("Time.fromJ1950(wsec,fsec)  t="+t+" (wsec)", TIME_VALUES[i+0], t.getWSec(), 0.0);
      assertEquals("Time.fromJ1950(wsec,fsec)  t="+t+" (fsec)", TIME_VALUES[i+1], t.getFSec(), TOLERANCE);

      t.fromJ1950(sec);
      assertEquals("Time.fromJ1950(sec)" +" t="+t, sec, t.getSec(), TOLERANCE);
    }

    // test case for Bug 1635 - integer part of fsec should be moved to wsec
    // and fractional part of wsec should be moved to fsec
    for (int i = 0; i < MORE_TIME_VALUES.length; i+=5) {
      t.fromJ1950(MORE_TIME_VALUES[i+0], MORE_TIME_VALUES[i+1]);
      assertEquals("Time.fromJ1950(wsec,fsec)  t="+t+" (sec)",  MORE_TIME_VALUES[i+2], t.getSec(), TOLERANCE);
      assertEquals("Time.fromJ1950(wsec,fsec)  t="+t+" (wsec)", MORE_TIME_VALUES[i+3], t.getWSec(), 0.0);
      assertEquals("Time.fromJ1950(wsec,fsec)  t="+t+" (fsec)", MORE_TIME_VALUES[i+4], t.getFSec(), TOLERANCE);
    }

    _testManyThings();
  }

  public void testToJ1950 () {
    Time t = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      double sec = TIME_VALUES[i+0] + TIME_VALUES[i+1];
      t.fromJ1950(sec);
      assertEquals("Time.toJ1950()"  +" t="+t, sec, t.toJ1950(),  0.0000001);
      assertEquals("Time.getSec()"   +" t="+t, sec, t.getSec(),   0.0000001);
      assertEquals("Time.getMJS()"   +" t="+t, sec, t.getMJS(),   0.0000001);
    }
  }

  public void testFromCurrent() {
    // JC4: There isn't a good test for this... just make sure that it is close to Time.current().
    Time t = new Time();
    t.fromCurrent();
    assertEquals("Time.fromCurrent()", Time.current(), t.getSec(), 0.1);
  }

  public void testCurrentTime() {
    // JMM: There isn't a good test for this... just make sure that it is close to Time.current().
    Time t = Time.currentTime();
    assertEquals("Time.currentTime()", Time.current(), t.getSec(), 0.1);
  }

  // NTN: prepended "_" (underscore) to hide normal JUnit from running this internal test directly
  private void _testManyThings () {
    int    mjd  = 0; // Days
    double wsec = 0; // Seconds (0 = 1950:01:01:00:00:00)
    double jd   = 0; // set below
    Time   t    = new Time();

    for (; mjd < 25000; mjd++) { // mjd=25000 is in the year 2018
      wsec = mjd * 86400.0;
      jd   = mjd + Time.MJDTOJD;
      t.fromJ1950(wsec, 0.0);
      assertEquals("getMJD  mjd = "+mjd+" t = "+t,  mjd, t.getMJD());
      assertEquals("getJD   mjd = "+mjd+" t = "+t,   jd, t.getJD(),   0.0000000);
      assertEquals("getWSec mjd = "+mjd+" t = "+t, wsec, t.getWSec(), 0.0000000);
      assertEquals("getFSec mjd = "+mjd+" t = "+t,  0.0, t.getFSec(), 0.0000000);
      assertEquals("getSec  mjd = "+mjd+" t = "+t, wsec, t.getSec(),  0.0000001);
    }
  }

  public void testYmd2mjd() {
    Time t = new Time();
    for (int i=0, n=0; i < TIME_VALUES.length; i+=2,n+=8) {
      int mjd = Time.ymd2mjd(TIME_DATE[n+0], TIME_DATE[n+1], TIME_DATE[n+2]);

      t.fromJ1950(TIME_VALUES[i+0], TIME_VALUES[i+1]);
      assertEquals("ymd2mjd(..) t = "+t, t.getMJD(), mjd);
    }
  }

  public void testGetMJD() {
    // Most tests for getMJD() are in testManyThings() that is called by getFromJ1950().
  }

  public void testGetJD() {
    // Most tests for getMJD() are in testManyThings() that is called by getFromJ1950().

    // Check the conversion on a known reference point
    // (source http://en.wikipedia.org/wiki/Julian_day)
    Time t = new Time();
    t.fromJ1970(0.0);
    assertEquals("getJD jd=2440587.5 t=1970:01:01::00:00:00"+t, 2440587.5, t.getJD());
  }

  public void testFromJ1970() {
    // Leap Years: 1952, 1956, 1960, 1964, 1968
    int days = 365 * 15 + 366 * 5;
    assertEquals("Time.J1970TOJ1950 = ", days * 86400, Time.J1970TOJ1950);

    Time t = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      t.fromJ1970(TIME_VALUES[i+0] - Time.J1970TOJ1950 + TIME_VALUES[i+1]);
      assertEquals("Time.fromJ1970(sec)  t="+t+" (wsec)", TIME_VALUES[i+0], t.getWSec(), 0.0000000);
      assertEquals("Time.fromJ1970(sec)  t="+t+" (fsec)", TIME_VALUES[i+1], t.getFSec(), 0.0000001);
    }
  }

  public void testFromJ2000() {
    // Leap Years: 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996
    int days = 365 * 38 + 366 * 12;
    assertEquals("Time.J2000TOJ1950 = ", days * 86400, Time.J2000TOJ1950);

    Time t = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      t.fromJ2000(TIME_VALUES[i+0] - Time.J2000TOJ1950 + TIME_VALUES[i+1]);
      assertEquals("Time.fromJ2000(sec)  t="+t+" (wsec)", TIME_VALUES[i+0], t.getWSec(), 0.0000000);
      assertEquals("Time.fromJ2000(sec)  t="+t+" (fsec)", TIME_VALUES[i+1], t.getFSec(), 0.0000001);
    }
  }

  public void testIsLeapYear() {
    for (int i = 1950; i < 2050; i+=4) {
      assertEquals("Time.isLeapYear("+(i+0)+")", false, Time.isLeapYear(i+0));
      assertEquals("Time.isLeapYear("+(i+1)+")", false, Time.isLeapYear(i+1));
      assertEquals("Time.isLeapYear("+(i+2)+")", true,  Time.isLeapYear(i+2));
      assertEquals("Time.isLeapYear("+(i+3)+")", false, Time.isLeapYear(i+3));
    }

    Time t = new Time();
    double sec = 0.0;
    for (int i = 1950; i < 2050; i++) {
      boolean ly = Time.isLeapYear(i);
      t.fromJ1950(sec);
      assertEquals("Time.isLeapYear() t="+t+" year="+i, ly, t.isLeapYear());
      sec += 86400 * 100;
      t.fromJ1950(sec);
      assertEquals("Time.isLeapYear() t="+t+" year="+i, ly, t.isLeapYear());
      sec += 86400 * 100;
      t.fromJ1950(sec);
      assertEquals("Time.isLeapYear() t="+t+" year="+i, ly, t.isLeapYear());
      sec += 86400 * 100;
      t.fromJ1950(sec);
      assertEquals("Time.isLeapYear() t="+t+" year="+i, ly, t.isLeapYear());
      if (ly) {
        sec += 86400 * 66;
      } else {
        sec += 86400 * 65;
      }
    }
  }

  public void testToDateObject() {
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      double sec = TIME_VALUES[i+0] + TIME_VALUES[i+1];          // J1950 (sec)
      long   ms  = (long)((sec - Time.J1970TOJ1950) * 1000);     // J1970 (ms)
      Date   d   = Time.toDateObject(sec);
      assertEquals("Time.toDateObject(sec)  sec="+sec, ms, d.getTime());
    }
  }

  public void testFromDateObject() {
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      double sec = TIME_VALUES[i+0] + TIME_VALUES[i+1];          // J1950 (sec)
      long   ms  = (long)((sec - Time.J1970TOJ1950) * 1000);     // J1970 (ms)
      Date   d   = new Date(ms);
      assertEquals("Time.fromDateObject(Date)  sec="+sec, sec, Time.fromDateObject(d), 0.0001);
    }
  }

  public void testFromTime() {
    Time t1 = new Time();
    Time t2 = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      t1.fromJ1950(TIME_VALUES[i+0], TIME_VALUES[i+1]);
      t2.fromTime(t1);
      assertEquals("Time.fromTime(..)  t1="+t1+" (wsec)", t1.getWSec(), t2.getWSec());
      assertEquals("Time.fromTime(..)  t1="+t1+" (fsec)", t1.getFSec(), t2.getFSec());
    }
  }

  public void testAddSec() {
    final double[] DELTAS = { 0.0,  100.5, 42.0, -42.0, -19.6 };

    Time t = new Time();
    for (int ii = 0; ii < TIME_VALUES.length; ii+=2) {
      for (int jj = 0; jj < DELTAS.length; jj++) {
        double expected = TIME_VALUES[ii+0] + (TIME_VALUES[ii+1] + DELTAS[jj]);

        t.fromJ1950(TIME_VALUES[ii+0], TIME_VALUES[ii+1]);
        t.addSec(DELTAS[jj]);
        assertEquals("Time.addSec("+DELTAS[jj]+")  -> "+t, expected, t.getSec(), 0.0000000);
      }
    }

    // These tests check for loss of precision (see Bug 2674)
    final double[] tests = {
      // WSEC      FSEC  + SEC    -> WSEC         FSEC           TOL
      1.3886208E9, 0.0,    1e-10,    1388620800,  1e-10,         0.0,
      1.3886208E9, 0.0,   -1e-10,    1388620799,  0.9999999999,  1e-10,
      1.3886208E9, 0.0,     3.14,    1388620803,  0.14,          1e-3,
    };

    for (int i = 0; i < tests.length; i+=6) {
      String msg = "new Time("+tests[i+0]+", "+tests[i+1]+") .addSec("+tests[i+2]+") ";

      Time time = new Time(tests[i+0], tests[i+1]);
      time.addSec(tests[i+2]);

      assertEquals(msg+"wsec", tests[i+3], time.getWSec(), 0.0);
      assertEquals(msg+"wsec", tests[i+4], time.getFSec(), tests[i+5]);
    }
  }

  public void testDiff() {
    final double[] DELTAS = { 0.0,  100.5, 42.0, -42.0, -19.6 };

    Time t1 = new Time();
    Time t2 = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      for (int j = 0; j < DELTAS.length; j++) {
        double sec = TIME_VALUES[i+0] + TIME_VALUES[i+1];

        t1.fromJ1950(sec);
        t2.fromJ1950(sec + DELTAS[j]);
        assertEquals("Time.diff(..)  t1="+t1+" t2="+t2, DELTAS[j], t2.diff(t1), 0.0000001);
      }
    }
  }

  public void testFloor() {
    Time t = new Time();
    for (int ii=0; ii < TIME_VALUES.length; ii+=2) {
      double sec = TIME_VALUES[ii+0] + TIME_VALUES[ii+1];

      for (int resolution=1; resolution < 1000; resolution*=10) {
        t.fromJ1950(sec);
        t.floor(resolution);
        assertEquals("Time.floor("+resolution+")  t="+t+" (wsec/r)", Math.floor(sec/resolution), Math.floor(t.getSec()/resolution));
        assertEquals("Time.floor("+resolution+")  t="+t+" (wsec%r)", 0.0, t.getWSec()%resolution);
        assertEquals("Time.floor("+resolution+")  t="+t+" (fsec)",   0.0, t.getFSec());
      }
    }
  }

  public void testModulo() {
    Time t = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      double sec = TIME_VALUES[i+0] + TIME_VALUES[i+1];

      for (int r = 1; r < 1000; r*=10) {
        t.fromJ1950(TIME_VALUES[i+0], TIME_VALUES[i+1]);
        t.modulo(r);
        assertEquals("Time.modulo("+r+")  t="+t+" sec="+sec+" r="+r, sec%r, t.getSec(), 0.0001);
      }
    }
  }

  public void testRound() {
    Time t = new Time();
    for (int i = 0; i < TIME_VALUES.length; i+=2) {
      double sec = TIME_VALUES[i+0] + TIME_VALUES[i+1];

      for (int r = 1; r < 1000; r*=10) {
        t.fromJ1950(sec);
        t.round(r);
        assertEquals("Time.round("+r+")  t="+t+" (wsec)", Math.round(sec/r), Math.round(t.getWSec()/r));
        assertEquals("Time.round("+r+")  t="+t+" (wsec)", 0.0, t.getWSec()%r);
        assertEquals("Time.round("+r+")  t="+t+" (fsec)", 0.0, t.getFSec());
      }
    }
  }

  public void testGetFormat() {
    Parser p = new Parser(Time.formatList);

    for (int i = 1; i <= p.elements(); i++) {
      String exp = p.get(i);
      String fmt = Time.getFormat(i);
      assertEquals("Time.getFormat("+i+") -> "+fmt, exp, fmt);
    }
  }

  // NTN: prepended "_" (underscore) to hide normal JUnit from running this internal test directly
  private void _testLotsOfStuff () {
    Time t = new Time();
    for (int n=0, i=0; n < TIME_DATE.length; n+=8,i++) {
      t.fromJ1950(TIME_VALUES[i*2+0], TIME_VALUES[i*2+1]);

      double sec = TIME_VALUES[i*2+0] + TIME_VALUES[i*2+1];
      double sod = TIME_SOY[i] - (TIME_DATE[n+6] * 86400);
      double fod = sod / 86400;
      double yis = sec - sod - (TIME_DATE[n+6] * 86400);

      assertEquals("getYear()"      +"t="+t, TIME_DATE[n+0], t.getYear());
      assertEquals("getMonth()"     +"t="+t, TIME_DATE[n+1], t.getMonth());
      assertEquals("getDay()"       +"t="+t, TIME_DATE[n+2], t.getDay());
      assertEquals("getHour()"      +"t="+t, TIME_DATE[n+3], t.getHour());
      assertEquals("getMinute()"    +"t="+t, TIME_DATE[n+4], t.getMinute());
      assertEquals("getSecond()"    +"t="+t, TIME_DATE[n+5], t.getSecond());
      assertEquals("getDoY()"       +"t="+t, TIME_DATE[n+6], t.getDoY());
      assertEquals("getDayOfWeek()" +"t="+t, TIME_DATE[n+7], t.getDayOfWeek());

      assertEquals("getSoY()"       +"t="+t, TIME_SOY[i],    t.getSoY(), 0.0001);
      assertEquals("getSoD()"       +"t="+t, sod,            t.getSoD(), 0.0001);
      assertEquals("getFoD()"       +"t="+t, fod,            t.getFoD(), 0.0001);
      assertEquals("getYis()"       +"t="+t, yis,            t.getYiS(), 0.0001);
    }

    //**********************************************************************
    // test high precision & rounding cases
    //**********************************************************************

    // Whole Second, Fractional Second Pairs used to generate Time value
    double[][] wsecFsecPairs = {
      {2.019698745E9, 0.6789012345},// "2014:01:01::03:25:45.679012345"
      {2.051222399E9, 0.999999999999999}, // "2014:12:31::23:59:5.999999999999999"
      {2.051222399E9, 0.999999999},
      {2.051222399E9, 0.999999},
    };
    // Expected values for public Time get methods that indirectly use either
    // getSoY or getSoD (getFoD, getHour, getMinute, GetSecond, getDoY, getDay)
    // plus a few others
    int[] expectedYear = {2014, 2014, 2014, 2014};
    int[] expectedMonth = {1, 12, 12, 12};
    int[] expectedDay = {1, 31, 31, 31};
    int[] expectedHour = {3, 23, 23, 23};
    int[] expectedMin = {25, 59, 59, 59};
    int[] expectedDoY = {0, 364, 364, 364};
    double[] expectedFoD = {0.142889802097620, 0.999999999999999,
        0.999999999999988, 0.999999999988};
    double[] expectedSoD = {12345.6789012345, 86399.9999999999,
        86399.999999999, 86399.999999};
    double[] expectedSoY = {12345.67890123,  3.153599999999999E7,
        3.153599999999999E7, 3.1535999999999E7};

    // Values for checking toYYMMDDHHMMSS calculations
    // uses ymdhms[YMDHMS_SOYW], ymdhms[YMDHMS_SOYF], ymdhms[YMDHMS_YEAR],
    // ymdhms[YMDHMS_DOY], ymdhms[YMDHMS_HOUR], ymdhms[YMDHMS_MIN],
    // ymdhms[YMDHMS_YEAR], ymdhms[YMDHMS_MON], ymdhms[YMDHMS_DAY],
    // ymdhms[YMDHMS_SECW], ymdhms[YMDHMS_SECF], and ymdhms[YMDHMS_FOD]
    // (Note the other ymdhms fields are private and not used by any Time method,
    // but the unused field YMDHMS_SEC is really YMDHMS_SECW and YMDHMS_SOD is
    // assigned sod which is used in calculating YMDHMS_FOD, so only YMDHMS_SOY
    // is truly unused and untested)
    String[] expectedEPOCH_15dp = {"2014:12345.678901234500000",
        "2014:31535999.999999999999999", "2014:31535999.999999999000000",
        "2014:31535999.999999000000000"};
    String[] expectedEPOCH_9dp = {"2014:12345.678901235",
        "2015:0.000000000", "2014:31535999.999999999",
        "2014:31535999.999999000"};
    String[] expectedEPOCH_6dp = {"2014:12345.678901",
        "2015:0.000000", "2015:0.000000", "2014:31535999.999999"};
    String[] expectedACQ_15dp = {"14.1:03:25:45.678901234500000",
        "14.365:23:59:59.999999999999999","14.365:23:59:59.999999999000000",
        "14.365:23:59:59.999999000000000"};
    String[] expectedACQ_9dp = {"14.1:03:25:45.678901235",
        "15.1:00:00:00.000000000", "14.365:23:59:59.999999999",
        "14.365:23:59:59.999999000"};
    String[] expectedACQ_6dp = {"14.1:03:25:45.678901", "15.1:00:00:00.000000",
        "15.1:00:00:00.000000", "14.365:23:59:59.999999"};
    String[] expectedSTD_15dp = {"2014:01:01::03:25:45.678901234500000",
        "2014:12:31::23:59:59.999999999999999",
        "2014:12:31::23:59:59.999999999000000",
        "2014:12:31::23:59:59.999999000000000"};
    String[] expectedSTD_9dp = {"2014:01:01::03:25:45.678901235",
        "2015:01:01::00:00:00.000000000", "2014:12:31::23:59:59.999999999",
        "2014:12:31::23:59:59.999999000"};
    String[] expectedSTD_6dp = {"2014:01:01::03:25:45.678901",
        "2015:01:01::00:00:00.000000", "2015:01:01::00:00:00.000000",
        "2014:12:31::23:59:59.999999"};
    String[] expectedNORAD = {"14001.14288980", "15001.00000000",
        "15001.00000000", "15001.00000000"};

    for (int pair=0; pair < wsecFsecPairs.length; pair++){
      t = new Time(wsecFsecPairs[pair][0], wsecFsecPairs[pair][1]);
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
            + " Year", expectedYear[pair], t.getYear());
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " Month", expectedMonth[pair], t.getMonth());
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " Day", expectedDay[pair], t.getDay());
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " Hour", expectedHour[pair], t.getHour());
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " Minute", expectedMin[pair], t.getMinute());
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " DoY", expectedDoY[pair], t.getDoY());
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " Fraction of Day", expectedFoD[pair], t.getFoD(), 0.000000000001);
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " Seconds of Day", expectedSoD[pair], t.getSoD(), 0.000000001);
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " Seconds of Year", expectedSoY[pair], t.getSoY(), 0.0000001);
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: EPOCH with 15 decimal places",
          expectedEPOCH_15dp[pair], t.toString(Time.EPOCH, 15));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: EPOCH with 9 decimal places",
          expectedEPOCH_9dp[pair], t.toString(Time.EPOCH, 9));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: EPOCH with 6 decimal places",
          expectedEPOCH_6dp[pair], t.toString(Time.EPOCH, 6));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: ACQ with 15 decimal places",
          expectedACQ_15dp[pair], t.toString(Time.ACQ, 15));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: ACQ with 9 decimal places",
          expectedACQ_9dp[pair], t.toString(Time.ACQ, 9));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: ACQ with 6 decimal places",
          expectedACQ_6dp[pair], t.toString(Time.ACQ, 6));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: STD with 15 decimal places",
          expectedSTD_15dp[pair], t.toString(Time.STD, 15));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: STD with 9 decimal places",
          expectedSTD_9dp[pair], t.toString(Time.STD, 9));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: STD with 6 decimal places",
          expectedSTD_6dp[pair], t.toString(Time.STD, 6));
      assertEquals("Time("+wsecFsecPairs[pair][0]+","+ wsecFsecPairs[pair][1]+") "
          + " toString - fmt: NORAD",
          expectedNORAD[pair], t.toString(Time.NORAD, 0)); //dp not used w/ NORAD
    }

  }

  public void testToTime() {
    String str     = "2007:06:25::11:22:33.444";
    Time   time    = new Time(str);
    double sec     = time.getSec();
    double wsec    = time.getWSec();
    double fsec    = time.getFSec();
    double date    = (new Time("2007:06:25::00:00:00")).getWSec();
    double tod     = (new Time("11:22:33")).getWSec();
    Data   dataSD  = new Data("SD",1); dataSD.setD(0,sec);
    Data   data1D  = new Data("1D",1); data1D.setD(0,sec);
    Data   dataCD  = new Data("CD",1); dataCD.setD(0,wsec); dataCD.setD(1,fsec);
    Data   data2D  = new Data("2D",1); data2D.setD(0,wsec); data2D.setD(1,fsec);
    String mathCD  = "("+(wsec-1e9)+"+1e9,"+(fsec-0.4)+"+0.4)";

    Object[] testCases = {
      // OBJECT                     FORMAT     ->  WSEC        FSEC
      str,                          Time.STD,      wsec,       fsec,
      time,                         Time.STD,      wsec,       fsec,
      Double.valueOf(sec),          Time.STD,      wsec,       fsec,
      Time.toDateObject(sec),       Time.STD,      wsec,       fsec,
      dataSD,                       Time.STD,      wsec,       fsec,
      data1D,                       Time.STD,      wsec,       fsec,
      dataCD,                       Time.STD,      wsec,       fsec,
      data2D,                       Time.STD,      wsec,       fsec,
      new Data(str),                Time.STD,      wsec,       fsec,
      ""+sec,                       Time.STD,      wsec,       fsec,
      "("+sec+")",                  Time.STD,      wsec,       fsec,
      "("+wsec+","+fsec+")",        Time.STD,      wsec,       fsec,
      "("+wsec+"+"+fsec+")",        Time.STD,      wsec,       fsec,
      mathCD,                       Time.STD,      wsec,       fsec,
      "2007-06-25T11:22:33.444",    Time.STD,      wsec,       fsec,  // <-- STD->ISO8601 due to 'T'
      "2007-06-25T11:22:33.444",    Time.ISO8601,  wsec,       fsec,
      "20070625T112233.444",        Time.ISO8601,  wsec,       fsec,
      "20070625T112233",            Time.ISO8601,  wsec,       0.0,
      "2007-06-25",                 Time.STD,      date,       0.0,   // <-- STD->ISO8601 due to '-'
      "2007-06-25",                 Time.ISO8601,  date,       0.0,
      "20070625",                   Time.ISO8601,  date,       0.0,   // <-- ISO8601 date
      "20070625",                   Time.STD,      20070625.0, 0.0,   // <-- STD number of seconds
      "11:22:33.444",               Time.STD,      tod,        fsec,
      "11:22:33.444",               Time.ISO8601,  tod,        fsec,  // <-- Same as STD in this case!
      "112233.444",                 Time.ISO8601,  tod,        fsec,  // <-- ISO8601 time
      "112233.444",                 Time.STD,      112233.0,   fsec,  // <-- STD number of seconds
      "11:22:33",                   Time.STD,      tod,        0.0,
      "11:22:33",                   Time.ISO8601,  tod,        0.0,   // <-- Same as STD in this case!
      "112233",                     Time.ISO8601,  tod,        0.0,   // <-- ISO8601 time
      "112233",                     Time.STD,      112233.0,   0.0,   // <-- STD number of seconds
    };

    for (int i = 0; i < testCases.length; i+=4) {
      Object  _obj  = testCases[i];
      Integer _fmt  = (Integer)testCases[i+1];
      Double  _wsec = (Double)testCases[i+2];
      Double  _fsec = (Double)testCases[i+3];
      Time    _time = Time.toTime(_obj, _fmt);

      assertEquals("toTime("+_obj+","+_fmt+") WSEC", _wsec, _time.getWSec(), 0.000);
      assertEquals("toTime("+_obj+","+_fmt+") FSEC", _fsec, _time.getFSec(), 0.001);

      if (_fmt == Time.STD) {
        _time = Time.toTime(_obj);
        assertEquals("toTime("+_obj+") WSEC", _wsec, _time.getWSec(), 0.000);
        assertEquals("toTime("+_obj+") FSEC", _fsec, _time.getFSec(), 0.001);
      }
    }
    assertEquals("toTime(..) obj=null", null, Time.toTime(null));
  }

  public void testEquals() {
    String str  = "2007:06:25::11:22:33.444";
    Time   time = new Time(str);
    Time   t2   = new Time();
    double sec  = time.getSec();

    t2.fromTime(time);
    Object[] EQUAL    = { time, str, t2 };
    Object[] NOTEQUAL = { null, Double.valueOf(0), "2002:03:10::11:22:33.444", new Time() };

    for (int i = 0; i < EQUAL.length; i++) {
      assertTrue("equals(..) obj="+EQUAL[i], time.equals(EQUAL[i]));
      assertEquals("compareTo(..) obj="+EQUAL[i], 0, time.compareTo(EQUAL[i]));
      assertEquals("equals(..) obj="+EQUAL[i], time.hashCode(), Time.toTime(EQUAL[i]).hashCode());
    }
    for (int i = 0; i < NOTEQUAL.length; i++) {
      assertFalse("equals(..) obj="+NOTEQUAL[i], time.equals(NOTEQUAL[i]));
    }
  }

  public void testCompareTo() {
    String str   = "2007:06:25::11:22:33.444";
    Time   time  = new Time(str);
    String strL  = "2005:06:25::11:22:33.444";
    Time   timeL = new Time(strL);
    String strE  = str;
    Time   timeE = new Time(strE);
    String strG  = "2009:06:25::11:22:33.444";
    Time   timeG = new Time(strG);

    Time t1L = new Time(time.getWSec()-1, time.getFSec());
    Time t1E = new Time(time.getWSec()+0, time.getFSec());
    Time t1G = new Time(time.getWSec()+1, time.getFSec());

    Time t2L = new Time(time.getWSec(), time.getFSec()-1e-7);
    Time t2E = new Time(time.getWSec(), time.getFSec()+0e-7);
    Time t2G = new Time(time.getWSec(), time.getFSec()+1e-7);

    Object[] LESS    = { strL, timeL, t1L, t2L };
    Object[] EQUAL   = { strE, timeE, t1E, t2E };
    Object[] GREATER = { strG, timeG, t1G, t2G };

    for (int i = 0; i < LESS.length; i++) {
      assertEquals("compareTo(..) obj="+LESS[i],    +1, time.compareTo(LESS[i]));
      assertEquals("compareTo(..) obj="+EQUAL[i],   +0, time.compareTo(EQUAL[i]));
      assertEquals("compareTo(..) obj="+GREATER[i], -1, time.compareTo(GREATER[i]));
    }
  }

  public void testFromCalendar () {
    Calendar cal;
    String[] times = (String[])TIME_TESTS[3];
    for(int i = 3; i < 13; i++) {
      cal = Calendar.getInstance();
      cal.setTimeZone(TimeZone.getTimeZone("UTC"));
      cal.set(TIME_DATE[i*8], TIME_DATE[i*8+1] - 1, TIME_DATE[i*8+2], TIME_DATE[i*8+3], TIME_DATE[i*8+4], TIME_DATE[i*8+5]);

      //remove the 'random' fractional seconds remaining from the initializaion of the calendar object and add the correct value.
      cal.setTimeInMillis((long)(((cal.getTimeInMillis() / 1000) * 1000) + (TIME_VALUES[i*2+1]*1000)));

      assertEquals(Time.fromCalendar(cal).toString(), times[i]);
    }
  }

  public void testToCalendar () {
    String[] times = (String[])TIME_TESTS[3];
    for(int i = 3; i < 13; i++) {
      Time t = new Time();
      t.fromString(times[i]);

      Calendar cal = t.toCalendar();
      t = Time.fromCalendar(cal);

      assertEquals(t.toString(), times[i]);
    }
  }

  public void testFromXMLGregorianCalendar () {
    XMLGregorianCalendar xcal;
    String[] times = (String[])TIME_TESTS[3];
    try {
      DatatypeFactory dtf = DatatypeFactory.newInstance();

      for(int i = 3; i < 13; i++) {
        Time t = new Time();
        t.fromString(times[i]);
        GregorianCalendar cal = t.toCalendar();
        xcal = dtf.newXMLGregorianCalendar(cal);

        assertEquals(Time.fromXMLGregorianCalendar(xcal).toString(), times[i]);
      }
    }
    catch (DatatypeConfigurationException e) {
      throw new RuntimeException("Could not create new DatatypeFactory", e);
    }
  }

  public void testToXMLGregorianCalendar () {
    String[] times = (String[])TIME_TESTS[3];
    for (int i = 3; i < 13; i++) {
      Time t = new Time();
      t.fromString(times[i]);
      assertEquals(Time.fromXMLGregorianCalendar(t.toXMLGregorianCalendar()).toString(), times[i]);
    }
  }


  //////////////////////////////////////////////////////////////////////////////
  // Private Methods
  //////////////////////////////////////////////////////////////////////////////
  /** Strips any fractional part of a day off. */
  private static final double[] ymdOnly(double[] oldTimes) {
    double[] newTimes = new double[oldTimes.length];
    for (int i = 0; i < newTimes.length; i+=2) {
      double sec = oldTimes[i] % 86400;
      newTimes[i+0] = oldTimes[i] - sec;
      newTimes[i+1] = 0.0;
    }
    return newTimes;
  }

  /** Strips any whole days off. */
  private static final double[] hmsOnly(double[] oldTimes) {
    double[] newTimes = new double[oldTimes.length];
    for (int i = 0; i < newTimes.length; i+=2) {
      if ((oldTimes[i+0] == 86400.0) && (oldTimes[i+1] == 0.0)) { // <-- Special case 24:00:00 (Bug 1250)
        newTimes[i+0] = 86400.0;
        newTimes[i+1] = 0.0;
      } else {
        double sec = oldTimes[i] % 86400;
        newTimes[i+0] = sec;
        newTimes[i+1] = oldTimes[i+1];
      }
    }
    return newTimes;
  }

  /** Converts a set of dates from year X to this year. */
  private static final double[] toThisYear(double[] oldTimes) {
    double[] newTimes = new double[oldTimes.length];
    Time now = new Time();
    now.fromCurrent();
    int newDays = now.getMJD() - now.getDoY(); // MJD to this year

    Time t = new Time();
    for (int i = 0; i < newTimes.length; i+=2) {
      t.fromJ1950(oldTimes[i+0],oldTimes[i+1]);
      int oldDays = t.getMJD() - t.getDoY(); // MJD to that year

      newTimes[i+0] = oldTimes[i+0] - (86400d * Double.valueOf(oldDays-newDays));
      newTimes[i+1] = oldTimes[i+1];
    }
    return newTimes;
  }

  /** Test BUG 2126 */
  public void testIllegalTime() {
    try {
      info("!!!!! It is ok to see warning below: Invalid input time... !!!!!");
      Time time = new Time(1e15);
      String timeString = Time.toString(1e15);
      fail("Failed to throw IllegalArgumentException for illegal input time.");
    }
    catch ( IllegalArgumentException iae ) {
      assertTrue(true);
    }
    catch ( Exception e ) {
      assertTrue(true);
    }
  }

  /** Test BUG 2126 */
  public void testValidTime() {
    double inputValue = 86400.0 * (double) Integer.MAX_VALUE;
    try {
      Time time = new Time(inputValue);
      String timeString = Time.toString(inputValue);
      assertNotNull("able to create Time from a valid time input as a double", time);
      assertNotNull("able to convert string from a valid time input as a double", timeString);
    }
    catch ( Exception e ) {
      fail("Failed to create Time based on a valid input value of " + inputValue);
    }
  }

  public void testToTimeTicks () {
    final Object[] testCases = {
      //                   from epoch             from J1950
      // EPOCH      DELTA  TICKS               -> WSEC               FSEC    TOL
      "1950-01-01", 1.0,                     0L,  0.0,               0.0,              0.0,   // 1950-01-01::00:00:00
      "1970-01-01", 1.0,                     0L,  Time.J1970TOJ1950, 0.0,              0.0,   // 1970-01-01::00:00:00
      "1994-01-01", 1.0,                     0L,  1.3885344E9,       0.0,              0.0,   // 1994-01-01::00:00:00
      "1994-01-01", 1e-6,                    0L,  1.3885344E9,       0.0,              0.0,   // 1994-01-01::00:00:00
      "1994-01-01", 1e-10,                   1L,  1.3885344E9,       1e-10,            1e-10, // 1994-01-01::00:00:00.0000000001
      "1994-01-01", 1e-9,       86400000000000L,  1.3886208E9,       0.0,              0.0,   // 1994-01-01::00:00:00
      "1994-01-01", 1e-10, 5948228960000000000L,  1.983357296E9,     0.0,              0.0,   // 2012-11-06T12:34:56
      "1994-01-01", 1e-10, 5948228967890000000L,  1.983357296E9,     0.789,            0.001, // 2012-11-06T12:34:56.789
      "1994-01-01", 1e-9,   599615999123456789L,  1.988150399E9,     0.123456789,      1e-9,  // 2012:12:31::23:59:59.123456789 (J1994 epoch)
      "2012-01-01", 1e-10,  271624876543210123L,  1.983690487E9,     0.6543210123,     1e-10, // 2012:11:10::09:08:07.6543210123  (start of year epoch) - 0.1 nanosecond precision
      "2012-01-01", 1e-10,  271624876543210124L,  1.983690487E9,     0.6543210124,     1e-10, // 2012:11:10::09:08:07.6543210124  (start of year epoch)
      "2012-01-01", 1e-11, 2716248765432101234L,  1.983690487E9,     0.65432101234,    1e-11, // 2012:11:10::09:08:07.65432101234 (start of year epoch) - 10 picosecond precision
      "2012-01-01", 1e-11, 2716248765432101235L,  1.983690487E9,     0.65432101235,    1e-11, // 2012:11:10::09:08:07.65432101235 (start of year epoch)
      "1950-01-01", 1e-8,   921481919912345678L,  9.214819199E9,     0.12345678,       1e-8,  // 2241:12:31::23:59:59.12345678  (J1950 epoch)
      "1950-01-01", 1e-8,   921481919912345679L,  9.214819199E9,     0.12345679,       1e-8,  // 2241:12:31::23:59:59.12345679  (J1950 epoch)
      "1950-01-01", 1e-9,  9214819199123456789L,  9.214819199E9,     0.123456789,      1e-9,  // 2241:12:31::23:59:59.123456789 (J1950 epoch)
      "1950-01-01", 1e-9,  9214819199123456788L,  9.214819199E9,     0.123456788,      1e-9,  // 2241:12:31::23:59:59.123456788 (J1950 epoch)
      "2012-01-01", 1e-12,       7654321012345L,  1.956528007E9,     0.654321012345,   1e-12, // 2012:01:01::00:00:07.654321012345 (start of day epoch) - picosecond precision
      "2012-01-01", 1e-14,     712345678901234L,  1.956528007E9,     0.12345678901234, 1e-14, // 2012:01:01::00:00:07.12345678901234 (start of day epoch)
      "2012-01-01", 1e-14,     712345678901235L,  1.956528007E9,     0.12345678901235, 1e-14, // 2012:01:01::00:00:07.12345678901235  (start of day epoch)
      "2012-12-12", 1e-15, 9033123456789012345L,  1.986431433E9,     0.123456789012345,1e-15, // 2012:12:12::02:30:33.123456789012345 (start of collect epoch) - femtosecond precision
      "2012-12-12", 1e-15, 9033123456789012346L,  1.986431433E9,     0.123456789012346,1e-15, // 2012:12:12::02:30:33.123456789012346 (start of collect epoch)
    };

    for (int i = 0; i < testCases.length; i+=6) {
      String epochStr = testCases[i].toString();
      Time   epoch = Time.toTime(epochStr);
      double delta = ((Number)testCases[i+1]).doubleValue();
      long   ticks = ((Number)testCases[i+2]).longValue();
      double wsec  = ((Number)testCases[i+3]).doubleValue();
      double fsec  = ((Number)testCases[i+4]).doubleValue();
      double tol   = ((Number)testCases[i+5]).doubleValue();

      Time   t   = new Time(ticks, epoch, delta);
      String msg = i/6+ ". new Time("+ticks+", '"+testCases[i]+"', "+delta+") ";
      assertEquals(msg+"wsec",  wsec,  t.getWSec(), 0.0);
      assertEquals(msg+"fsec",  fsec,  t.getFSec(), tol);
      assertEquals(msg+"ticks", ticks, t.toTimeTicks(epoch, delta));
    }
  }

  /** Tests new Time(double) for a problem seen in NeXtMidas 3.3.0rc1. */
  public void testTime () {
    double[] TEST_VALUES = {
      // INPUT           =>     WSEC       FSEC
      0.0,                      0.0,       0.0,
      0.2,                      0.0,       0.2,
      0.02,                     0.0,       0.02,
      0.002,                    0.0,       0.002,
      0.0002,                   0.0,       0.0002,
      0.00002,                  0.0,       0.00002,
      0.000002,                 0.0,       0.000002,
      0.2000000,                0.0,       0.200000,
      0.2000001,                0.0,       0.200000,
      0.2000002,                0.0,       0.200000,
      0.2000003,                0.0,       0.200000,
      0.2000004,                0.0,       0.200000,
      0.2000005,                0.0,       0.200001, // <-- use of Math.round(..) vs Math.rint(..)
      0.2000006,                0.0,       0.200001,
      0.2000007,                0.0,       0.200001,
      0.2000008,                0.0,       0.200001,
      0.2000009,                0.0,       0.200001,
      0.9999999,                1.0,       0.000000,
    };

    for (int i = 0; i < TEST_VALUES.length; i+=3) {
      Time t = new Time(TEST_VALUES[i]);

      assertEquals("new Time("+TEST_VALUES[i]+") wsec", TEST_VALUES[i+1], t.getWSec());
      assertEquals("new Time("+TEST_VALUES[i]+") fsec", TEST_VALUES[i+2], t.getFSec());
    }
  }

  public void testToAcqDate () {
    DataFile dfOrig = new DataFile(getMidasContext(), "nxm.net.dat.test1000.prm");
    DataFile df = new DataFile(getMidasContext(),"test1000Copy",dfOrig,0);
    Time time = new Time("2014:08:22::16:47:58.123");
    time.toAcqDate(df);
    time.fromAcqDate(df);
    assertEquals(time.getYear(), 2014);
    assertEquals(time.getMonth(), 8);
    assertEquals(time.getDay(), 22);
    assertEquals(time.getHour(), 16);
    assertEquals(time.getMinute(), 47);
    assertEquals(time.getSecond(), 58);
    assertEquals(time.getFSec(), 0); // ACQDATE and ACQTIME drop off milliseconds, should be 0 now
  }
}
