// $Id: Epoch.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_EPOCH data type.
 *
 * @version 1.0
 *
 * <B>Example:</B>
 * <PRE> 
 * // Get the milliseconds to Aug 5, 1990 at 5:00
 * double ep = Epoch.compute(1990, 8, 5, 5, 0, 0, 0);
 * //Get the year, month, day, hour, minutes, seconds, milliseconds for ep
 * long[] times = Epoch.breakdown(ep);
 * 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(Epoch.encode(ep));
 * System.out.println(Epoch.encode1(ep));
 * System.out.println(Epoch.encode2(ep));
 * System.out.println(Epoch.encode3(ep));
 * System.out.println(Epoch.encode4(ep));
 * // 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(Epoch.encodex(ep,format));
 * </PRE>
 */
public class Epoch implements gsfc.nssdc.cdf.CDFConstants {

    private static double MAX_EPOCH_BINARY = 3.15569519999998e14;

    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 an array of date/time strings and returns an array 
     * of EPOCH values.  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
     * Examples:  1-Apr-1990 03:05:02.000
     *           10-Oct-1993 23:45:49.999
     *
     * Style 1 - 
     * Format:   yyyymmdd.ttttttt
     * Examples: 19900401.3658893
     *           19931010.0000000
     *
     * Style 2 - 
     * Format:   yyyymmddhhmmss
     * Examples: 19900401030502
     *           19931010234549
     *
     * Style 3 - 
     * Format:   yyyy-mm-ddThh:mm:ss.mmmZ
     * Examples: 1990-04-01T03:05:02.000Z
     *           1993-10-10T23:45:49.999Z
     *
     * Style 4 - 
     * Format:   yyyy-mm-ddThh:mm:ss.mmm
     * Examples: 1990-04-01T03:05:02.000
     *           1993-10-10T23:45:49.999
     *
     * </PRE>
     *
     * @param inStrings the epochs in string representation
     *
     * @return the values of the epoch represented by inStrings or null if null
     *         or an empty input array is detected
     *
     * @exception CDFException if a bad epoch value is passed in inStrings
     */
    public static double[] toParse (String[] inStrings)
        throws CDFException
    {
        if (inStrings == null) return null;
        double[] dateTimes = new double[inStrings.length];
        int ix;
        for (ix = 0; ix < inStrings.length; ++ix)
          dateTimes[ix] = toParse (inStrings[ix]);
        return dateTimes;
    }

