// $Id: Epoch16.java,v 1.1.1.1 2025/09/11 12:24:45 mhliu Exp $
/*
 * Copyright 1996-2014 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gsfc.nssdc.cdf.util;

import gsfc.nssdc.cdf.CDFException;
import java.text.NumberFormat;
import java.text.DecimalFormat;

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

/**
 *
 * This class contains the handy utility routines (methods) called by the
 * CDF applications to handle epoch data of CDF's CDF_EPOCH16 data type.
 *
 * @version 1.0
 *
 * <B>Example:</B>
 * <PRE> 
 * // Get the time, down to picoseconds, for Aug 5, 1990 at 5:0:0.0.0.0
 * double[] epoch16 = new double[2];
 * double ep = Epoch16.compute(1990, 8, 5, 5, 0, 0, 0, 0, 0, 0, epoch16);
 * //Get the year, month, day, hour, minutes, seconds, milliseconds,
 * //    microseconds, nanaseconds and picoseconds for epoch16
 * long[] times = Epoch16.breakdown(epoch16);
 * for (int i=0;i&lt;times.length;i++)
 *     System.out.print(times[i]+" ");
 * System.out.println();
 * // Printout the epoch in various formats
 * System.out.println(Epoch16.encode(epoch16));
 * System.out.println(Epoch16.encode1(epoch16));
 * System.out.println(Epoch16.encode2(epoch16));
 * System.out.println(Epoch16.encode3(epoch16));
 * System.out.println(Epoch16.encode4(epoch16));
 * // Print out the date using format
 * String format = "&lt;month&gt; &lt;dom.02&gt;, &lt;year&gt; at &lt;hour&gt;:&lt;min&gt;";
 * System.out.println(Epoch16.encodex(epoch16,format));
 * </PRE>
 */
public class Epoch16 implements gsfc.nssdc.cdf.CDFConstants {

    private static double MAX_EPOCH16_1_BINARY = 3.15569519999E11;
    private static double MAX_EPOCH16_2_BINARY = 9.99999999998E11;

    private static int MAX_ePART_LEN =	25;

    private static String [] _monthToken = {    
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec"
    };

    /**
     * 
     * This function parses a date/time string and returns an
     * EPOCH16 value.  The string format must be exactly as one of the
     * followings (which are made from the corresponding encoding methods).
     *
     * <PRE>
     * Style 0 - 
     * Format:   dd-mmm-yyyy hh:mm:ss.mmm.uuu.nnn.ppp
     * Examples:  1-Apr-1990 03:05:02.000.000.000.000
     *           10-Oct-1993 23:45:49.999.999.999.999
     *
     * Style 1 - 
     * Format:   yyyymmdd.ttttttttttttttt
     * Examples: 19900401.365889312345678
     *           19931010.000000000000000
     *
     * Style 2 - 
     * Format:   yyyymmddhhmmss
     * Examples: 19900401030502
     *           19931010234549
     *
     * Style 3 - 
     * Format:   yyyy-mm-ddThh:mm:ss.mmm.uuu.nnn.pppZ
     * Examples: 1990-04-01T03:05:02.000.000.000.000Z
     *           1993-10-10T23:45:49.999.999.999.999Z
     *
     * Style 4 - 
     * Format:   yyyy-mm-ddThh:mm:ss.mmmuuunnnppp
     * Examples: 1990-04-01T03:05:02.000000000000
     *           1993-10-10T23:45:49.999999999999
     *
     * </PRE>
     *
     * @param inString the epoch in string representation
     *
     * @return the value of the epoch represented by inString or null if null
     *         or an empty string is detected
     *
     * @exception CDFException if a bad epoch value is passed in inStrings
     */
    public static double[] toParse (String inString)
        throws CDFException
    {
        if (inString == null) return null;
        inString = inString.trim();
        int len = inString.length();
        char aC;
        if (len == EPOCH_STRING_LEN_EXTEND) { // style 0 or style 3
          if (inString.charAt(11) == ' ') return (double[]) parse(inString);
          aC = inString.charAt(10);
          if (aC == 'T' || aC == 't' || aC == ' ' || aC == '/') 
            return (double[]) parse3(inString);
          else return null;
        } else if (len == EPOCH4_STRING_LEN_EXTEND) {
          aC = inString.charAt(10);
          if (aC == 'T' || aC == 't' || aC == ' ' || aC == '/')
            return (double[]) parse4(inString);
          else return new double[] {-1.0E31, -1.0E31};
        } else if (len <= EPOCH1_STRING_LEN_EXTEND &&
                   inString.charAt(8) == '.' &&
                   inString.matches("\\d+.\\d+")) {
          return (double[]) parse1(inString);
        } else if (len == EPOCH2_STRING_LEN_EXTEND && 
                   inString.matches("\\d+")) {
          return (double[]) parse2(inString);
        } else if (inString.charAt(11) == ' ') { // style 0, sort of
          return (double[]) parse(inString);
        } else if (inString.charAt(10) == 'T' || inString.charAt(10) == 't' ||
                   inString.charAt(10) == ' ' || inString.charAt(10) == '/') {
             if (inString.endsWith("Z") || inString.endsWith("z"))
               return (double[]) parse3(inString);
             else {
               if (len == (EPOCH3_STRING_LEN_EXTEND - 1)) 
                 return (double[]) parse3(inString);
               else
                 return (double[]) parse4(inString);
             }
        } else
          return new double[] {-1.0E31, -1.0E31};
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH16
     * value.  The format must be exactly as shown below.  
     * Month abbreviations may be in any case and are always the first 
     * three letters of the month.
     *
     * <PRE>
     * Format:          dd-mmm-yyyy hh:mm:ss.ccc.mmm.nnn.ppp
     * Examples:         1-Apr-1990 03:05:02.000.000.000.000
     *                  10-Oct-1993 23:45:49.999.999.999.999
     * </PRE>
     *
     * The expected format is the same as that produced by encode.
     *
     * @param inString the epoch in string representation
     * @return the value of the epoch represented by inString
     *
     * @exception CDFException if a bad epoch value is passed in inString
     */
    public static Object parse (String inString)
	throws CDFException
    {
	long 
	    year   = 0, 
	    month  = 0, 
	    day    = 0, 
	    hour   = 0, 
	    minute = 0, 
	    second = 0, 
	    msec   = 0,
	    usec   = 0,
	    nsec   = 0,
	    psec   = 0;
	double mmm;
	double[]  epoch16 = new double[2];
        inString = inString.trim();
	String monthStr, secondStr;
        if (inString.endsWith("Z") || inString.endsWith("z"))
          inString = inString.substring(0, inString.length()-1);
	try {
	    StringTokenizer st = new StringTokenizer(inString, " ");
	    String date = st.nextToken();
	    String time = st.nextToken();

	    // Get the date portion of the string
	    st = new StringTokenizer(date, "-");

	    day = Long.parseLong(st.nextToken());
	    monthStr = st.nextToken();
	    year = Long.parseLong(st.nextToken());
	    for (int monthX = 1; monthX <= 12; monthX++) {
		if (monthStr.equalsIgnoreCase(_monthToken[monthX-1])) {
		    month = monthX;
		    break;
		}
	    }
	    if (month == 0) 
		throw new CDFException(ILLEGAL_EPOCH_FIELD);
	    
	    // Get the time portion
	    st = new StringTokenizer(time, ":");
	    hour   = Long.parseLong(st.nextToken());
	    minute = Long.parseLong(st.nextToken());
	    secondStr = st.nextToken();
	    st = new StringTokenizer(secondStr, ".");
	    if (st.hasMoreElements()) second = Long.parseLong(st.nextToken());
	    if (st.hasMoreElements()) msec   = Long.parseLong(st.nextToken());
	    if (st.hasMoreElements()) usec   = Long.parseLong(st.nextToken());
	    if (st.hasMoreElements()) nsec   = Long.parseLong(st.nextToken());
	    if (st.hasMoreElements()) psec   = Long.parseLong(st.nextToken());
	    mmm =  compute(year, month, day, hour, minute, second, 
			   msec, usec,  nsec, psec, epoch16);
	    if (mmm == ILLEGAL_EPOCH_VALUE) 
	      throw new CDFException(ILLEGAL_EPOCH_FIELD);
	    return epoch16;

	} catch (Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH16
     * value.  The format must be exactly as shown below.  Note that if 
     * there are less than 15 digits after the decimal point, zeros (0's)
     * are assumed for the missing digits.
     *
     * <PRE>
     * Format:            yyyymmdd.ttttttttttttttt
     * Examples:          19950508.000000000000000
     *                    19671231.58      (== 19671213.580000000000000)
     * </PRE>
     *
     * The expected format is the same as that produced by encode1.
     *
     * @param inString the epoch in string representation
     * @return the value of the epoch represented by inString
     *
     * @exception  CDFException if a bad epoch value is passed in inString
     */
    public static Object parse1 (String inString)
	throws CDFException
    {
	StringTokenizer st = new StringTokenizer(inString, ".");
        inString = inString.trim();
        try {
	  String date = st.nextToken();
	  String time = st.nextToken();
	  long year = 0, month = 0, day = 0, hour = 0, minute = 0, 
	       second = 0, msec = 0, usec = 0, nsec = 0, psec = 0,
	       fractionL = 0;
	  double fraction = 0.0, mmm; 
	  double[] epoch16 = new double[2];
	  int len;
	    year   = Long.parseLong(date.substring(0,4));
	    month  = Long.parseLong(date.substring(4,6));
	    day    = Long.parseLong(date.substring(6));
	    fractionL = Long.parseLong(time);
	    len = time.length();
	    
	    fraction = ((double) fractionL) / Math.pow(10.0, (double) len);
	    hour = (long) (fraction * 24.0);
	    fraction -= (double) (hour / 24.0);
	    minute = (long) (fraction * 1440.0);
	    fraction -= (double) (minute / 1440.0);
	    second = (long) (fraction * 86400.0);
	    fraction -= (double) (second / 86400.0);
	    msec = (long) (fraction * 86400.0 * Math.pow(10.0, 3.0));
            fraction -= (double) (msec / (86400.0 * Math.pow(10.0, 3.0)));
	    usec = (long) (fraction * 86400.0 * Math.pow(10.0, 6.0));
            fraction -= (double) (usec / (86400.0 * Math.pow(10.0, 6.0)));
	    nsec = (long) (fraction * 86400.0 * Math.pow(10.0, 9.0));
            fraction -= (double) (nsec / (86400.0 * Math.pow(10.0, 9.0)));
	    psec = (long) (fraction * 86400.0 * Math.pow(10.0, 12.0));
	    mmm = compute(year, month, day, hour, minute, second, 
			  msec, usec, nsec, psec, epoch16);
	    if (mmm == ILLEGAL_EPOCH_VALUE)
	      throw new CDFException(ILLEGAL_EPOCH_FIELD);
	    return epoch16;
	} catch (Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH16
     * value.  The format must be exactly as shown below.
     *
     * <PRE>
     * Format:           yyyymmddhhmmss
     * Examples:         19950508000000
     *                   19671231235959
     * </PRE>
     *
     * The expected format is the same as that produced by encode2.
     *
     * @param inString the epoch in string representation
     * @return the value of the epoch represented by inString
     *
     * @exception CDFException if a bad epoch value is passed in inString
     */
    public static Object parse2 (String inString)
	throws CDFException
    {
	long year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
	double mmm;
	double[] epoch16 = new double[2];
        inString = inString.trim();
	try {
	    year   = Long.parseLong(inString.substring(0,4));
	    month  = Long.parseLong(inString.substring(4,6));
	    day    = Long.parseLong(inString.substring(6,8));
	    hour   = Long.parseLong(inString.substring(8,10));
	    minute = Long.parseLong(inString.substring(10,12));
	    second = Long.parseLong(inString.substring(12));
	    mmm =  compute(year, month, day, hour, minute, second, 
			   0L, 0L, 0L, 0L, epoch16);
	    if (mmm == ILLEGAL_EPOCH_VALUE)
	      throw new CDFException(ILLEGAL_EPOCH_FIELD);
	    return epoch16;
	} catch (Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }
    
    /**
     * 
     * This function parses an input date/time string and returns an EPOCH16
     * value.  The format must be exactly as shown below.
     *
     * <PRE>
     * Format:            yyyy-mm-ddThh:mm:ss.ccc.mmm.nnn.pppZ
     * Examples:          1990-04-01T03:05:02.000.000.000.000Z
     *                    1993-10-10T23:45:49.999.999.999.999Z
     * </PRE>
     *
     * The expected format is the same as that produced by encode3.
     *
     * @param inString the epoch in string representation
     * @return the value of the epoch represented by inString
     *
     * @exception CDFException if a bad epoch value is passed in inString
     */
    public static Object parse3 (String inString)
	throws CDFException
    {
	long year = 0, month = 0, day = 0, hour = 0, 
	    minute = 0, second = 0, msec = 0, usec = 0, nsec = 0, psec = 0;

	String secondStr;
	double[] epoch16 = new double[2];
	double mmm;
        inString = inString.trim();
	try {
	    // Breakup the date and time fields
	    String tt0 = new String(new char[] {inString.charAt(10)});
    	    StringTokenizer st = new StringTokenizer(inString, tt0);
	    String date = st.nextToken();
	    String tt = st.nextToken();
	    String time;
            if (inString.endsWith("Z") || inString.endsWith("z"))
              time = tt.substring(0,tt.length() - 1);
            else
              time = tt.substring(0,tt.length());
	    String fracSec;

	    // Breakup the date portion
	    st = new StringTokenizer(date, "-");
	    year = Long.parseLong(st.nextToken());
	    month = Long.parseLong(st.nextToken());
	    day = Long.parseLong(st.nextToken());

	    // Get the time portion
	    st = new StringTokenizer(time, ":");
	    hour   = Long.parseLong(st.nextToken());
	    minute = Long.parseLong(st.nextToken());
	    secondStr = st.nextToken();
	    st = new StringTokenizer(secondStr, ".");
	    second = Long.parseLong(st.nextToken());
	    if (st.hasMoreTokens()) msec = Long.parseLong(st.nextToken());
	    if (st.hasMoreTokens()) usec = Long.parseLong(st.nextToken());
	    if (st.hasMoreTokens()) nsec = Long.parseLong(st.nextToken());
	    if (st.hasMoreTokens()) psec = Long.parseLong(st.nextToken());
	    mmm =  compute(year, month, day, hour, minute, second, 
			   msec, usec,  nsec, psec, epoch16);
	    if (mmm == ILLEGAL_EPOCH_VALUE)
	      throw new CDFException(ILLEGAL_EPOCH_FIELD);
	    return epoch16;
	} catch (java.lang.Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * This function parses an input date/time, ISO8601 string and returns
     * an EPOCH16 value.  The format must be exactly as shown below.
     *
     * <PRE>
     * Format:            yyyy-mm-ddThh:mm:ss.cccmmmnnnppp
     * Examples:          1990-04-01T03:05:02.000000000000
     *                    1993-10-10T23:45:49.999999999999
     * </PRE>
     *
     * The expected format is the same as that produced by encode4.
     *
     * @param inString the epoch in string representation
     * @return the value of the epoch represented by inString
     *
     * @exception CDFException if a bad epoch value is passed in inString
     */
    public static Object parse4 (String inString)
	throws CDFException
    {
	long year = 0, month = 0, day = 0, hour = 0, 
	    minute = 0, second = 0, msec = 0, usec = 0, nsec = 0, psec = 0;

	String secondStr;
	double[] epoch16 = new double[2];
	double mmm;
        inString = inString.trim();
	try {
	    // Breakup the date and time fields
	    String tt0 = new String(new char[] {inString.charAt(10)});
    	    StringTokenizer st = new StringTokenizer(inString, tt0);
	    String date = st.nextToken();
	    String time = st.nextToken();
	    String fracSec;

	    // Breakup the date portion
	    st = new StringTokenizer(date, "-");
	    year = Long.parseLong(st.nextToken());
	    month = Long.parseLong(st.nextToken());
	    day = Long.parseLong(st.nextToken());

	    // Get the time portion
	    st = new StringTokenizer(time, ":");
	    hour   = Long.parseLong(st.nextToken());
	    minute = Long.parseLong(st.nextToken());
	    secondStr = st.nextToken();
	    st = new StringTokenizer(secondStr, ".");
	    second = Long.parseLong(st.nextToken());
            if (st.hasMoreTokens()) {
              String subSecs = st.nextToken();
              int subLen = subSecs.length();
	      msec = Long.parseLong(subSecs.substring(0,3));
              if (subLen > 3) usec = Long.parseLong(subSecs.substring(3,6));
	      if (subLen > 6) nsec = Long.parseLong(subSecs.substring(6,9));
	      if (subLen > 9) psec = Long.parseLong(subSecs.substring(9,12));
            }
	    mmm =  compute(year, month, day, hour, minute, second, 
			   msec, usec,  nsec, psec, epoch16);
	    if (mmm == ILLEGAL_EPOCH_VALUE)
	      throw new CDFException(ILLEGAL_EPOCH_FIELD);
	    return epoch16;
	} catch (java.lang.Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * Converts an EPOCH16 value into a readable date/time string.
     * Depending on the encoding style, it can present the string to
     * one of the following forms: 
     *
     * <PRE>
     * For style 0: 
     * Format:    dd-mmm-yyyy hh:mm:ss.ccc.uuu.nnn.ppp
     * Examples:  01-Apr-1990 03:05:02.000.111.222.333
     *            10-Oct-1993 23:45:49.999.999.999.999
     * 
     * For style 1: 
     * Format:    yyyymmdd.ttttttt
     * Examples:  19950508.0000000
     *            19671231.58      (== 19671213.5800000)
     *  
     * For style 2: 
     * Format:    yyyymmddhhmmss
     * Examples:  19900401030502 
     *            19931010234549
     *  
     * For style 3: 
     * Format:    yyyymmddThh:mm:ss.ccc.uuu.nnn.pppZ
     * Examples:  19900401T03:05:02.000.111.222.333Z
     *            19931010T23:45:49.999.999.999.999Z
     *      
     * For style 4: 
     * Format:    yyyymmddThh:mm:ss.cccuuunnnppp
     * Examples:  19900401T03:05:02.000111222333
     *            19931010T23:45:49.999999999999
     * The default style, if the style is invalid.
     *      
     * </PRE>
     *      
     * @param epoch the epoch value
     * @param style the encoding style
     *      
     * @return A string representation of the epoch
     */     
    public static String toEncode(double[] epoch, int style)
    {       
        if (style < 0 || style > 4) style = 4;
        if (style == 0) return encode(epoch);
        else if (style == 1) return encode1(epoch);
        else if (style == 2) return encode2(epoch);
        else if (style == 3) return encode3(epoch);
        else return encode4(epoch);
    }

    /**
     * 
     * Converts an EPOCH object into a scalar or array of readable 
     * date/time string.
     * Each string is presented in the following ISO 8601 form: 
     *
     * <PRE>
     *
     * Format:    yyyymmddThh:mm:ss.cccuuunnnppp
     * Examples:  19900401T03:05:02.000111222333
     *            19931010T23:45:49.999999999999
     *
     * </PRE>
     *
     * @param epoch the epoch object
     *
     * @return A string or array of strings representation of the epoch
     */
    public static Object toEncode(Object epoch)
    {
        if (epoch == null) return null;
        int nDims = CDFUtils.getNumDimensions(epoch);
        long[] dimSizes = CDFUtils.getDimensionSizes(epoch);
        if (nDims > 1) {
          Object encodedArray = null;
          String className = "java.lang.String";
          int[] tempSizes = new int[nDims];
          for (int j = 0; j < nDims; ++j) tempSizes[j] = (int) dimSizes[j];
          Class arrayClass;
          try {
            arrayClass = Class.forName(className);
            encodedArray = Array.newInstance(arrayClass, tempSizes);
          } catch (ClassNotFoundException e) {
            System.out.println("**** error BAD_ARGUMENT");
            System.exit(0);
          }
          int arrayLength = ((Object[])epoch).length;
          int i;
          Object subObj;
          for (i = 0; i < arrayLength; ++i) {
            ((Object[])encodedArray)[i] = toEncode (((Object[])epoch)[i]);
          }
          return encodedArray;
        } else {
           long num = dimSizes[0] / 2;
          return toEncode(epoch, num);
        }

    }

    /**
     * 
     * Converts an EPOCH object into a scalar or array of readable 
     * date/time string.
     * Each string is presented in the following ISO 8601 form: 
     *
     * <PRE>
     *
     * Format:    yyyymmddThh:mm:ss.cccuuunnnppp
     * Examples:  19900401T03:05:02.000111222333
     *            19931010T23:45:49.999999999999
     *
     * </PRE>
     *
     * @param epoch the epoch object
     *
     * @return A string or array of strings representation of the epoch
     */
    public static String toEncode(double[] epoch)
    {
         return toEncode(epoch, 4);

    }

    /**
     * 
     * Converts an EPOCH object into a scalar or array(s) of readable 
     * date/time string.
     * Each string is  presented in the following ISO 8601 form: 
     *
     * <PRE>
     *
     * Format:    yyyymmddThh:mm:ss.cccuuunnnppp
     * Examples:  19900401T03:05:02.000111222333
     *            19931010T23:45:49.999999999999
     *
     * </PRE>
     *
     * @param epoch the epoch object
     * @param size the number of CDF_EPOCH16 data
     *
     * @return A string or array(s) of strings representation of the epoch
     */
    public static Object toEncode(Object epoch, long size)
    {
        if (size == 1)
          return toEncode((double[])epoch, 4);
        else if (size == 0)
          return null;
        else {
          int i;
          String[] buff = new String[(int)size];
          double[] ep16 = new double[2];
          for (i = 0; i < size; ++i) {
            ep16[0] = ((double[])epoch)[2*i];
            ep16[1] = ((double[])epoch)[2*i+1];
            buff[i] = toEncode(ep16, 4);
          }
          return buff; 
        }
    }

    /**
     * 
     * Converts an EPOCH16 value to a unix time of a double value (seconds from
     * 1970-01-01 00:00:00 UTC). The unix time will have a time 
     * resolution of microseconds at its fraction part.
     *
     * Note: As EPOCH16 have a higher time resolution than the unix time. The
     * conversion might not precisely preserve the microseconds due to the 
     * floating point presentation.
     *
     * @param epoch the epoch16 value (2-double)
     *
     * @return A unix time value
     */
    public static double Epoch16toUnixTime (double[] epoch)
    {
        double seconds = ((double[])epoch)[0];
        double subsecs = ((double[])epoch)[1];
        double submics = subsecs / Math.pow(10.0, 6.0);
        long   micsecs = (long) submics;
        if ((submics - micsecs) > 0.5) micsecs += 1;
	return (seconds-BeginUnixTimeEPOCH16+micsecs/Math.pow(10.0, 6.0));
	
    }

    /**
     * 
     * Converts a unix time of a double value (seconds from 
     * 1970-01-01 00:00:00 UTC) to an EPOCH16 value. The unix time can have a 
     * time resolution in microseconds at its fraction part.
     *
     * Note: As EPOCH16 have a higher time resolution than the unix time. The
     * conversion might not precisely preserve the microseconds due to the 
     * floating point presentation.
     * 
     * @param unixTime the unix time value
     *
     * @return An EPOCH16 value (2-double)
     */
    public static double[] UnixTimetoEpoch16 (double unixTime)
    {
        double[] epoch = new double[2];
        epoch[0] = BeginUnixTimeEPOCH16 + (long) unixTime;
        double submics = (unixTime - (long) unixTime) * Math.pow(10.0, 6.0);
        long micsecs = (long) submics;
        if ((submics - micsecs) > 0.5) micsecs += 1;
        epoch[1] = (double) micsecs * Math.pow(10.0, 6.0); 
	return epoch;
	
    }

    /**
     * 
     * Converts an EPOCH16 value into a readable date/time string.
     *
     * <PRE>
     * Format:            dd-mmm-yyyy hh:mm:ss.ccc.mmm.nnn.ppp
     * Examples:          01-Apr-1990 03:05:02.000.000.000.000
     *                    10-Oct-1993 23:45:49.999.999.999.999
     * </PRE>
     *
     * This format is the same as that expected by parse.
     *
     * @param epoch the epoch value
     *
     * @return A string representation of the epoch
     */
    public static String encode (Object epoch)
    {
	double[] epoch16 = new double[2];
	String epString1 = new String(),
	       epString2 = new String();
	epoch16[0] = ((double[]) epoch)[0];
	epoch16[1] = ((double[]) epoch)[1];
	if (epoch16[0] == -1.0E31 && epoch16[1] == -1.0E31) {
          return "31-Dec-9999 23:59:59.999.999.999.999";
        }

	epString1 = encodex (epoch16, 
		 "<dom.02>-<month>-<year> <hour>:<min>:<sec>.<fos>");
	int ix = epString1.indexOf(".")+1;
	epString2 = encode_2 (epoch16[1]);
	return new StringBuffer(epString1.substring(0,ix)).append(epString2).toString();
	
    }

    /**
     * 
     * Converts an EPOCH16 value into a readable date/time string.
     *
     * <PRE>
     * Format:            yyyymmdd.ttttttttttttttt
     * Examples:          19900401.365889312341234
     *                    19611231.000000000000000
     * </PRE>
     *
     * This format is the same as that expected by parse1.
     *
     * @param epoch the epoch value
     *
     * @return A string representation of the epoch
     */
    public static String encode1 (Object epoch)
    {
        double[] epoch16 = new double[2];
        epoch16[0] = ((double[]) epoch)[0];
        epoch16[1] = ((double[]) epoch)[1];
        if (epoch16[0] == -1.0E31 && epoch16[1] == -1.0E31) {
          return "99991231.999999999999999";
        }

	return encodex (epoch16, "<year><mm.02><dom.02>.<fod.15>");
    }

    /**
     * 
     * Converts an EPOCH16 value into a readable date/time string.
     *
     * <PRE>
     * Format:          yyyymmddhhmmss
     * Examples:        19900401235959
     *                  19611231000000
     * </PRE>
     *
     * This format is the same as that expected by parse2.
     *
     * @param epoch the epoch value
     *
     * @return A string representation of the epoch
     */
    public static String encode2 (Object epoch)
    {
        double[] epoch16 = new double[2];
        epoch16[0] = ((double[]) epoch)[0];
        epoch16[1] = ((double[]) epoch)[1];
        if (epoch16[0] == -1.0E31 && epoch16[1] == -1.0E31) {
          return "99991231235959";
        }

	return encodex (epoch16, "<year><mm.02><dom.02><hour><min><sec>");
    }

    /**
     * 
     * Converts an EPOCH16 value into a readable date/time string.
     *
     * <PRE>
     * Format:            yyyy-mm-ddThh:mm:ss.ccc.mmm.nnn.pppZ
     * Examples:          1990-04-01T03:05:02.000.000.000.000Z
     *                    1993-10-10T23:45:49.999.999.999.999Z
     * </PRE>
     *
     * This format is the same as that expected by parse3.
     *
     * @param epoch the epoch value
     *
     * @return A string representation of the epoch
     */
    public static String encode3 (Object epoch)
    {
        double[] epoch16 = new double[2];
        epoch16[0] = ((double[]) epoch)[0];
        epoch16[1] = ((double[]) epoch)[1];
        if (epoch16[0] == -1.0E31 && epoch16[1] == -1.0E31) {
          return "9999-12-31T23:59:59.999.999.999.999Z";
        }

	return 
	    encodex (epoch16, 
		     "<year>-<mm.02>-<dom.02>T<hour>:<min>:<sec>.<fos>Z");
    }

    /**
     * 
     * Converts an EPOCH16 value into a readable date/time, ISO8601  string.
     *
     * <PRE>
     * Format:            yyyy-mm-ddThh:mm:ss.cccmmmnnnppp
     * Examples:          1990-04-01T03:05:02.000000000000
     *                    1993-10-10T23:45:49.999999999999
     * </PRE>
     *
     * This format is the same as that expected by parse4.
     *
     * @param epoch the epoch value
     *
     * @return A string representation of the epoch
     */
    public static String encode4 (Object epoch)
    {
        double[] epoch16 = new double[2];
        epoch16[0] = ((double[]) epoch)[0];
        epoch16[1] = ((double[]) epoch)[1];
        if (epoch16[0] == -1.0E31 && epoch16[1] == -1.0E31) {
          return "9999-12-31T23:59:59.999999999999";
        }

	return 
	    encodex (epoch16, 
		     "<year>-<mm.02>-<dom.02>T<hour>:<min>:<sec>.<iso8601>");
    }

    /**
     * 
     * Converts an EPOCH16 value into a readable date/time string using the
     * specified format.  See the C Reference Manual section 8.7 for details
     *
     * @param epoch the epoch value
     * @param formatString a string representing the desired 
     *      format of the epoch
     *
     * @return A string representation of the epoch according to formatString
     */
    public static String encodex(Object epoch, String formatString)
    {
	StringBuffer 
	    encoded = new StringBuffer(), // Fully encoded epString
	    part = new StringBuffer(),    // Part being encoded
	    mod  = new StringBuffer();    // Part modifier.
	int ptr = 0;                // Current position in format string.
	int ptrD;		    // Location of decimal point.
	int ptrE;		    // Location of ending right angle bracket.
	int p;                      // temp position
	long [] components =  	    // EPOCH16 components.
	    new long[10]; //year, month, day, hour, minute, second, msec
			 //usec, nsec, psec
	double[] epoch16 = new double[2];
        double mmm;

	epoch16[0] = ((double[]) epoch)[0];
	epoch16[1] = ((double[]) epoch)[1];

	if (formatString == null || formatString.equals(""))
	    return encode(epoch16);
	
	char [] format = formatString.toCharArray();
	components = breakdown(epoch16);

	// Scan format string.
	for (ptr = 0;ptr<format.length;ptr++) {
	    if (format[ptr] == '<') {

		// If next character is also a `<' (character stuffing), 
		// then append a `<' and move on.
		if (format[ptr+1] == '<') {
		    encoded.append("<");
		    System.out.println("append a literal \"<\"");

		    // char is not a literal '<'
		} else { 

		    // Find ending right angle bracket.
		    ptrE = formatString.indexOf('>',ptr + 1);
		    if (ptrE == -1) {
			encoded.append("?");
			System.out.println("appending ? (1)");
			return encoded.toString();
		    }

		    part.setLength(0);
		    for (p = ptr+1; p != ptrE; p++) 
			part.append(format[p]);
		    
		    // Check for a modifier.
		    ptrD = formatString.indexOf(".",ptr + 1);
		    mod = new StringBuffer();
		    if (ptrD != -1 && ptrD < ptrE) { // Modifier present
			for (p = ptrD+1; p != ptrE; p++) 
			    mod.append(format[p]);
		    }
		    ptr = ptrE;
		    String sPart = part.toString();

		    // Day (of month), <dom>.
		    if (sPart.indexOf("dom") == 0) {
			appendIntegerPart(encoded,components[2],0,
					  false,mod.toString());
		    } 
		    
		    // Day of year, <doy>.
		    else if (sPart.indexOf("doy") == 0) {
			long doy = 
			    JulianDay(components[0],components[1],components[2]) - 
			    JulianDay(components[0],1L,1L) + 1;
			
			appendIntegerPart(encoded, doy, 3,
					  true, mod.toString());
		    }
		    
		    // Month (3-character), <month>.
		    else if (sPart.indexOf("month") == 0) {
			encoded.append(_monthToken[(int)components[1] - 1]);
		    }
		    
		    // Month (digits), <mm>.
		    else if (sPart.indexOf("mm") ==0) {
			appendIntegerPart(encoded,components[1], 0,
					  false, mod.toString());
		    }
		    
		    // Year (full), <year>.
		    else if (sPart.indexOf("year") == 0) {
			appendIntegerPart(encoded, components[0], 4,
					  true,mod.toString());
		    }
		    
		    // Year (2-digit), <yr>.
		    else if (sPart.indexOf("yr") == 0) {
			long yr = components[0] % 100L;
			appendIntegerPart(encoded, yr, 2,
					  true, mod.toString());
		    }
		    
		    // Hour, <hour>.
		    else if (sPart.indexOf("hour") == 0) {
			appendIntegerPart(encoded, components[3], 2,
					  true,mod.toString());
		    }
		    
		    // Minute, <min>.
		    else if (sPart.indexOf("min") == 0) {
			appendIntegerPart(encoded, components[4], 2,
					  true,mod.toString());
		    }
		    
		    // Second, <sec>.
		    else if (sPart.indexOf("sec") == 0) {
			appendIntegerPart(encoded, components[5], 2,
					  true,mod.toString());
		    }
		    
		    // Fraction of second, <fos>.
		    else if (sPart.indexOf("fos") == 0) {
			double fos = ((double) components[6]) / 1000.0;
			appendFractionPart(encoded, fos, 3, mod.toString());
			encoded.append(".");
			appendIntegerPart(encoded, components[7], 3, true, 
					  mod.toString());
			encoded.append(".");
			appendIntegerPart(encoded, components[8], 3, true, 
					  mod.toString());
			encoded.append(".");
			appendIntegerPart(encoded, components[9], 3, true, 
					  mod.toString());
		    }

		    // Fraction of second, <iso8601>.
		    else if (sPart.indexOf("iso8601") == 0) {
			double fos = ((double) components[6]) / 1000.0;
			appendFractionPart(encoded, fos, 3, mod.toString());
			appendIntegerPart(encoded, components[7], 3, true, 
					  mod.toString());
			appendIntegerPart(encoded, components[8], 3, true, 
					  mod.toString());
			appendIntegerPart(encoded, components[9], 3, true, 
					  mod.toString());
		    }
		    
		    // Fraction of day, <fod>.
		    else if (sPart.indexOf("fod") == 0) {
			double fod = 
			    ((double) components[3] / 24.0) +
			    ((double) components[4] / 1440.0) +
			    ((double) components[5] / 86400.0) +
			    ((double) components[6] / 86400000.0) +
			    ((double) components[7] / 86400000000.0) +
			    ((double) components[8] / 86400000000000.0) +
			    ((double) components[9] / 86400000000000000.0);
			appendFractionPart(encoded,fod,15,mod.toString());
		    }
		    
		    // Unknown/unsupported part.
		    else {
			encoded.append("?");
			System.out.println("append ? (2)");
		    }
		}
	    } else {
		if (ptr >= format.length) break;
		encoded.append(format[ptr]);
	    }
	}
	return encoded.toString();
    }

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

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

	format.append(".");

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

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

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

    /**
     * Will append an integer to encoded.
     */
    private static void appendIntegerPart(StringBuffer encoded, 
					  long integer, 
					  int defaultWidth,
					  boolean defaultLeading0, 
					  String modifier)
    {
	StringBuffer ePart = new StringBuffer(MAX_ePART_LEN+1); 
	char [] modArray = modifier.toCharArray();
	int width; 
	boolean leading0;
	NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
	nf.setParseIntegerOnly(true);
	if (!modifier.equals("")) {
	    try {
		width = nf.parse(modifier).intValue();
		if (width < 0) {
		    encoded.append("?");
		    System.out.println("append ? (5)");
		    return;
		}
		leading0 = (modArray[0] == '0');
	    } catch (java.text.ParseException e) {
		encoded.append("?");
		System.out.println("append ? (6)");
		return;
	    }
	} else {
	    width = defaultWidth;
	    leading0 = defaultLeading0;
	}
	ePart.append(""+integer);
	appendPart(encoded, ePart.toString(), width, leading0);
    }
    
    /**
     * Append ePart to encoded.
     *
     * @param encoded The encoded epoch string.
     * @param ePart The part to append to encoded.
     * @param width The string length that the ePart should occupy. A width
     *        of zero indicates that the length of ePart should be used.
     * @param leading0 If true, that pad ePart with leading zeros.
     */
    private static void appendPart (StringBuffer encoded, 
				    String ePart, 
				    int width, 
				    boolean leading0)
    {
	int i;
	if (width == 0) {
	    encoded.append(ePart);
	} else {
	    int length = ePart.length();
	    if (length > width) {
		for (i = 0; i < width; i++) 
		    encoded.append("*");
	    } else {
		int pad = width - length;
		if (pad > 0) {
		    for (i = 0; i < pad; i++) 
			encoded.append((leading0 ? "0" : " "));
		}
		encoded.append(ePart);
	    }
	}
    }

    /**
     * 
     * Computes an EPOCH16 value based on its component parts.
     *
     * @param year the year
     * @param month the month
     * @param day the day
     * @param hour the hour
     * @param minute the minute
     * @param second the second 
     * @param msec the milliseconds
     * @param usec the microseconds
     * @param nsec the nanoseconds
     * @param psec the picoseconds
     * @param epoch returned epoch value
     *
     * @return a double value which indicates whether the operation is 
     *         successful
     * @exception CDFException an ILLEGAL_EPOCH_FIELD if an illegal 
     *    component value is detected.
     */
    public static double compute(long year, 
				 long month, 
				 long day, 
				 long hour,
				 long minute, 
				 long second,
				 long msec,
				 long usec,
				 long nsec,
				 long psec, 
				 Object epoch)
	throws CDFException
    {
	long daysSince0AD, secInDay;
	double[] epoch16 = new double[2];

        if (year == 9999L && month == 12L && day == 31L && hour == 23L &&
            minute == 59L && second == 59L && msec == 999L && usec == 999L &&
            nsec == 999L && psec == 999L) {
          ((double[]) epoch)[0] = -1.0E31;
          ((double[]) epoch)[1] = -1.0E31;
          return 0.0;
	}

	/*
	 * Calculate the days since 0 A.D (1-Jan-0000).  If a value of zero 
	 * is passed in for `month', assume that `day' is the day-of-year
	 * (DOY) with January 1st being day 1.
	 */
	if (year < 0L) 
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
        if ((year > 9999L) || (month < 0L || month > 12L) ||
            (hour < 0L || hour > 23L) || (minute < 0L || minute > 59L) ||
            (second < 0L || second > 59L) || (msec < 0L || msec > 999L) ||
            (usec < 0L || usec > 999L) || (nsec < 0L || nsec > 999L) ||
            (psec < 0L || psec > 999L))
          return computeEpoch16(year, month, day, hour, minute, second, msec, 
                                usec, nsec, psec, epoch);
	if (month == 0L) {
          if (day < 1L || day > 366L)
            return computeEpoch16(year,month,day,hour,minute,second,msec,usec,
                                  nsec, psec, epoch);
	} else {
          if (day < 1L || day > 31L)
            return computeEpoch16(year,month,day,hour,minute,second,msec,usec,
                                  nsec, psec, epoch);
	}
        if (month == 0L) {
          daysSince0AD = JulianDay(year,1L,1L) + (day-1) - 1721060L;
        }
        else {
          daysSince0AD = JulianDay(year,month,day) - 1721060L;
        }
	secInDay = (3600L * hour) + (60L * minute) + second; 
	
	// Return the picoseconds, in two double words, since 0 A.D.
	epoch16[0] = 86400.0 * daysSince0AD + (double) secInDay;
	epoch16[1] = (double) psec + Math.pow(10.0, 3.0) * nsec +
		     Math.pow(10.0, 6.0) * usec + Math.pow(10.0, 9.0) * msec;
	((double[]) epoch)[0] = epoch16[0];
	((double[]) epoch)[1] = epoch16[1];
	return 0.0;
    }

    /**
     * 
     * Breaks an EPOCH16 value down into its component parts.
     *
     * @param epoch the epoch value to break down
     * @return an array containing the epoch parts:
     *  <TABLE SUMMARY="" BORDER="0">
     *    <TR><TD ALIGN="CENTER">Index</TD><TD ALIGN="CENTER">Part</TD></TR>
     *    <TR><TD>0</TD><TD>year</TD></TR>
     *    <TR><TD>1</TD><TD>month</TD></TR>
     *    <TR><TD>2</TD><TD>day</TD></TR>
     *    <TR><TD>3</TD><TD>hour</TD></TR>
     *    <TR><TD>4</TD><TD>minute</TD></TR>
     *    <TR><TD>5</TD><TD>second</TD></TR>
     *    <TR><TD>6</TD><TD>msec</TD></TR>
     *    <TR><TD>7</TD><TD>usec</TD></TR>
     *    <TR><TD>8</TD><TD>nsec</TD></TR>
     *    <TR><TD>9</TD><TD>psec</TD></TR>
     *  </TABLE>
     */
    public static long [] breakdown (Object epoch)
    {
	long [] components = new long[10];
	long jd,i,j,k,l,n;
	double msec, second_AD, minute_AD, hour_AD, day_AD;
	double[] epoch16 = new double[2];
	
	epoch16[0] = ((double[]) epoch)[0];
	epoch16[1] = ((double[]) epoch)[1];

	if (epoch16[0] == -1.0E31 && epoch16[1] == -1.0E31) {
	  components[0] = 9999;
	  components[1] = 12;
	  components[2] = 31;
	  components[3] = 23;
	  components[4] = 59;
	  components[5] = 59;
	  components[6] = 999;
	  components[7] = 999;
	  components[8] = 999;
	  components[9] = 999;
          return components;
	}

	if (epoch16[0] < 0.0) epoch16[0] = -epoch16[0];
        if (epoch16[1] < 0.0) epoch16[1] = -epoch16[1];
        epoch16[0] = (MAX_EPOCH16_1_BINARY < epoch16[0] ?
                                         MAX_EPOCH16_1_BINARY :
                                         epoch16[0]);
        if (epoch16[0] == MAX_EPOCH16_1_BINARY)
          epoch16[1] = (MAX_EPOCH16_2_BINARY < epoch16[1] ?
                                         MAX_EPOCH16_2_BINARY :
                                         epoch16[1]);
        else
          epoch16[1] = ((MAX_EPOCH16_2_BINARY+1.0) < epoch16[1] ?
                                         (MAX_EPOCH16_2_BINARY+1.0) :
                                         epoch16[1]);

	second_AD = epoch16[0];
	minute_AD = second_AD / 60.0;
	hour_AD = minute_AD / 60.0;
	day_AD = hour_AD / 24.0;
	
	jd = 1721060 + (long) day_AD;
	l=jd+68569;
	n=4*l/146097;
	l=l-(146097*n+3)/4;
	i=4000*(l+1)/1461001;
	l=l-1461*i/4+31;
	j=80*l/2447;
	k=l-2447*j/80;
	l=j/11;
	j=j+2-12*l;
	i=100*(n-49)+i+l;
	
	components[0] = i;
	components[1] = j;
	components[2] = k;
	
	components[3] = (long) (hour_AD   % (double) 24.0);
	components[4] = (long) (minute_AD % (double) 60.0);
	components[5] = (long) (second_AD % (double) 60.0);

	msec = epoch16[1];
        components[9] = (long) (msec % (double) 1000.0);
	msec = msec / 1000.0;
	components[8] = (long) (msec % (double) 1000.0);
        msec = msec / 1000.0;
	components[7] = (long) (msec % (double) 1000.0);
        msec = msec / 1000.0; 
	components[6] = (long) msec;
	
	return components;
    }
    
    /**
     * JulianDay.
     * The year, month, and day are assumed to have already been validated.
     * This is the day since 0 AD/1 BC.  (Julian day may not be the
     * proper term.)
     */
    private static long JulianDay (long y, long m, long d)
    {
	return (367*y -
		7*(y+(m+9)/12)/4 -
		3*((y+(m-9)/7)/100+1)/4 +
		275*m/9 + 
		d +
		1721029);
    }

    /**
     * 
     * Convert the fraction of a second in the EPOCH16 data type to a string.
     */
    private static String encode_2 (double epoch)
    {
	long msec, usec, nsec, psec;
	double mmm;
	StringBuffer encoded = new StringBuffer(),
		     fracString = new StringBuffer();
	psec = (long) (epoch % (double) 1000.0);
	mmm = epoch / (double) 1000.0;
	nsec = (long) (mmm % (double) 1000.0);
	mmm = mmm / (double) 1000.0;
	usec = (long) (mmm % (double) 1000.0);
	msec = (long) (mmm / (double) 1000.0);
        appendIntegerPart(encoded, msec, 3, true, "");
	fracString.append(encoded).append(".");
	encoded.setLength(0);
	appendIntegerPart(encoded, usec, 3, true, "");
	fracString.append(encoded).append(".");
	encoded.setLength(0);
	appendIntegerPart(encoded, nsec, 3, true, "");
	fracString.append(encoded).append(".");
	encoded.setLength(0);
	appendIntegerPart(encoded, psec, 3, true, "");
	fracString.append(encoded); 
	return fracString.toString();
    }

    /**
     * computeEpoch16.
     * This is the seconds and picoseconds since 0 AD/1 BC.
     */
    private static double computeEpoch16 (long y, long m, long d, long h,
                                          long mn, long s, long ms, long msu,
                                          long msn, long msp, Object epoch)
                                          throws CDFException
    {
        long daysSince0AD;
        if (m == 0L) {
          daysSince0AD = JulianDay(y,1L,1L) + (d-1L) - 1721060L;
        }
        else {
          if (m < 0L) {
            --y;
            m = 13 + m;
          }
          daysSince0AD = JulianDay(y,m,d) - 1721060L;
        }
        if (daysSince0AD < 0L)
          throw new CDFException(ILLEGAL_EPOCH_FIELD);
        ((double[]) epoch)[0] = 86400.0 * daysSince0AD + 3600.0 * h + 60.0 *
                                mn + (double) s;
        ((double[]) epoch)[1] = (double) msp + Math.pow(10.0, 3.0) * msn +
                   Math.pow(10.0, 6.0) * msu + Math.pow(10.0, 9.0) * ms;
        if (((double[]) epoch)[1] < 0.0 || 
            ((double[]) epoch)[1] >= Math.pow(10.0, 12.0)) {
          int sec;
          double tmp;
          if (((double[]) epoch)[1] < 0.0) {
            sec = (int) (((double[]) epoch)[1] / Math.pow(10.0, 12.0));
            tmp = ((double[]) epoch)[1] - sec * Math.pow(10.0, 12.0);
            if (tmp != 0.0 && tmp != -0.0) {
              ((double[]) epoch)[0] = ((double[]) epoch)[0] + sec - 1;
              ((double[]) epoch)[1] = Math.pow(10.0, 12.0) + tmp;
            } else {
              ((double[]) epoch)[0] = ((double[]) epoch)[0] + sec;
              ((double[]) epoch)[1] = 0.0;
            }
          } else {
            sec = (int) (((double[]) epoch)[1] / Math.pow(10.0, 12.0));
            tmp = ((double[]) epoch)[1] - sec * Math.pow(10.0, 12.0);
            if (tmp != 0.0 && tmp != -0.0) {
              ((double[]) epoch)[1] = tmp;
              ((double[]) epoch)[0] = ((double[]) epoch)[0] + sec;
            } else {
              ((double[]) epoch)[1] = 0.0;
              ((double[]) epoch)[0] = ((double[]) epoch)[0] + sec;
            }
          }
        }
        if (((double[]) epoch)[0] < 0.0)
          return ILLEGAL_EPOCH_VALUE;
        else
          return 0.0;
    }

}