    /**
     * 
     * This function parses a date/time string and returns an EPOCH 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
     * Examples:  1-Apr-1990 03:05:02.000
     *           10-Oct-1993 23:45:49.999
     *
     * Style 1 - 
     * Format:   yyyymmdd.ttttttt
     * Examples: 19900401.3658893
     *           19931010.0000000
     *
     * Style 2 - 
     * Format:   yyyymmddhhmmss
     * Examples: 19900401030502
     *           19931010234549
     *
     * Style 3 - 
     * Format:   yyyy-mm-ddThh:mm:ss.mmmZ
     * Examples: 1990-04-01T03:05:02.000Z
     *           1993-10-10T23:45:49.999Z
     *
     * Style 4 - 
     * Format:   yyyy-mm-ddThh:mm:ss.mmm
     * Examples: 1990-04-01T03:05:02.000
     *           1993-10-10T23:45:49.999
     *
     * </PRE>
     *
     * @param inString the epoch in string representation
     *
     * @return the value of the epoch represented by inStrings or null if null
     *         or an empty input array 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 ILLEGAL_EPOCH_VALUE;
        inString = inString.trim();
        int len = inString.length();
        char aC;
        if (len == EPOCH_STRING_LEN) { // style 0 or style 3
          if (inString.charAt(11) == ' ') return parse(inString);
          aC = inString.charAt(10);
          if (aC == 'T' || aC == 't' || aC == ' ' || aC == '/')
            return parse3(inString);
          else return ILLEGAL_EPOCH_VALUE;
        } else if (len == EPOCH4_STRING_LEN) { // style 4
          aC = inString.charAt(10);
          if (aC == 'T' || aC == 't' || aC == ' ' || aC == '/')
            return parse4(inString);
          else return ILLEGAL_EPOCH_VALUE;
        } else if (len <= EPOCH1_STRING_LEN && inString.charAt(8) == '.' &&
                   inString.matches("\\d+.\\d+")) { // style 1
          return parse1(inString);
        } else if (len == EPOCH2_STRING_LEN &&
                   inString.matches("\\d+")) { // style 2
          return parse2(inString);
        } else if (inString.charAt(11) == ' ') {
          return 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 parse3(inString);
            else
              return parse4(inString);
        } else
          return ILLEGAL_EPOCH_VALUE;
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH
     * value.  The string 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.mmm
     * Examples:  1-Apr-1990 03:05:02.000
     *           10-Oct-1993 23:45:49.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 double parse (String inString)
	throws CDFException
    {
	long 
	    year   = 0, 
	    month  = 0, 
	    day    = 0, 
	    hour   = 0, 
	    minute = 0, 
	    second = 0, 
	    msec   = 0;
	String monthStr, secondStr;
        inString = inString.trim();
        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, ".");
	    second = Long.parseLong(st.nextToken());
            if (st.hasMoreTokens())
 	      msec   = Long.parseLong(st.nextToken());
	    return compute(year, month, day, hour, minute, second, msec);
	} catch (Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * This function parses an array of date/time strings and returns an array 
     * of EPOCH values.  The string 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.mmm
     * Examples:  1-Apr-1990 03:05:02.000
     *           10-Oct-1993 23:45:49.999
     * </PRE>
     *
     * The expected format is the same as that produced by encode.
     *
     * @param inStrings the epochs in string representation
     *
     * @return the values of the epoch represented by inStrings or null if null
     *         or an empty input array is detected
     *
     * @exception CDFException if a bad epoch value is passed in inStrings
     */
    public static double[] parse (String[] inStrings)
	throws CDFException
    {
	if (inStrings == null) return null;
	int len = inStrings.length;
	double[] epochs = new double[len];
	for (int i = 0; i < len; ++i)
	  epochs[i] = parse (inStrings[i]);
	return epochs;
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH
     * value.  The format must be exactly as shown below.  Note that if 
     * there are less than 7 digits after the decimal point, zeros (0's)
     * are assumed for the missing digits.
     *
     * <PRE>
     * Format:    yyyymmdd.ttttttt
     * Examples:  19950508.0000000
     *            19671231.58      (== 19671213.5800000)
     * </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 double parse1 (String inString)
	throws CDFException
    {
        inString = inString.trim();
	StringTokenizer st = new StringTokenizer(inString, ".");
        try {
	  String date = st.nextToken();
	  String time = st.nextToken();
	  long year = 0, month = 0, day = 0, hour = 0, minute = 0, 
	       second = 0, msec = 0, fractionL = 0;
	  double fraction = 0.0;

	    year   = Long.parseLong(date.substring(0,4));
	    month  = Long.parseLong(date.substring(4,6));
	    day    = Long.parseLong(date.substring(6));
            if (time.length() < 7) {
              StringBuffer tmp = new StringBuffer(time);
              for (int i = 0; i < (7 - time.length()); ++i)
                tmp = tmp.append('0');
              time = tmp.toString();
            }
	    fractionL = Long.parseLong(time);
	    
	    fraction = ((double) fractionL) / 10000000.0;
	    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 * 86400000.0);
	    return compute(year, month, day, hour, minute, second, msec);
	} catch (Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * This function parses an array of date/time strings and returns an EPOCH
     * value array.  The format must be exactly as shown below.  Note that if 
     * there are less than 7 digits after the decimal point, zeros (0's)
     * are assumed for the missing digits.
     *
     * <PRE>
     * Format:    yyyymmdd.ttttttt
     * Examples:  19950508.0000000
     *            19671231.58      (== 19671213.5800000)
     * </PRE>
     *
     * The expected format is the same as that produced by encode1.
     *
     * @param inStrings the epoch array in string representation
     * @return the value array of the epoch represented by inString
     *
     * @exception  CDFException if a bad epoch value is passed in inString
     */
    public static double[] parse1 (String[] inStrings)
        throws CDFException
    {
        if (inStrings == null) return null;
        int len = inStrings.length;
        double[] epochs = new double[len];
        for (int i = 0; i < len; ++i)
          epochs[i] = parse1 (inStrings[i]);
        return epochs;
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH
     * 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 double parse2 (String inString)
	throws CDFException
    {
	long year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
        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));
	    return compute(year, month, day, hour, minute, second, 0L);
	} catch (Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }
    
    /**
     * 
     * This function parses an array of date/time strings and returns an EPOCH
     * value array.  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 inStrings the epoch array in string representation
     * @return the value array of the epoch represented by inString
     *
     * @exception CDFException if a bad epoch value is passed in inString
     */
    public static double[] parse2 (String[] inStrings)
        throws CDFException
    {
        if (inStrings == null) return null;
        int len = inStrings.length;
        double[] epochs = new double[len];
        for (int i = 0; i < len; ++i)
          epochs[i] = parse2(inStrings[i]);
        return epochs;
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH
     * value.  The format must be exactly as shown below.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.cccZ
     * Examples:  1990-04-01T03:05:02.000Z
     *            1993-10-10T23:45:49.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 double parse3 (String inString)
	throws CDFException
    {
	long year = 0, month = 0, day = 0, hour = 0, 
	     minute = 0, second = 0, msec = 0;

	String secondStr;
        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 = tt.substring(0,tt.length() - 1);

	    // 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());
	    return compute(year, month, day, hour, minute, second, msec);
	} catch (java.lang.Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * This function parses an array of date/time strings and returns an EPOCH
     * value array.  The format must be exactly as shown below.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.cccZ
     * Examples:  1990-04-01T03:05:02.000Z
     *            1993-10-10T23:45:49.999Z
     * </PRE>
     *
     * The expected format is the same as that produced by encode3.
     *
     * @param inStrings the epoch array in string representation
     * @return the value array of the epoch represented by inString
     *
     * @exception CDFException if a bad epoch value is passed in inString
     */
    public static double[] parse3 (String[] inStrings)
        throws CDFException
    {
        if (inStrings == null) return null;
        int len = inStrings.length;
        double[] epochs = new double[len];
        for (int i = 0; i < len; ++i)
          epochs[i] = parse3 (inStrings[i]);
        return epochs;
    }

    /**
     * 
     * This function parses an input date/time string and returns an EPOCH
     * value.  The format must be an ISO8601 and is exactly as shown below.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.ccc
     * Examples:  1990-04-01T03:05:02.000
     *            1993-10-10T23:45:49.999
     * </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 double parse4 (String inString)
	throws CDFException
    {
	long year = 0, month = 0, day = 0, hour = 0, 
	     minute = 0, second = 0, msec = 0;

	String secondStr;
        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 time = tt.substring(0,tt.length());

	    // 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());

	    return compute(year, month, day, hour, minute, second, msec);
	} catch (java.lang.Exception e) {
	    throw new CDFException(ILLEGAL_EPOCH_FIELD);
	}
    }

    /**
     * 
     * This function parses an array of date/time strings and returns an EPOCH
     * value array.  The format must be an ISO8601 and is exactly as shown below.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.ccc
     * Examples:  1990-04-01T03:05:02.000
     *            1993-10-10T23:45:49.999
     * </PRE>
     *
     * The expected format is the same as that produced by encode3.
     *
     * @param inStrings the epoch array in string representation
     * @return the value array of the epoch represented by inString
     *
     * @exception CDFException if a bad epoch value is passed in inString
     */
    public static double[] parse4 (String[] inStrings)
        throws CDFException
    {
        if (inStrings == null) return null;
        int len = inStrings.length;
        double[] epochs = new double[len];
        for (int i = 0; i < len; ++i)
          epochs[i] = parse4 (inStrings[i]);
        return epochs;
    }

    /**
     * 
     * Converts an EPOCH 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
     * Examples:  01-Apr-1990 03:05:02.000
     *            10-Oct-1993 23:45:49.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.cccZ
     * Examples:  19900401T03:05:02.000Z
     *            19931010T23:45:49.999Z
     *
     * For style 4: 
     * Format:    yyyymmddThh:mm:ss.ccc
     * Examples:  19900401T03:05:02.000
     *            19931010T23:45:49.999
     * 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 array of EPOCH values into readable date/time strings.
     * 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
     * Examples:  01-Apr-1990 03:05:02.000
     *            10-Oct-1993 23:45:49.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.cccZ
     * Examples:  19900401T03:05:02.000Z
     *            19931010T23:45:49.999Z
     *
     * For style 4: 
     * Format:    yyyymmddThh:mm:ss.ccc
     * Examples:  19900401T03:05:02.000
     *            19931010T23:45:49.999
     *
     * </PRE>
     *
     * @param epochs the epoch values
     * @param style the encoding style
     *
     * @return An array of strings representation of the epochs
     *         If the style is invalid, it will return the string
     *         "99991231T23:59:59.999"
     */
    public static String[] toEncode (double[] epochs, int style)
    {
        if (epochs == null) return null;
        int len = epochs.length;
        String[] strings = new String[len];
        for (int i = 0; i < len; ++i)
          strings[i] = toEncode (epochs[i], style);
        return strings;
    }

    /**
     * 
     * Converts an EPOCH object into a scalar or array(s) of readable
     * date/time string(s).
     * Each string is presented in the following ISO 8601 form: 
     *
     * <PRE>
     *
     * Format:    yyyymmddThh:mm:ss.ccc
     * Examples:  19900401T03:05:02.000
     *            19931010T23:45:49.999
     *
     * </PRE>
     *
     * @param epoch the epoch object
     *
     * @return A string or array(s) of strings representation of the epoch
     */
    public static Object toEncode (Object epoch)
    {
        if (epoch == null) return null;
        int nDims = CDFUtils.getNumDimensions(epoch);
        if (nDims > 1) {
          Object encodedArray = null;
          String className = "java.lang.String";

          long[] dimSizes = CDFUtils.getDimensionSizes(epoch);
          int[] tempSizes = new int[nDims];
          for (int j = 0; j < nDims; ++j) tempSizes[j] = (int) dimSizes[j];
          Class arrayClass;
          try {
            arrayClass = Class.forName(className);
            encodedArray = Array.newInstance(arrayClass, tempSizes);
          } catch (ClassNotFoundException e) {
            System.out.println("**** error BAD_ARGUMENT");
            System.exit(0);
          }

          int arrayLength = ((Object[])epoch).length;
          int i;
          Object subObj;
          for (i = 0; i < arrayLength; ++i) {
            ((Object[])encodedArray)[i] = toEncode (((Object[])epoch)[i]);
          }
          return encodedArray;
        } else {
          String sig = CDFUtils.getSignature (epoch);
          if (sig.charAt(0) != '[') { /* scalar */
            return toEncode((Double)epoch, 4);
          } else { /* 1-D */
            return toEncode((double[])epoch, 4);
          }
        }
    }

    /**
     * 
     * Converts an EPOCH value into a readable date/time string.
     * It present the string to the following ISO 8601 form: 
     *
     * <PRE>
     *
     * Format:    yyyymmddThh:mm:ss.ccc
     * Examples:  19900401T03:05:02.000
     *            19931010T23:45:49.999
     *
     * </PRE>
     *
     * @param epoch the epoch value
     *
     * @return A string representation of the epoch
     */
    public static String toEncode (double epoch)
    {
        return toEncode(epoch, 4);
    }

    /**
     * 
     * Converts an array of EPOCH values into readable date/time strings.
     * The strings are of the following ISO 8601 form: 
     *
     * <PRE>
     *
     * Format:    yyyymmddThh:mm:ss.ccc
     * Examples:  19900401T03:05:02.000
     *            19931010T23:45:49.999
     *
     * </PRE>
     *
     * @param epochs the epoch values
     *
     * @return A array of strings representation of the epochs
     */
    public static String[] toEncode (double[] epochs)
    {
        return toEncode(epochs, 4);
    }

    /**
     * 
     * Converts an EPOCH value to a unix time of a double value (seconds from 
     * 1970-01-01 00:00:00 UTC). Resolution to milliseconds will be made in 
     * unix time at its fraction part.
     *
     * @param epoch the epoch value
     *
     * @return A unix time value
     */
    public static double EpochtoUnixTime (double epoch)
    {
        try {
	  return (epoch - BeginUnixTimeEPOCH)/1000.0;
        } catch (Exception ex) {
          return 0.0;
        }
    }

    /**
     * 
     * Converts an array of EPOCH values to an array of unix time of a double
     * value (seconds from 1970-01-01 00:00:00 UTC). Resolution to milliseconds 
     * will be made in unix time at its fraction part.
     *
     * @param epoch the epoch values
     *
     * @return Array of unix time values
     */
    public static double[] EpochtoUnixTime (double[] epoch)
    {
        double[] unixTimes = new double[epoch.length];
        for (int i = 0; i < epoch.length; ++i)
          unixTimes[i] = (epoch[i] - BeginUnixTimeEPOCH)/1000.0;
	return unixTimes;
    }

    /**
     * 
     * Converts a Unix time value (seconds from 1970-01-01 00:00:00 UTC) to
     * an EPOCH time. The Unix time can have a time resoultion of milliseconds
     * (0-999) or microseconds (0-999999) at its fraction part. Only resolution
     * to milliseconds in unix time will be used.
     *
     * @param unixTime the unix time value
     *
     * @return An epoch value
     */
    public static double UnixTimetoEpoch (double unixTime)
    {
        try {
          long seconds, milsecs; double subsecs;
          seconds = (long) unixTime;
          subsecs = (unixTime - seconds) * 1000.0;
          milsecs = (long) subsecs;
          if ((subsecs - milsecs) > 0.5) milsecs += 1;
	  return (seconds*1000.0 + milsecs + BeginUnixTimeEPOCH);
        } catch (Exception ex) {
          return 0.0;
        }
    }

    /**
     * 
     * Converts an array of Unix time values (seconds from 1970-01-01
     * 00:00:00 UTC) to an array of EPOCH times. The Unix time can have a time
     * resoultion of milliseconds (0-999) or microseconds (0-999999) at its 
     * fraction part. Only resolution to milliseconds in unix time will be used.
     *
     * @param unixTimes the unix time values
     *
     * @return An array of epoch values
     */
    public static double[] UnixTimetoEpoch (double[] unixTimes)
    {
        double[] epoch = new double[unixTimes.length];
        for (int i = 0; i < unixTimes.length; ++i)
          epoch[i] = UnixTimetoEpoch (unixTimes[i]);
        return epoch;
    }

    /**
     * 
     * Converts an EPOCH value into a readable date/time string.
     *
     * <PRE>
     * Format:    dd-mmm-yyyy hh:mm:ss.ccc
     * Examples:  01-Apr-1990 03:05:02.000
     *            10-Oct-1993 23:45:49.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 (double epoch)
    {
        if (epoch == -1.0E31 || epoch == ILLEGAL_EPOCH_VALUE) {
          return "31-Dec-9999 23:59:59.999";
        }

	return encodex (epoch, 
			"<dom.02>-<month>-<year> <hour>:<min>:<sec>.<fos>");
    }

    /**
     * 
     * Converts an array of EPOCH values into an array of readable date/time
     * strings.
     *
     * <PRE>
     * Format:    dd-mmm-yyyy hh:mm:ss.ccc
     * Examples:  01-Apr-1990 03:05:02.000
     *            10-Oct-1993 23:45:49.999
     * </PRE>
     *
     * This format is the same as that expected by parse.
     *
     * @param epochs the epoch values
     *
     * @return Strings representing the epochs or null if null or an empty
     *         input array is detected
     */
    public static String[] encode (double[] epochs)
    {
	if (epochs == null) return null;
	int len = epochs.length;
        if (len <= 0) return null;
	String[] strings = new String[len];
	for (int i = 0; i < len; ++i)
          strings[i] = Epoch.encode (epochs[i]);
	return strings;
    }

    /**
     * 
     * Converts an EPOCH value into a readable date/time string.
     *
     * <PRE>
     * Format:    yyyymmdd.ttttttt
     * Examples:  19900401.3658893
     *            19611231.0000000
     * </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 (double epoch)
    {
        if (epoch == -1.0E31 || epoch == ILLEGAL_EPOCH_VALUE) {
          return "99991231.9999999";
        }
        
	return encodex (epoch, "<year><mm.02><dom.02>.<fod.7>");
    }

    /**
     * 
     * Converts an array EPOCH values into readable date/time strings.
     *
     * <PRE>
     * Format:    yyyymmdd.ttttttt
     * Examples:  19900401.3658893
     *            19611231.0000000
     * </PRE>
     *
     * This format is the same as that expected by parse1.
     *
     * @param epochs the epoch values
     *
     * @return Array of strings representation of the epochs
     */
    public static String[] encode1 (double[] epochs)
    {
        if (epochs == null) return null;
        int len = epochs.length;
        String[] strings = new String[len];
        for (int i = 0; i < len; ++i)
          strings[i] = encode1 (epochs[i]);
        return strings;
    }

    /**
     * 
     * Converts an EPOCH 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 (double epoch)
    {
        if (epoch == -1.0E31 || epoch == ILLEGAL_EPOCH_VALUE) {
          return "99991231235959";
        }
        
	return encodex (epoch, "<year><mm.02><dom.02><hour><min><sec>");
    }

    /**
     * 
     * Converts an array of EPOCH values into readable date/time strings.
     *
     * <PRE>
     * Format:     yyyymmddhhmmss
     * Examples:   19900401235959
     *             19611231000000
     * </PRE>
     *
     * This format is the same as that expected by parse2.
     *
     * @param epochs the epoch values
     *
     * @return A array of strings representation of the epochs
     */
    public static String[] encode2 (double[] epochs)
    {
        if (epochs == null) return null;
        int len = epochs.length;
        String[] strings = new String[len];
        for (int i = 0; i < len; ++i)
          strings[i] = encode2 (epochs[i]);
        return strings;
    }

    /**
     * 
     * Converts an EPOCH value into a readable date/time string.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.cccZ
     * Examples:  1990-04-01T03:05:02.000Z
     *            1993-10-10T23:45:49.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 (double epoch)
    {
        if (epoch == -1.0E31 || epoch == ILLEGAL_EPOCH_VALUE) {
          return "9999-12-31T23:59:59.999Z";
        }
        
	return 
	    encodex (epoch, 
		     "<year>-<mm.02>-<dom.02>T<hour>:<min>:<sec>.<fos>Z");
    }

    /**
     * 
     * Converts an array of EPOCH values into readable date/time strings.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.cccZ
     * Examples:  1990-04-01T03:05:02.000Z
     *            1993-10-10T23:45:49.999Z
     * </PRE>
     *
     * This format is the same as that expected by parse3.
     *
     * @param epochs the epoch values
     *
     * @return A array of strings representation of the epochs
     */
    public static String[] encode3 (double[] epochs)
    {
        if (epochs == null) return null;
        int len = epochs.length;
        String[] strings = new String[len];
        for (int i = 0; i < len; ++i)
          strings[i] = encode3 (epochs[i]);
        return strings;
    }

    /**
     * 
     * Converts an EPOCH value into a readable date/time, ISO8601 string.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.ccc
     * Examples:  1990-04-01T03:05:02.000
     *            1993-10-10T23:45:49.999
     * </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 encode4 (double epoch)
    {
        if (epoch == -1.0E31 || epoch == ILLEGAL_EPOCH_VALUE) {
          return "9999-12-31T23:59:59.999";
        }
        
	return 
	    encodex (epoch, 
		     "<year>-<mm.02>-<dom.02>T<hour>:<min>:<sec>.<fos>");
    }

    /**
     * 
     * Converts an array of EPOCH values into readable date/time, ISO8601
     * strings.
     *
     * <PRE>
     * Format:    yyyy-mm-ddThh:mm:ss.ccc
     * Examples:  1990-04-01T03:05:02.000
     *            1993-10-10T23:45:49.999
     * </PRE>
     *
     * This format is the same as that expected by parse3.
     *
     * @param epochs the epoch values
     *
     * @return A array of strings representation of the epochs
     */
    public static String[] encode4 (double[] epochs)
    {
        if (epochs == null) return null;
        int len = epochs.length;
        String[] strings = new String[len];
        for (int i = 0; i < len; ++i)
          strings[i] = encode4 (epochs[i]);
        return strings;
    }

    /**
     * 
     * Converts an EPOCH 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(double 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 =  	    // EPOCH components.
	    new long[7]; //year, month, day, hour, minute, second, msec;

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

	// 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());
		    }
		    
		    // 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);
			appendFractionPart(encoded,fod,8,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 EPOCH 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 millisecond
     *
     * @return the epoch value
     *
     * @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)
	throws CDFException
    {
	long daysSince0AD, msecInDay;

	if (year == 9999L && month == 12L && day == 31L && hour == 23L &&
            minute == 59L && second == 59L && msec == 999L) 
          return -1.0E31;
	/*
	 * 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))
          return computeEpoch(year,month,day,hour,minute,second,msec);
        if (month == 0L) {
          if (day < 1L || day > 366L)
            return computeEpoch(year,month,day,hour,minute,second,msec);
        } else {
          if (day < 1L || day > 31L)
            return computeEpoch(year,month,day,hour,minute,second,msec);
        }
        if (hour == 0L && minute == 0L && second == 0L) {
          if (msec < 0L || msec > 86399999L)
            return computeEpoch(year,month,day,hour,minute,second,msec);
        }
	if (month == 0) {
	    daysSince0AD = JulianDay(year,1L,1L) + (day-1) - 1721060L;
	} else {
	    daysSince0AD = JulianDay(year,month,day) - 1721060L;
	}
	/*
	 * Calculate the millisecond in the day (with the first millisecond
	 * being 0). If values of zero are passed in for `hour', `minute',
	 * and `second', assume that `msec' is the millisecond in the day.
	 */
	if (hour == 0 && minute == 0 && second == 0) {
	    msecInDay = msec;
	} else {
	    msecInDay = (3600000L * hour) + (60000L * minute) + 
             		(1000L * second) + msec;
	}
	
	// Return the milliseconds since 0 A.D.
	return (86400000.0 * daysSince0AD + (double) msecInDay);
    }

    /**
     * 
     * Computes an array of EPOCH values based on their component parts.
     *
     * @param year the year array
     * @param month the month array
     * @param day the day array
     *
     * @return an array of epoch values or null if an invalid input array(s)
     *         is detected.
     *
     * @exception CDFException an ILLEGAL_EPOCH_FIELD if an illegal 
     *    component value is detected.
     */
    public static double[] compute(long[] year, 
				   long[] month, 
				   long[] day) 
	throws CDFException
    {
	if (year == null || month == null || day == null) return null;
	int len = year.length;
	if (len <= 0) return null;
        if (len != month.length || len != day.length) return null;
	double[] epochs = new double[len];
	for (int i = 0; i < len; ++i)
	  epochs[i] = Epoch.compute (year[i], month[i], day[i], 0L,
				     0L, 0L, 0L);
	return epochs;
    }

    /**
     * 
     * Computes an array of EPOCH values based on their component parts.
     *
     * @param year the year array
     * @param month the month array
     * @param day the day array
     * @param hour the hour array
     *
     * @return an array of epoch values  or null if an invalid input array(s)
     *         is detected
     *
     * @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)
	throws CDFException
    {
	if (year == null || month == null || day == null ||
	    hour == null) return null;
	int len = year.length;
	if (len <= 0) return null;
        if (len != month.length || len != day.length ||
	    len != hour.length) return null;
	double[] epochs = new double[len];
	for (int i = 0; i < len; ++i)
	  epochs[i] = Epoch.compute (year[i], month[i], day[i], hour[i],
				     0L, 0L, 0L);
	return epochs;
    }

    /**
     * 
     * Computes an array of EPOCH values based on their component parts.
     *
     * @param year the year array
     * @param month the month array
     * @param day the day array
     * @param hour the hour array
     * @param minute the minute array
     *
     * @return an array of epoch values  or null if an invalid input array(s)
     *         is detected
     *
     * @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)
	throws CDFException
    {
	if (year == null || month == null || day == null ||
	    hour == null || minute == null) return null;
	int len = year.length;
	if (len <= 0) return null;
        if (len != month.length || len != day.length ||
	    len != hour.length || len != minute.length) return null;
	double[] epochs = new double[len];
	for (int i = 0; i < len; ++i)
	  epochs[i] = Epoch.compute (year[i], month[i], day[i], hour[i],
				     minute[i], 0L, 0L);
	return epochs;
    }

    /**
     * 
     * Computes an array of EPOCH values based on their component parts.
     *
     * @param year the year array
     * @param month the month array
     * @param day the day array
     * @param hour the hour array
     * @param minute the minute array
     * @param second the second  array
     *
     * @return an array of epoch values  or null if an invalid input array(s)
     *         is detected
     *
     * @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)
	throws CDFException
    {
	if (year == null || month == null || day == null ||
	    hour == null || minute == null || second == null) return null;
	int len = year.length;
	if (len <= 0) return null;
        if (len != month.length || len != day.length ||
	    len != hour.length || len != minute.length ||
	    len != second.length) return null;
	double[] epochs = new double[len];
	for (int i = 0; i < len; ++i)
	  epochs[i] = Epoch.compute (year[i], month[i], day[i], hour[i],
				     minute[i], second[i], 0L);
	return epochs;
    }

    /**
     * 
     * Computes an array of EPOCH values based on their component parts.
     *
     * @param year the year array
     * @param month the month array
     * @param day the day array
     * @param hour the hour array
     * @param minute the minute array
     * @param second the second array
     * @param msec the millisecond array
     *
     * @return an array of epoch values  or null if an invalid input array(s)
     *         is detected
     *
     * @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)
	throws CDFException
    {
	if (year == null || month == null || day == null ||
	    hour == null || minute == null || second == null ||
	    msec == null) return null;
	int len = year.length;
	if (len <= 0) return null;
        if (len != month.length || len != day.length ||
	    len != hour.length || len != minute.length ||
	    len != second.length || len != msec.length) return null;
	double[] epochs = new double[len];
	for (int i = 0; i < len; ++i)
	  epochs[i] = Epoch.compute (year[i], month[i], day[i], hour[i],
				     minute[i], second[i], msec[i]);
	return epochs;
    }

    /**
     * 
     * Breaks an EPOCH 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>
     *  </TABLE>
     */
    public static long [] breakdown (double epoch)
    {
	long [] components = new long[7];
	long jd,i,j,k,l,n;
	double msec_AD, second_AD, minute_AD, hour_AD, day_AD;
	
	if (epoch == -1.0E31) {
	  components[0] = 9999;
	  components[1] = 12;
	  components[2] = 31;
	  components[3] = 23;
	  components[4] = 59;
	  components[5] = 59;
	  components[6] = 999;
          return components;
	}

	if (epoch < 0.0) epoch = -epoch;
	epoch = (MAX_EPOCH_BINARY < epoch ? MAX_EPOCH_BINARY : epoch);

	msec_AD = epoch;
	second_AD = msec_AD / 1000.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);
	components[6] = (long) (msec_AD   % (double) 1000.0);
	
	return components;
    }

    /**
     * 
     * Breaks an array of EPOCH values down into its component parts.
     *
     * @param epochs the array of epoch values to break down
     * @return a 2-dimensional array, the first element being the row, while
     *         the second element containing the epoch parts, as follwos:
     *  <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>
     *  </TABLE>
     *  or null if null or an empty input array is detected
     */
    public static long [][] breakdown (double[] epochs)
    {
	if (epochs == null) return null;
	int len = epochs.length;
	if (len <= 0) return null;
	long[][] comps = new long[len][7];
	for (int i = 0; i < len; ++i)
	  comps[i] = Epoch.breakdown (epochs[i]);
	return comps;
    }

    /**
     * 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);
    }

    /**
     * computeEpoch.
     * This is the millseconds since 0 AD/1 BC.
     */
    private static double computeEpoch (long y, long m, long d, long h, long mn,
                                        long s, long ms) throws CDFException
    {
        long daysSince0AD;
        double msecInDay;
        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 < 1L) 
         throw new CDFException(ILLEGAL_EPOCH_FIELD);
        msecInDay = 3600000.0 * h + 60000.0 * mn + 1000.0 * s +
                    (double) ms;
        double msecFromEpoch =  86400000.0 * daysSince0AD + msecInDay;
        if (msecFromEpoch < 0.0)
          return ILLEGAL_EPOCH_VALUE;
        else
          return msecFromEpoch;
    }

}
