DoubleArray.java [src/java/m/cfa] Revision: 6b58cc57283496d8dcd336490b705c8a4da969a9  Date: Wed May 31 11:22:27 MDT 2017
package m.cfa;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;

/**
* Last Updated: 31-May-2017
* @author Tyler Wible
* @since 21-June-2012
*/
public class DoubleArray {
    /**
     * Append a value on to the end of list of values
     * @param currentArray  current list of values (double[])
     * @param newValue  new value to append to the list
     * @return  the new list containing the currentArray list of values and the appended newValue
     */
    public static double[] appendcolumn(double[] currentArray, double newValue){
        //Initialize new array to contain the new value
        double[] newArray = new double[currentArray.length + 1];

        //Fill the new array
        for(int i=0; i<currentArray.length; i++){
            newArray[i] = currentArray[i];
        }
        //Append newValue
        newArray[currentArray.length] = newValue;

        return newArray;
    }
    /**
     * Append a value on to the end of list of values
     * @param currentArray  current list of values (double[])
     * @param newValue  new value to append to the list
     * @return  the new list containing the currentArray list of values and the appended newValue
     */
    public static Double[] appendcolumn(double[] currentArray, double[] newValue){
        //Initialize new array to contain the new value
        Double[] newArray = new Double[currentArray.length + newValue.length];

        //Fill the new array
        for(int i=0; i<newArray.length; i++){
            if(i<currentArray.length){
                newArray[i] = currentArray[i];
            }else{
                newArray[i] = newValue[i - currentArray.length];
            }
        }

        return newArray;
    }
    /**
     * Appends an array onto another array to create a 2D array.   double[][] newArray = {currentArray, newArray};
     * @param currentArray1  current list of values (double[])
     * @param currentArray2  new value to append to the list (double[])
     * @return  the new list containing the currentArray list of values and the appended newValue
     */
    public static double[][] appendcolumn_Matrix(double[] currentArray1, double[] currentArray2){
        //Check that the arrays are the same size to be combined
        if(currentArray1.length != currentArray2.length){
            System.out.println("The arrays are not the same size so they can not be combined");
            return new double[0][0];
        }

        //Initialize new array to contain the new value
        double[][] newArray = new double[currentArray1.length][2];

        //Fill the new array
        for(int i=0; i<newArray.length; i++){
            newArray[i][0] = currentArray1[i];
            newArray[i][1] = currentArray2[i];
        }

        return newArray;
    }
    /**
     * Append a value on to the end of list of values
     * @param array1  current list of values (double[][])
     * @param array2  new value to append to the list (double[])
     * @return  the new list containing the currentArray list of values and the appended newValue
     */
    public static double[][] appendcolumn_Matrix(double[][] array1, double[] array2){
        //Check that the arrays are the same size to be combined
        if(array1.length != array2.length){
            System.out.println("The arrays are not the same size so they can not be combined");
            return new double[0][0];
        }

        //Initialize new array to contain the new value
        double[][] newArray = new double[array1.length][array1[0].length + 1];

        //Fill the new array
        for(int i=0; i<newArray.length; i++){
            for(int j=0; j<array1[i].length; j++){
                newArray[i][j] = array1[i][j];
            }
            newArray[i][newArray[i].length - 1] = array2[i];
        }

        return newArray;
    }
    /**
     * Append a value on to the end of list of values
     * @param array1  current list of values (String[][])
     * @param array2  new value to append to the list (String[])
     * @return  the new list containing the currentArray list of values and the appended newValue
     */
    public static String[][] appendcolumn_Matrix(String[][] array1, String[] array2){
        //Check that the arrays are the same size to be combined
        if(array1.length != array2.length){
            System.out.println("The arrays are not the same size so they can not be combined");
            return new String[0][0];
        }

        //Initialize new array to contain the new value
        String[][] newArray = new String[array1.length][array1[0].length + 1];

        //Fill the new array
        for(int i=0; i<newArray.length; i++){
            for(int j=0; j<array1[i].length; j++){
                newArray[i][j] = array1[i][j];
            }
            newArray[i][newArray[i].length - 1] = array2[i];
        }

        return newArray;
    }
    /**
     * Replace the first ("numberOfRows" + 1) of "startingArray" with those of "replacingArray"
     * @param startingArray  the double[] array to begin duplicating
     * @param replacingArray  the double[] array to be used replacing the first (rows + 1) of starting Array
     * @param numberOfRows  the number of rows in startingArray to be replaced by those in replacingArray
     * @return  a duplicate of startingArray with the first rows ("numberOfRows" + 1) being from replacingArray not startingArray
     */
    public static double[] replaceRows(double[] startingArray, double[] replacingArray, int numberOfRows){
        //Matlab code:	m(Z+1:end) = mh(Z+1:end);
        double[] newArray = new double[startingArray.length];

        for(int i=0; i<startingArray.length; i++){
            if(i<(numberOfRows+1)){
                //Replace the elements of startingArray with those of replacingArray
                newArray[i] = replacingArray[i];
            }else{
                //Otherwise keep the elements of startingArray
                newArray[i] = startingArray[i];
            }
        }
        return newArray;
    }
    /**
     * Keeps only the rows in startingArray that are not listed in rowIndexList
     * @param startingArray  The array to be minimized (double[][])
     * @param rowIndexList  a row index of the rows not to be included in the new minimized array (ArrayList)
     * @return the minimized version of the startingArray (double[][]) 
     */
    public static double[][] replaceRows(double[][] startingArray, ArrayList<Integer> rowIndexList){

        //Initialize new array
        double[][] newArray = new double[startingArray.length][startingArray[0].length];

        //Keep only the rows in startingArray that are not contained in the rowIndexList
        int indexCtr = 0;
        int currentRowIndex = rowIndexList.get(indexCtr);

        for(int i=0; i<startingArray.length; i++){//Loop rows of startingArray
            if(i != currentRowIndex){//Check if the current row should be kept or not
                //Keep current row's infomation
                newArray[i][0] = startingArray[i][0];//Populate the newArray with the content of startingArray
                newArray[i][1] = startingArray[i][1];
            }else{
                //Don't keep current row's information
                newArray[i][0] = startingArray[i][0];//Populate the newArray with the content of startingArray
                newArray[i][1] = 99999;//replace the values below 0.002
                indexCtr++;
                if(indexCtr >= rowIndexList.size()){
                    //If the indexCtr == rowIndexList.size then the last row index has been used out 
                    //of the rowIndexList so define an empty early index to keep the loop going
                    currentRowIndex = 0;
                }else{
                    //Then get the next row 
                    currentRowIndex = rowIndexList.get(indexCtr);
                }
            }
        }
        return newArray;
    }
    /**
     * Gets the unique values of the specified column of a double[][] array
     * @param dataArray  the double array
     * @param column  the desired column of the double array
     * @return a double[] array of the unique valued contained in the specified column of the dataArray
     */
    public static double[] getUnique(double[][] dataArray, int column){
        //Matlab code:	P = unique(ktable(:,2));
        double[] currentColumn = new double[dataArray.length];

        for(int i=0; i<dataArray.length; i++){
            currentColumn[i] = dataArray[i][column];
        }

        //Check and remove duplicate values for a list of unique values
        double[] uniqueColumn = getUnique(currentColumn);

        return uniqueColumn;
    }
    /**
     * Gets the unique values of the double[] array
     * @param currentColumn  the double array
     * @return a double[] array of the unique valued contained in the dataArray
     */
    public static double[] getUnique(double[] currentColumn){
        Arrays.sort(currentColumn);

        //Check and remove duplicate values for a list of unique values
        int ctr=0;
        for(int i=0; i<currentColumn.length; i++){
            if(i == 0){
                ctr++;
                continue;
            }
            if(Double.compare(currentColumn[i-1],currentColumn[i]) != 0){
                ctr++;
            }
        }
        double[] uniqueColumn = new double[ctr];
        ctr=0;
        for(int i=0; i<currentColumn.length; i++){
            if(i==0){
                uniqueColumn[ctr] = currentColumn[i];
                ctr++;
                continue;
            }
            if(Double.compare(currentColumn[i-1],currentColumn[i]) != 0){
                uniqueColumn[ctr] = currentColumn[i];
                ctr++;
            }
        }
        return uniqueColumn;
    }
    /**
     * Creates an ArrayList of the rows in dataArray that have the column number "column" equal to "currentValue"
     * @param dataArray  the double[][] data array
     * @param column  the column of interest of the data array
     * @param currentValue  the desired value from the data array
     * @return  an ArrayList of the rows in dataArray[][column] == currentValue
     */
    public static ArrayList<Integer> findRowsEqual(double[][] dataArray, int column, double currentValue){
        ArrayList<Integer> rowIndexList = new ArrayList<>();
        for(int i=0; i<dataArray.length; i++){
            if(Double.compare(dataArray[i][column],currentValue) == 0){
                rowIndexList.add(i);
            }
        }
        return rowIndexList;
    }
    /**
     * Creates an ArrayList of the rows in dataArray that have the column number "column" less or greater than to "currentValue"
     * @param dataArray  the double[][] data array
     * @param column  the column of interest of the data array
     * @param currentValue  the desired value from the data array
     * @param lessThan  if true, then all the rows with data less than the currentValue will be returned, if false all the rows with data greater than the current value will be returned
     * @return  an ArrayList of the rows in dataArray[][column] less than or greater than (depending on the value of "lessThan" provided) currentValue
     */
    public static ArrayList<Integer> findRowsConditional(double[][] dataArray, int column, double currentValue, boolean lessThan){
        ArrayList<Integer> rowIndexList = new ArrayList<>();
        for(int i=0; i<dataArray.length; i++){
            if(lessThan){//If lessThan ==  true, then find values less than the currentValue
                if(Double.compare(dataArray[i][column],currentValue) < 0){
                    rowIndexList.add(i);
                }
            }else{//If lessThan ==  false, then find values greater than the currentValue
                if(Double.compare(dataArray[i][column],currentValue) > 0){
                    rowIndexList.add(i);
                }
            }
        }
        return rowIndexList;
    }
    /**
     * Creates an ArrayList of the rows in dataArray that have the column number "column" less/equal to or greater than to "currentValue"
     * @param dataArray  the double[][] data array
     * @param currentValue  the desired value from the data array
     * @param lessThan  if true, then all the rows with data less than the currentValue will be returned, if false all the rows with data greater than the current value will be returned
     * @return  an ArrayList of the rows in dataArray[][column] less than/equal to or greater than (depending on the value of "lessThan" provided) currentValue
     */
    public static ArrayList<Integer> findRowsConditional(double[] dataArray, double currentValue, boolean lessThan){
        ArrayList<Integer> rowIndexList = new ArrayList<>();
        for(int i=0; i<dataArray.length; i++){
            if(lessThan){//If lessThan ==  true, then find values less than the currentValue
                if(Double.compare(dataArray[i],currentValue) <= 0){
                    rowIndexList.add(i);
                }
            }else{//If lessThan ==  false, then find values greater than the currentValue
                if(Double.compare(dataArray[i],currentValue) >= 0){
                    rowIndexList.add(i);
                }
            }
        }

        return rowIndexList;
    }
    /**
     * Creates a double[] array of the elements in dataArray[all][column]
     * @param dataArray  the double[][] data array
     * @param column  the desired column from data array (zero based)
     * @return  a double[] array of the elements in dataArray[all][column]
     */
    public static double[] getColumn(double[][] dataArray, int column){
        //Gets the values of dataArray[all][column] in a double[]
        double[] currentElements = new double[dataArray.length];

        for(int i=0; i<dataArray.length; i++){
            currentElements[i] = dataArray[i][column];
        }
        return currentElements;
    }
    /**
     * Creates a double[] array of the elements in dataArray[rowIndexList][column]
     * @param dataArray  the double[][] data array
     * @param column  the desired column from data array (zero based)
     * @param rowIndexList  an ArrayList containing the desired row indices from dataArray
     * @return  a double[] array of the elements in dataArray[rowIndexList][column]
     */
    public static double[] getColumn(double[][] dataArray, int column, ArrayList<Integer> rowIndexList){
        //Gets the values of dataArray[rowIndexList][column] in a double[]
        double[] currentElements = new double[rowIndexList.size()];

        int indexCtr = 0;
        for(int i=0; i<dataArray.length; i++){
            if(i == rowIndexList.get(indexCtr)){
                currentElements[indexCtr] = dataArray[i][column];
                indexCtr++;
                if(indexCtr == rowIndexList.size()){
                    //If the indexCtr == rowIndexList.size then the last row 
                    //index has been used out of the rowIndexList so exit the loop and return the list
                    break;
                }
            }
        }
        return currentElements;
    }
    /**
     * Creates a double[] array of the elements in dataArray[row][all]
     * @param dataArray  the double[][] data array
     * @param row  the desired row from data array
     * @return  a double[] array of the elements in dataArray[row][all]
     */
    public static double[] getRow(double[][] dataArray, int row){
        //Gets the values of dataArray[row][all] in a double[]
        double[] currentElements = new double[dataArray[row].length];

        for(int i=0; i<dataArray[row].length; i++){
            currentElements[i] = dataArray[row][i];
        }
        return currentElements;
    }
    /**
     * Creates a double[] array of the elements in dataArray[row][columnIndexList]
     * @param dataArray  the double[][] data array
     * @param row  the desired row from data array
     * @param columnIndexList  an ArrayList containing the desired row indices from dataArray
     * @return  a double[] array of the elements in dataArray[rowIndexList][column]
     */
    public static double[] getRow(double[][] dataArray, int row, ArrayList<Integer> columnIndexList){
        //Gets the values of dataArray[rowIndexList][column] in a double[]
        double[] currentElements = new double[columnIndexList.size()];

        int indexCtr = 0;
        for(int i=0; i<dataArray[row].length; i++){
            if(i == columnIndexList.get(indexCtr)){
                currentElements[indexCtr] = dataArray[row][i];
                indexCtr++;
                if(indexCtr == columnIndexList.size()){
                    //If the indexCtr == rowIndexList.size then the last row 
                    //index has been used out of the rowIndexList so exit the loop and return the list
                    break;
                }
            }
        }
        return currentElements;
    }
    /**
     * Creates a double[] array of the elements in dataArray[rowIndexList]
     * @param dataArray  the double[] data array
     * @param rowIndexList  an ArrayList containing the desired row indices from dataArray
     * @return a double[] array of the elements in dataArray[rowIndexList]
     */
    public static double[] getRows(double[] dataArray, ArrayList<Integer> rowIndexList){
        //Gets the values of dataArray[rowIndexList] in a double[]
        double[] newArray = new double[rowIndexList.size()];

        int indexCtr = 0;
        for(int i=0; i<dataArray.length; i++){
            if(i == rowIndexList.get(indexCtr)){
                newArray[indexCtr] = dataArray[i];
                indexCtr++;
                if(indexCtr == rowIndexList.size()){
                    //If the indexCtr == rowIndexList.size then the last row 
                    //index has been used out of the rowIndexList so exit the loop and return the list
                    break;
                }
            }
        }
        return newArray;
    }
    /**
     * Creates a double[][] array of the elements in dataArray[rowIndexList][] (aka it keeps some rows and all columns)
     * @param dataArray  the double[][] data array
     * @param rowIndexList  an ArrayList containing the desired row indices from dataArray
     * @return  a double[][] array containing the desired rows from rowIndexList and all the columns
     */
    public static double[][] getDataArrayRows(double[][] dataArray, ArrayList<Integer> rowIndexList){
        //Gets the values of dataArray[rowIndexList][all] in a double[][]
        double[][] currentElements = new double[rowIndexList.size()][dataArray[0].length];

        if(rowIndexList.isEmpty()){
            return currentElements;
        }
        int indexCtr = 0;
        for(int i=0; i<dataArray.length; i++){//Loop rows of data array
            if(i == rowIndexList.get(indexCtr)){//Check if current row is a desired row from the rowIndexList
                for(int j=0; j<dataArray[i].length; j++){//Loop columns of data array
                    currentElements[indexCtr][j] = dataArray[i][j];
                }
                indexCtr++;
                if(indexCtr == rowIndexList.size()){
                    //If the indexCtr == rowIndexList.size then the last row 
                    //index has been used out of the rowIndexList so exit the loop and return the list
                    break;
                }				
            }
        }
        return currentElements;
    }
    /**
     * Compares array1 and array2 and returns an ArrayList of the "j" rows in which array1[i] =  array2[j]
     * @param array1  double[] first arary to compare
     * @param array2  double[] second array to compare
     * @return  an ArrayList containing the rows in which array1[i] = array2[i]
     */
    public static ArrayList<Integer> intersect(double[] array1, double[] array2){
        ArrayList<Integer> rowIndex = new ArrayList<>();

        //Find the "j" rows where array1[i] = array2[j]
        for(int i=0; i<array1.length; i++){
            for(int j=0; j<array2.length; j++){
                if(Double.compare(array1[i], array2[j]) == 0){
                    rowIndex.add(j);
                }
            }
        }
        Collections.sort(rowIndex);
        return rowIndex;		
    }
    /**
     * Creates an double[] array of random numbers of array.length = size
     * @param size  the number of elements desired in the array
     * @return
     */
    public double[] RandomArray(int size){

        double[] randomArray = new double[size];
        for(int i=0; i<size; i++){
            randomArray[i] = Math.random();
        }

        return randomArray;
    }
    /**
     * Merges the public static dataset (first input) with the user dataset (second input)
     * based on the merge method selected
     * @param publicDataset  a String[][] array with the first column containing dates (yyyy-mm-dd)
     * and the second column values of flow or water quality test to be treated as the "public dataset"
     * for merge method purposes
     * @param userDataset  a String[][] array with the first column containing dates (yyyy-mm-dd)
     * and the second column values of flow or water quality test to be treated as the "user dataset"
     * for merge method purposes
     * @param mergeMethod  the method to merge the two datasets, one of the following:
     * "user" = overwrite public data with user data
     * "public" = overwrite user data with public data
     * "maximum" =  take the maximum value for that day from both datasets
     * "average" =  take the average of the two values for that day from both datasets
     * "minimum" =  take the minimum value for that day from both datasets
     * @return
     */
    public static String[][] mergeData(String[][] publicDataset, String[][] userDataset, String mergeMethod){
        //Allocate an array of values to determine if userdata needs to be kept (true) or merged (false)
        int ctr = 0;
        boolean[] addUserDataset = new boolean[userDataset.length];
        for(int i=0; i<userDataset.length; i++){
            addUserDataset[i] = true;
            for(int j=0; j<publicDataset.length; j++){
                if(userDataset[i][0].equalsIgnoreCase(publicDataset[j][0])){
                    addUserDataset[i] = false;
                    ctr++;
                    break;
                }
            }
        }        
        
        String[][] mergeDataset = new String[publicDataset.length + userDataset.length - ctr][2];
        boolean merge = false;
        ctr = 0;
        for(int i=0; i<publicDataset.length; i++){
            for(int j=0; j<userDataset.length; j++){
                //If the dates match, merge the datasets
                if(publicDataset[i][0].equalsIgnoreCase(userDataset[j][0])){
                    if(mergeMethod.equalsIgnoreCase("user")){
                        mergeDataset[ctr][0] = userDataset[j][0];
                        mergeDataset[ctr][1] = userDataset[j][1];
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("public")){
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        mergeDataset[ctr][1] = publicDataset[i][1];
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("max")){
                        double userValue = Double.valueOf(userDataset[j][1]);
                        double publicValue = Double.valueOf(publicDataset[i][1]);
                        if(Double.compare(userValue, publicValue) < 0){
                            mergeDataset[ctr][1] = String.valueOf(publicValue);
                        }else{
                            mergeDataset[ctr][1] = String.valueOf(userValue);
                        }
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("average")){
                        double userValue = Double.valueOf(userDataset[j][1]);
                        double publicValue = Double.valueOf(publicDataset[i][1]);
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        mergeDataset[ctr][1] = String.valueOf((userValue + publicValue)/2);
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("min")){
                        double userValue = Double.valueOf(userDataset[j][1]);
                        double publicValue = Double.valueOf(publicDataset[i][1]);
                        if(Double.compare(userValue, publicValue) < 0){
                            mergeDataset[ctr][1] = String.valueOf(userValue);
                        }else{
                            mergeDataset[ctr][1] = String.valueOf(publicValue);
                        }
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        ctr++;
                    }
                    merge = true;
                    break;
                }
            }
            if(!merge){
                mergeDataset[ctr][0] = publicDataset[i][0];
                mergeDataset[ctr][1] = publicDataset[i][1];
                ctr++;
            }
            merge = false;
        }
        
        //Include the points from the user dataset that aren't duplicate points
        for(int i=0; i<addUserDataset.length; i++){
            if(addUserDataset[i]){
                mergeDataset[ctr][0] = userDataset[i][0];
                mergeDataset[ctr][1] = userDataset[i][1];
                ctr++;
            }
        }
        
        Arrays.sort(mergeDataset, new DateComparator());
        return mergeDataset;
    }
    /**
     * Merges the public dataset (first input) with the user dataset (second input)
     * based on the merge method selected
     * @param publicDataset  a double[][] array with the first column containing years (yyyy)
     * and the second column values of flow or water quality test to be treated as the "public dataset"
     * for merge method purposes
     * @param userDataset  a String[][] array with the first column containing years (yyyy)
     * and the second column values of flow or water quality test to be treated as the "user dataset"
     * for merge method purposes
     * @param mergeMethod  the method to merge the two datasets, one of the following:
     * "user" = overwrite public data with user data
     * "public" = overwrite user data with public data
     * "max" =  take the maximum value for that day from both datasets
     * "average" =  take the average of the two values for that day from both datasets
     * "min" =  take the minimum value for that day from both datasets
     * @return
     */
    public static double[][] mergeData(double[][] publicDataset, double[][] userDataset, String mergeMethod){
        
        //Sort by dates for comparison purposes
        Arrays.sort(publicDataset, new sort1_smallToLargeDoubleMath());
        Arrays.sort(userDataset, new sort1_smallToLargeDoubleMath());
        
        //Allocate an array of values to determine if userdata needs to be kept (true) or merged (false)
        boolean[] useUserDataset = new boolean[userDataset.length];
        for(int i=0; i<useUserDataset.length; i++){
            useUserDataset[i] = true;
        }
        int ctr = 0;
        for(int i=0; i<publicDataset.length; i++){
            for(int j=0; j<userDataset.length; j++){
                if(Double.compare(publicDataset[i][0],userDataset[j][0]) == 0){
                    useUserDataset[j] = false;
                    ctr++;
                    break;
                }
            }
        }
        
        double[][] mergeDataset = new double[publicDataset.length + userDataset.length - ctr][2];
        boolean merge = false;
        ctr = 0;
        for(int i=0; i<publicDataset.length; i++){
            for(int j=0; j<userDataset.length; j++){
                //If the dates match, merge the datasets
                if(Double.compare(publicDataset[i][0],userDataset[j][0]) == 0){
                    if(mergeMethod.equalsIgnoreCase("user")){
                        mergeDataset[ctr][0] = userDataset[j][0];
                        mergeDataset[ctr][1] = userDataset[j][1];
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("public")){
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        mergeDataset[ctr][1] = publicDataset[i][1];
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("max")){
                        if(Double.compare(userDataset[i][1], publicDataset[i][1]) < 0){
                            mergeDataset[ctr][1] = publicDataset[i][1];
                        }else{
                            mergeDataset[ctr][1] = userDataset[j][1];
                        }
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("average")){
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        mergeDataset[ctr][1] = (userDataset[j][1] + publicDataset[j][1])/2;
                        ctr++;
                    }else if(mergeMethod.equalsIgnoreCase("min")){
                        if(Double.compare(userDataset[j][1], publicDataset[j][1]) < 0){
                            mergeDataset[ctr][1] = userDataset[j][1];
                        }else{
                            mergeDataset[ctr][1] = publicDataset[i][1];
                        }
                        mergeDataset[ctr][0] = publicDataset[i][0];
                        ctr++;
                    }
                    merge = true;
                    break;
                }
            }
            if(!merge){
                mergeDataset[ctr][0] = publicDataset[i][0];
                mergeDataset[ctr][1] = publicDataset[i][1];
                ctr++;
            }
            merge = false;
        }
        
        //Include the points from the user dataset that aren't duplicate points
        for(int i=0; i<useUserDataset.length; i++){
            if(useUserDataset[i]){
                mergeDataset[ctr][0] = userDataset[i][0];
                mergeDataset[ctr][1] = userDataset[i][1];
                ctr++;
            }
        }
        
        Arrays.sort(mergeDataset, new sort1_smallToLargeDoubleMath());
        return mergeDataset;
    }
    /**
     * Sorts the String[][] by the first column (dates) and then removes duplicate 
     * date values and returns an array of equal or lesser size than the original
     * @param currentStringArray the String[][] to be sorted and removed, column1 = dates (yyyy-mm-dd), column2 = values, other columns will also be kept
     * @return  a String[][] of equal or lesser size than the original containing the original data sorted by the first column
     */
    public static String[][] removeDuplicateDates(String[][] currentStringArray){
        //Sort the Data by date to remove duplicate date entries
        Arrays.sort(currentStringArray, new DateComparator());
        
        //Check and remove duplicate days as to correct statistical implication of duplicate flow values on a day
        int ctr=0;
        for(int i=0; i<(currentStringArray.length); i++){
            if(i == 0){
                ctr++;
                continue;
            }
            if(!currentStringArray[i-1][0].equals(currentStringArray[i][0])){
                ctr++;
            }
        }
        String[][] sortedData = new String[ctr][2];
        ctr=0;
        for(int i=0; i<(currentStringArray.length); i++){
            if(i==0){
                sortedData[ctr][0] = currentStringArray[i][0];
                sortedData[ctr][1] = currentStringArray[i][1];
                ctr++;
                continue;
            }
            if(!currentStringArray[i-1][0].equals(currentStringArray[i][0])){
                sortedData[ctr][0] = currentStringArray[i][0];
                sortedData[ctr][1] = currentStringArray[i][1];
                ctr++;
            }
        }

        return sortedData;
    }/**
     * Performs a Weibull plotting position ranking of the provided flow values in sortedData's second column
     * @param sortedData  a string[][] containing: column1 = dates (unused) column2 = flowValues to be ranked
     * @return  a double[][] containing: column1 = x, column2 = y coordinates of the ranking based on a duration 
     * curve method and Weibull plotting position
     */
    public static double[][] weibullPlottingPosition(String[][] sortedData){
        if(sortedData.length < 1){
            double[][] emptyArray = new double[0][2];
            return emptyArray;
        }
        
        //Sort the Data by flow to prepare for ranking
        Arrays.sort(sortedData, new FlowComparator());	

        // Index new variables of rank, and non-exceedance probability
        double total_samples = sortedData.length;
        double[][] xyRanks = new double[sortedData.length + 1][2];
        int g = 0, h = 0;

        //Flow duration curve using "Weibul" distribution
        for(int i=1; i<(sortedData.length +1); i++){
            if((i != sortedData.length) && (sortedData[i-1][1].equals(sortedData[i][1]))){
                //Find how many elements equal
                h = i;
                while((h != sortedData.length) && (sortedData[h-1][1].equals(sortedData[h][1]))){
                    h++;
                } 
                //Give all the equal elements the rank of the largest equal element (max rank for tied rank scenarios)
                for(g=i; g<=h; g++){
                    xyRanks[g-1][0] = (h / (total_samples))*100;
                    xyRanks[g-1][1] = Double.parseDouble(sortedData[i-1][1]);
                    if (g==h){
                        //If on the last repeated element, set the initial counter "i" that last 
                        //rank value as to not repeat comparing already sorted and ranked values
                        i=h;
                    }
                }
            }else{
                xyRanks[i-1][0] = (i / (total_samples))*100;
                xyRanks[i-1][1] = Double.parseDouble(sortedData[i-1][1]);
            }
        }
        xyRanks[sortedData.length][0] = 100;

        return xyRanks;
    }
    /**
     * Reduces the provided data to only within the user specified season
     * @param allData  all the data for the user specified date range to be minimized by season (column1 = date (yyyy-mm-dd), column2 = value)
     * @param seasonBegin  the begin of the season formatted MM-dd (ex June 1st is "06-01")
     * @param seasonEnd  the end of the season formatted MM-dd (ex June 1st is "06-01")
     * @return  an array containing the dates and values of water quality tests that occur within the specified season
     * @throws java.text.ParseException
     */
    public static String[][] getSeasonalData(String[][] allData, String seasonBegin, String seasonEnd) throws ParseException{
        //Convert season begin/end into dates
        DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date beginDate = desiredDateFormat.parse("2000-" + seasonBegin);
        Date endDate = desiredDateFormat.parse("2000-" + seasonEnd);
        
        //Check if the season is within 1 calendar year or not (ex. February to October)
        boolean previousYearSeasonTF = false;//(ex. February to October)
        if(beginDate.compareTo(endDate) > 0){
            beginDate = desiredDateFormat.parse("1999-" + seasonBegin);
            previousYearSeasonTF = true;//(ex. October to February)
        }
        
        //Remove data that are outside user specified seasonal range
        ArrayList<String> partialDates = new ArrayList<>();
        ArrayList<String> partialValues = new ArrayList<>();
        for(int i=0; i<allData.length; i++){
            //Convert the current data's date into a calendar
            String monthDay = allData[i][0].substring(5);
            Date currentDate = desiredDateFormat.parse("2000-" + monthDay);
            if(previousYearSeasonTF && monthDay.compareTo("12-31") <= 0){
                currentDate = desiredDateFormat.parse("1999-" + monthDay);
            }
            
            if(currentDate.compareTo(beginDate) >= 0 && currentDate.compareTo(endDate) <= 0){
                partialDates.add(allData[i][0]);
                partialValues.add(allData[i][1]);
            }
        }
        
        //Convert the format of the data back to the expected format
        String[][] seasonalData = new String[partialDates.size()][2];
        for(int i=0; i<partialDates.size(); i++){
            seasonalData[i][0] = partialDates.get(i);
            seasonalData[i][1] = partialValues.get(i);
        }

        return seasonalData;
    }
    /**
     * Reduces the provided data to only within the specified year
     * @param allData  all the data for the to be minimized by year (column1 = date (yyyy-mm-dd), column2 = value)
     * @param year  the specified year (ex "1981")
     * @return an array (same format as allData) containing the dates and values that occur within the specified year
     */
    public static String[][] getYearsData(String[][] allData, String year){
        //Remove data that are outside specified year
        int ctr = 0;
        for(int i=0; i<allData.length; i++){
            String tempYear = allData[i][0].substring(0,4);
            if(year.compareToIgnoreCase(tempYear) == 0){
                ctr++;
            }
        }

        String[][] SeasonalData = new String[ctr][2];
        ctr = 0;
        for(int i=0; i<allData.length; i++){
            String tempYear = allData[i][0].substring(0,4);
            if(year.compareToIgnoreCase(tempYear) == 0){
                SeasonalData[ctr][0] = allData[i][0];
                SeasonalData[ctr][1] = allData[i][1];
                ctr++;
            }
        }

        return SeasonalData;
    }
    /**
     * Calculate the flow associated with the n-year return period from the set of annual low flow data
     * @param nYearFlows  a list of annual flow values (aka one value per year)
     * @param n  the n-year return period for the desired flow
     * @return 
     */
    public static double calculateLowFlowReturnPeriod(ArrayList<Double> nYearFlows, double n){
        //Remove non-values
        int ctr = 0;
        for(int i=0; i<nYearFlows.size(); i++){
            if(nYearFlows.get(i) != Double.NaN){
                ctr++;
            }
        }
        String[][] nYearData = new String[ctr][2];
        ctr=0;
        for(int i=0; i<nYearFlows.size(); i++){
            if(nYearFlows.get(i) != Double.NaN){
                nYearData[ctr][0] = String.valueOf(ctr);
                nYearData[ctr][1] = String.valueOf(nYearFlows.get(i));
                ctr++;
            }
        }
        
        //Get ranks for remaining annual data
        double[][] nYearRanks = weibullPlottingPosition(nYearData);
        double[] nYearNonExceedance = getColumn(nYearRanks,0);
        double[] nYearValues = getColumn(nYearRanks,1);

        //Find the "n" recurrence interval and return its corresponding flow as the n-year Flow
        double target = (1-(1.0/n)) * 100.0;//nonexceedance = (1 - [exceedance = (1/return period)]) * 100 //to get range of 0-100
        double n_yearFlow = DoubleMath.linearInterpolation(nYearNonExceedance, nYearValues, target);
        return n_yearFlow;
    }
    /**
     * 
     * @param sortedData  the String[][] containing sorted data for the time series 
     * (column 1 = dates (yyyy-mm-dd format) column 2 = value
     * @param periodBegin  the begin date of the analysis period (yyyy-mm-dd format)
     * @param periodEnd  the end date of the analysis period (yyyy-mm-dd format)
     * @return a String[][] of the data from sortedData that exists within the 
     * period defined by the begin and end dates
     * @throws ParseException 
     */
    public static String[][] getPeriodData(String[][] sortedData, String periodBegin, String periodEnd) throws ParseException{
        if(!periodBegin.equalsIgnoreCase("") && !periodEnd.equalsIgnoreCase("")){
            //Change analysis period dates into Date objects
            SimpleDateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date periodBegin_date = desiredDateFormat.parse(periodBegin);
            Date periodEnd_date = desiredDateFormat.parse(periodEnd);
            
            //Loop through and pull out the period data
            ArrayList<String> periodDates = new ArrayList<>();
            ArrayList<String> periodValues = new ArrayList<>();
            for(int i=0; i<sortedData.length; i++){
                //Check if data is within the defined period
                Date newDate = desiredDateFormat.parse(sortedData[i][0]);
                if(newDate.compareTo(periodBegin_date) >= 0 && newDate.compareTo(periodEnd_date) <= 0){
                    periodDates.add(sortedData[i][0]);
                    periodValues.add(sortedData[i][1]);
                }
            }
            
            String[][] periodData = new String[periodDates.size()][2];
            for(int i=0; i<periodDates.size(); i++){
                periodData[i][0] = periodDates.get(i);
                periodData[i][1] = periodValues.get(i);
            }
            
            return periodData;
        }else{
            //If dates are blank return an empty array
            String[][] emptyData = new String[0][2];
            return emptyData;
        }
    }
    /**
     * Calculates a unique list of years contained in the flow data from STORET then proceeds to 
     * calculate the annual maximum flow value for each of the unique years and returns the list 
     * of years and flow values as a double array
     * @param sortableData  string[][] array containing the output of STORET_unzip_FDC.main which contains
     * dates (YYYY-mm-dd) in the first column and flow values (cfs) in the second column
     * @return returns a double[][] array the same size as the provided string array containing 
     * the first column of years and the second column of flow values
     */
    public static double[][] convertSTORETpeakData(String[][] sortableData){

        Arrays.sort(sortableData, new DateComparator());
        //Find a list of unique years for which an annual maximum flow will be calculated later
        int ctr=0;
        for(int i=0; i<(sortableData.length); i++){
            if(i == 0){
                ctr++;
                continue;
            }
            String year1 = sortableData[i-1][0].substring(0,4);
            String year2 = sortableData[i][0].substring(0,4);
            if (!year1.equals(year2)){
                ctr++;
            }
        }
        String[] uniqueYears = new String[ctr];
        ctr=0;
        for(int i=0; i<(sortableData.length); i++){
            if(i==0){
                uniqueYears[ctr] = sortableData[i][0].substring(0,4);
                ctr++;
                continue;
            }
            String year1 = sortableData[i-1][0].substring(0,4);
            String year2 = sortableData[i][0].substring(0,4);
            if (!year1.equals(year2)){
                uniqueYears[ctr] = sortableData[i][0].substring(0,4);
                ctr++;
            }
        }


        //Loop through and find the annual maximum flow value for each unique year
        double[][] peakFlowData = new double[uniqueYears.length][2];
        for(int i=0; i<uniqueYears.length; i++){
            peakFlowData[i][0] = Double.parseDouble(uniqueYears[i]);
            peakFlowData[i][1] = 0;
            for(int j=0; j<sortableData.length; j++){
                String currentYear = sortableData[j][0].substring(0,4);
                if(uniqueYears[i].equals(currentYear)){
                    double flowValue = Double.parseDouble(sortableData[j][1]);
                    if(Double.compare(flowValue, peakFlowData[i][1]) > 0){//If current value larger than "max" change the max to the current value
                        peakFlowData[i][1] = flowValue;
                    }
                }
            }
        }

        return peakFlowData;
    }
    /**
     * Computes the daily/monthly/yearly max/min/average/total of the daily dataset provided (requires previously sorted data by date)
     * @param dailyData  a string array with column1 = dates (yyyy-mm-dd format), column2 = value
     * @param timeStep  the desired timestep "daily", "monthly", or "yearly"
     * @param method  the desired method "max", "min", "average", or "total"
     * @return a new string[][] with column 1 = dates (yyyy-mm-dd format for "daily" timeStep,
     * yyyy-mm format for "monthly" timeStep, and yyyy format for "yearly" timeStep) and column2 = values
     * @param loadestTF  a flag for if analyzing loadest result data which uses a yyyymmdd date format from the
     * loadest.exe result file rather than typical date format of yyyy-mm-dd
     */
    public static String[][] computeFlowMethod(String[][] dailyData, String timeStep, String method, boolean loadestTF){
        String[][] newData = new String[0][2];
        if(dailyData.length > 0){
            if(timeStep.equalsIgnoreCase("daily")){
                return dailyData;
            }else if(timeStep.equalsIgnoreCase("monthly")){
                //Compute the method on the unique monthYears
                if(loadestTF){
                    newData = computeMethod(dailyData, method, 6);
                }else{
                    newData = computeMethod(dailyData, method, 7);
                }

            }else if(timeStep.equalsIgnoreCase("yearly")){
                //Compute the method on the unique monthYears
                newData = computeMethod(dailyData, method, 4);
            }
        }

        return newData;
    }
    /**
     * Computes the method on the dailyData provided substring-ing the dates from 0-dateLimit to 
     * get a unique set of time periods on which to perform the method (requires previously sorted data by date)
     * @param dailyData  a string array with column1 = dates (yyyy-mm-dd format), column2 = value
     * @param method  the desired method "max", "min", "average", or "total"
     * @param dateLimit  an integer for the limit of the date substring (typically 4 or 7 for the 
     * format yyyy-mm-dd resulting in yyyy and yyyy-mm respectively)
     * @return a new string[][] with column 1 = dates (yyyy-mm-dd format for "daily" timeStep,
     * yyyy-mm format for "monthly" timeStep, and yyyy format for "yearly" timeStep) and column2 = values
     */
    private static String[][] computeMethod(String[][] dailyData, String method, int dateLimit){
        //Find the unique set of months/years for which the method is desired for
        ArrayList<String> uniqueTimeStep = new ArrayList<>();
        String previousMonthYear = dailyData[0][0].substring(0,dateLimit);
        uniqueTimeStep.add(previousMonthYear);
        for(int i=1; i<dailyData.length; i++){
            //Check if current monthYear is the same or different from the previous
            String currentMonthYear = dailyData[i][0].substring(0,dateLimit);
            if(!previousMonthYear.equalsIgnoreCase(currentMonthYear)){
                uniqueTimeStep.add(currentMonthYear);
                previousMonthYear = currentMonthYear;
            }
        }

        //Loop through daily data, pull out each uniqueTimeStep's dataset and perform the method on that dataset
        String[][] newData = new String[uniqueTimeStep.size()][2];
        ArrayList<Double> currentMonthData = new ArrayList<>();
        int ctr=0;		
        for(int i=0; i<dailyData.length; i++){
            if(uniqueTimeStep.get(ctr).equals(dailyData[i][0].substring(0, dateLimit))){
                //If current data = current month, add it to the dataset
                currentMonthData.add(Double.parseDouble(dailyData[i][1]));

            }else{
                //If current data != current month, calculate method on the current month's dataset, save the result and reset the dataset
                newData[ctr][0] = uniqueTimeStep.get(ctr);
                if(method.equalsIgnoreCase("max")){
                    newData[ctr][1] = String.valueOf(DoubleMath.max(currentMonthData));
                }else if(method.equalsIgnoreCase("average")){
                    newData[ctr][1] = String.valueOf(DoubleMath.meanArithmetic(currentMonthData));
                }else if(method.equalsIgnoreCase("min")){
                    newData[ctr][1] = String.valueOf(DoubleMath.min(currentMonthData));
                }else if(method.equalsIgnoreCase("total")){
                    newData[ctr][1] = String.valueOf(DoubleMath.sum(currentMonthData));
                }

                //Reset the dataset and add the current data to it as the new month's data
                currentMonthData.clear();
                currentMonthData.add(Double.parseDouble(dailyData[i][1]));
                ctr++;
            }

            //If on the last point calculate the method on the current dataset
            if(i == dailyData.length-1){
                newData[ctr][0] = uniqueTimeStep.get(ctr);
                if(method.equalsIgnoreCase("max")){
                    newData[ctr][1] = String.valueOf(DoubleMath.max(currentMonthData));
                }else if(method.equalsIgnoreCase("average")){
                    newData[ctr][1] = String.valueOf(DoubleMath.meanArithmetic(currentMonthData));
                }else if(method.equalsIgnoreCase("min")){
                    newData[ctr][1] = String.valueOf(DoubleMath.min(currentMonthData));
                }else if(method.equalsIgnoreCase("total")){
                    newData[ctr][1] = String.valueOf(DoubleMath.sum(currentMonthData));
                }
            }
        }

        return newData;
    }
    /**
     * Writes out the formatted output file as expected by eRAMS to be used for a JHighchart timeseries
     * @param directory
     * @param data
     * @param timeStep
     * @param outputFileName
     * @param loadestTF
     * @throws IOException 
     * @throws java.text.ParseException 
     */
    public static void writeTimeSeries(String directory, String[][] data, String timeStep, String outputFileName, boolean loadestTF) throws IOException, ParseException {
        //Convert Date formate for output
        String[] date = changeDate2JHighChart(data, timeStep, loadestTF);
        
        // open the file writer and set path
        String path = directory + File.separator + outputFileName;
        FileWriter writer =  new FileWriter(path, false);
        PrintWriter print_line = new PrintWriter(writer);
        
        // Print the needed values
        for (int i = 0; i < date.length; i++){
            String currentLine = date[i];
            for(int j=1; j<data[i].length; j++){
                currentLine = currentLine + "\t" + data[i][j];
            }
            print_line.printf("%s" + "\r\n", currentLine);
        }
      
        // Close the file writer 
        print_line.close();
        writer.close();
        System.out.println("Text File located at:\t" + path);
    }
    /**
     * This function will prepare the data that is needed for the use on eRAMS 
     * with JHighCharts (date format: yyyy-MM-dd)
     * @param data
     * @param timeStep
     * @return
     */
    private static String[] changeDate2JHighChart(String[][] data, String timeStep, boolean loadestTF) throws ParseException{
        DateFormat desiredDateFormat = getDateFormat(timeStep,  loadestTF);
        
        String[] date = new String[data.length];
        Calendar currentCalendar = Calendar.getInstance();
        for(int i = 0; i < data.length; i++){
            Date currentDate = desiredDateFormat.parse(data[i][0]);
            currentCalendar.setTime(currentDate);
            date[i] = String.valueOf(currentCalendar.getTimeInMillis()/1000); 
        }
        
        return date;
    }
    /**
     * Writes out the formatted output file as expected by eRAMS to be used for a JHighchart boxplot
     * @param directory  the location of the output file
     * @param outliers
     * @param boxplotData  a double[][] array of each boxplot's data where 
     * boxplotData[0][i] = max, 
     * boxplotData[1][i] = upperQuartile, 
     * boxplotData[2][i] = median, 
     * boxplotData[3][i] = lowerQuartile, 
     * boxplotData[4][i] = min, 
     * and there are as many dimensions (i) as there are boxplots to be plotted
     * @param outputFileName
     * @throws IOException 
     */
    public static void writeBoxplot(String directory,
                             ArrayList<ArrayList<Double>> outliers,
                             double[][] boxplotData,
                             String outputFileName) throws IOException {
    	// open the file writer and set path
        String path = directory + File.separator + outputFileName;
        FileWriter writer =  new FileWriter(path, false);
        PrintWriter print_line = new PrintWriter(writer);
    	
        //Get first boxplot's information
        double outlierMaxSize = -1;
        try{
            outlierMaxSize = outliers.get(0).size();
        }catch(IndexOutOfBoundsException e){
            outlierMaxSize = 0;
        }
        double IQR = boxplotData[1][0] - boxplotData[3][0];
        double low = boxplotData[3][0] - 1.5 * IQR;
        double high = boxplotData[1][0] + 1.5 * IQR;
        if (low < boxplotData[4][0]){
            low = boxplotData[4][0];
        }
        if (high > boxplotData[0][0]){
            high = boxplotData[0][0];
        }
        String lowLine = String.valueOf(low);
        String lowQLine = String.valueOf(boxplotData[3][0]);
        String medianLine = String.valueOf(boxplotData[2][0]);
        String highQLine = String.valueOf(boxplotData[1][0]);
        String highLine = String.valueOf(high);
        
        //Loop through the array of boxplot data
        for(int i=1; i<outliers.size(); i++){
            IQR = boxplotData[1][i] - boxplotData[3][i];
            low = boxplotData[3][i] - 1.5 * IQR;
            high = boxplotData[1][i] + 1.5 * IQR;
            if (low < boxplotData[4][i]){
                low = boxplotData[4][i];
            }
            if (high > boxplotData[0][i]){
                high = boxplotData[0][i];
            }
            
            //Append this boxplot's results to the existing data to be graphed
            lowLine    = lowLine + "\t" + low;
            lowQLine   = lowQLine + "\t" + boxplotData[3][i];
            medianLine = medianLine + "\t" + boxplotData[2][i];
            highQLine  = highQLine + "\t" + boxplotData[1][i];
            highLine   = highLine + "\t" + high;
            
            //Determine how long to loop over the outliers
            double outlierSize = outliers.get(i).size();
            if(outlierSize > outlierMaxSize) outlierMaxSize = outlierSize;
        }
        print_line.printf("%s" + "\r\n", lowLine);
        print_line.printf("%s" + "\r\n", lowQLine);
        print_line.printf("%s" + "\r\n", medianLine);
        print_line.printf("%s" + "\r\n", highQLine);
        print_line.printf("%s" + "\r\n", highLine);
        
        // Print the outliers
        for(int i=0; i<outlierMaxSize; i ++){
            String currentLine = null;
            try{
                currentLine = String.valueOf(outliers.get(0).get(i));
            }catch(IndexOutOfBoundsException e){
                currentLine = "-1";
            }
            for(int j=1; j<outliers.size(); j++){
                try{
                    currentLine = currentLine + "\t" + String.valueOf(outliers.get(j).get(i));
                }catch(IndexOutOfBoundsException e){
                    currentLine = currentLine + "\t" + "-1";
                }
                
            }
            print_line.printf("%s" + "\r\n", currentLine);
        }
   
        // Close the file writer 
        print_line.close();
        writer.close();
        System.out.println("Text File located at:\t" + path);
    }
    /**
     * Writes out the formatted output file as expected by eRAMS to be used for a JHighchart XY
     * @param directory
     * @param data
     * @param outputFileName
     * @throws IOException 
     */
    public static void writeXYseries(String directory, String[][] data, String outputFileName) throws IOException {
        //open the file writer and set path
        String path = directory + File.separator + outputFileName;
        FileWriter writer =  new FileWriter(path, false);
        PrintWriter print_line = new PrintWriter(writer);
        
        //Print the needed values
        for(int i=0; i<data.length; i++){
            String currentLine = data[i][0];
            for(int j=1; j<data[i].length; j++){
                currentLine = currentLine + "\t" + data[i][j];
            }
            print_line.printf("%s" + "\r\n", currentLine);
        }
      
        // Close the file writer 
        print_line.close();
        writer.close();
        System.out.println("Text File located at:\t" + path);
    }
    /**
     * Gets the desired date format (Java clasS) for the timestep (string)
     * @param timeStep  String of the desired date format (15-min | daily | monthly | yearly) 
     * which return (yyyy-MM-dd HH:mm | yyyy-mm-dd | yyyy-mm | yyyy) respectively
     * @param loadestTF  a flag whether the daily, monthly, or yearly date format 
     * should exclude the "-" matching the output format of loadest.exe
     * which return (yyyy-MM-dd HH:mm | yyyymmdd | yyyymm | yyyy) respectively
     * @return Returns a DateFormat for the specified time step
     */
    public static DateFormat getDateFormat(String timeStep, boolean loadestTF){
        DateFormat desiredDateFormat = null;
        if(timeStep.equalsIgnoreCase("15-min")){
            desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        }else if(timeStep.equalsIgnoreCase("daily")){
            if(loadestTF){
                desiredDateFormat = new SimpleDateFormat("yyyyMMdd");
            }else{
                desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            }
        }else if(timeStep.equalsIgnoreCase("monthly")){
            if(loadestTF){
                desiredDateFormat = new SimpleDateFormat("yyyyMM");
            }else{
                desiredDateFormat = new SimpleDateFormat("yyyy-MM");
            }
        }else if(timeStep.equalsIgnoreCase("yearly")){
            desiredDateFormat = new SimpleDateFormat("yyyy");
        }
        return desiredDateFormat;
    }
    /**
     * Checks if the provided dates are subsequent dates, aka nextDate = date + 1day.  
     * This check includes December 31st to January 1st catchs, 4-year leap-year catches, 
     * 100-year non-leap-year catches and 400-year leap-year catches
     * @param firstDate_str  the first date to be compared (expected format = yyyy-mm-dd)
     * @param secondDate_str  the second date to be compared (expected format = (yyyy-mm-dd)
     * @return returns true if nextDate = date + 1day, false otherwise
     * @throws java.text.ParseException
     */
    public static boolean checkSubsequentDates(String firstDate_str, String secondDate_str) throws ParseException{
        DateFormat desiredDateFormat = getDateFormat("daily", false);
        
        Date firstDate = desiredDateFormat.parse(firstDate_str);
        Calendar firstCalendar = Calendar.getInstance();
        firstCalendar.setTime(firstDate);
        firstCalendar.add(Calendar.DAY_OF_YEAR, 1);
        
        Date secondDate = desiredDateFormat.parse(secondDate_str);
        Calendar secondCalendar = Calendar.getInstance();
        secondCalendar.setTime(secondDate);
        
        boolean subsequentDates = false;
        if(firstCalendar.equals(secondCalendar)){
            subsequentDates = true;
        }
        
        return subsequentDates;
    }
    /**
     * 
     * @param allData
     * @param date
     * @return 
     */
    public static ArrayList<Double> getDaysData(String[][] allData, String date){
        //Remove data that are outside specified date (yyyy-MM-dd)
        ArrayList<Double> dayData = new ArrayList<>();
        for(int i=0; i<allData.length; i++){
            String tempDate = allData[i][0].substring(0,10);
            if(date.compareToIgnoreCase(tempDate) == 0){
                dayData.add(Double.parseDouble(allData[i][1]));
            }
        }

        return dayData;
    }
    /**
     * Gets the day that is 'direction' amount before/after the provided date
     * @param date  the current date (formatted yyyy-MM-dd)
     * @param direction  the magnitude and direction of days from 'date' that is desired 
     * (i.e. a value of -2 would yield the date of 2 days ago while a value of 1 will yield tomorrow's date)
     * @return returns the string value of the date that is 'direction' away formatted as yyyy-MM-dd
     * @throws java.text.ParseException
     */
    public static String getDay(String date, int direction) throws ParseException{
        //Parse Date
        DateFormat desiredDateFormat = getDateFormat("daily",  false);
        Date currentDate = desiredDateFormat.parse(date);
        
        Calendar currentCalendar = Calendar.getInstance();
        currentCalendar.setTime(currentDate);
        
        //Get next date
        currentCalendar.add(Calendar.DAY_OF_YEAR, direction);
        
        //Parse next date
        int yearInt = currentCalendar.get(Calendar.YEAR);
        int monthInt = currentCalendar.get(Calendar.MONTH) + 1;//because calendar months are zero based (aka 0 = January, etc.)
        int dayInt = currentCalendar.get(Calendar.DAY_OF_MONTH);
        
        //Check for a single digit month, if so make it a 2 digit month starting with a zero
        String monthString = String.valueOf(monthInt);
        if(monthString.length() < 2){
            monthString = "0" + monthString;
        }

        //Check for a single digit day, if so make it a 2 digit day starting with a zero
        String dayString = String.valueOf(dayInt);
        if(dayString.length() < 2){
            dayString = "0" + dayString;
        }
        
        //Format next date
        String nextDate = String.valueOf(yearInt) + "-" + monthString + "-" + dayString;
        
        return nextDate;
    }
    /**
     * Determines if the provided year (as an integer) is a leap year or not taking into 
     * account for leap years every 4 years, not every 100 years, and leap years every 
     * 400 years (this has to do with round off errors in the length of a day that propagate 
     * over time).  If support for >400 year leap year information is desired modify this subfunction.
     * @param currentYear  an integer for the current year
     * @return  true if the currentYear is a leap year, false otherwise
     */
    public static boolean getLeapYearTF(int currentYear){
        //Determine if the current year is a leap year (366 days) or not (365 days)
        boolean leapYear = false;
        double currentYear_db = (double) currentYear;

        //Determine if this year is a leap year (divide by 4) and take into account every 100 years it is not a leap year and every 400 it is
        double yearUp4		= Math.ceil(currentYear_db/4);
        double yearDown4	= Math.floor(currentYear_db/4);
        double yearUp100	= Math.ceil(currentYear_db/100);
        double yearDown100	= Math.floor(currentYear_db/100);
        double yearUp400	= Math.ceil(currentYear_db/400);
        double yearDown400	= Math.floor(currentYear_db/400);
        if(yearUp400 == yearDown400){
            leapYear = true;
        }else if(yearUp100 == yearDown100){
            leapYear = false;
        }else if(yearUp4 == yearDown4){
            leapYear = true;
        }
        return leapYear;
    }
    /**
     * Loops through and finds "m-day" consecutive values and takes the arithmetic/harmonic average of them
     * @param flowData  a string[][] containing: column1 = dates, column2 = flowValues
     * @param numDays  an integer representing the number (m) of consecutive days to be desired for analysis
     * @param averageType  a flag for what sort of average to take of the dataset either 'arithmetic' or 'harmonic' are supported currently
     * @throws java.text.ParseException
     * @returns  an ArrayList containing an ArrayList of each set of "m-day" consecutive set of flows for analysis (min, max, average, etc)
     * @throws IOException
     */
    public static Object[] getMdayData(String[][] flowData, int numDays, String averageType) throws IOException, ParseException{
        //Loop through flow data and find "m"-day consecutive flows
        ArrayList<String> allDate = new ArrayList<>();
        ArrayList<Double> allData = new ArrayList<>();
        try{
            for(int i=0; i<flowData.length; i++){
                ArrayList<String> mDayDate = new ArrayList<>();
                ArrayList<Double> mDayData = new ArrayList<>();
                int ctr = i;
                for(int j=0; j<numDays; j++){
                    if(j==0){
                        //Keep the first day
                        mDayDate.add(flowData[ctr][0]);
                        mDayData.add(Double.parseDouble(flowData[ctr][1]));
                    }else{
                        //Compare the current day to the previous day for consecutive-ness
                        boolean checkNextDate = checkSubsequentDates(flowData[ctr-1][0], flowData[ctr][0]);
                        if(checkNextDate){
                            mDayDate.add(flowData[ctr][0]);
                            mDayData.add(Double.parseDouble(flowData[ctr][1]));
                        }else{
                            //If not consecutive days, break out of the loop and move to the next date for flowData
                            mDayDate.clear();
                            mDayData.clear();
                            i = ctr - 1;//Skip to newest date since there is a break in the consecutive day data
                            break;
                        }
                    }
                    ctr++;
                }
                if(mDayData.size() == numDays){
                    //Add this m-consecutive day set of data to the all data array list for statistics later
                    String startDate = mDayDate.get(0);
                    String endDate = mDayDate.get(numDays - 1);
                    allDate.add(startDate + " to " + endDate);
                    if(averageType.equalsIgnoreCase("arithmetic")){
                        allData.add(DoubleMath.meanArithmetic(mDayData));
                    }else if(averageType.equalsIgnoreCase("harmonic")){
                        allData.add(DoubleMath.meanHarmonic(mDayData));
                    }
                }
            }
        }catch(ArrayIndexOutOfBoundsException e){
            //If the consecutive day counter (ctr) goes beyond the length of data available,
            //stop the subroutine and return the existing results
            Object[] returnArray = {allDate, allData};
            return returnArray;
        }
        
        Object[] returnArray = {allDate, allData};
        return returnArray;
    }
//    /**
//     * Reduces all data to just that within the specified date range
//     * @param allData  all water quality data for the earlier provided date range and station ID (column1 = date, column2 = value)
//     * @param beginDate  the user defined begin date for data search
//     * @param endDate  the user defined end date for data search
//     * @return  A string array formatted the same as the input array allData (column1 = date, column2 = value) containing only the 
//     * data for dates which were beginDate < data-date < endDate
//     * @throws IOException 
//     */
//    public static String[][] minimize15minData(String[][] allData, String beginDate, String endDate) throws IOException{
//        //Get today's date
//        DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
//        Date currentDate = new Date();
//        String todaysDate = desiredDateFormat.format(currentDate);
//        
//        int ctr = 0;
//        for(int i=0; i<allData.length; i++){
//            if(todaysDate.equals(endDate)){
//                //If the end limit is today, keep future forcasted data as well
//                if((allData[i][0].compareTo(beginDate) >= 0)){
//                    ctr++;
//                }
//            }else{
//                //Check if the current data is within the date range, if so keep it
//                if((allData[i][0].compareTo(beginDate) >= 0) && (allData[i][0].compareTo(endDate) <= 0)){
//                    ctr++;
//                }
//            }
//        }
//
//        String[][] reducedData = new String[ctr][2];
//        ctr=0;
//        for(int i=0; i<allData.length; i++){
//            if(todaysDate.equals(endDate)){
//                //If the end limit is today, keep future forcasted data as well
//                if((allData[i][0].compareTo(beginDate) >= 0)){
//                    reducedData[ctr][0] = allData[i][0];//date
//                    reducedData[ctr][1] = allData[i][1];//value
//                    ctr++;
//                }
//            }else{
//                //Check if the current data is within the date range, if so keep it
//                if((allData[i][0].compareTo(beginDate) >= 0) && (allData[i][0].compareTo(endDate) <= 0)){
//                    reducedData[ctr][0] = allData[i][0];//date
//                    reducedData[ctr][1] = allData[i][1];//value
//                    ctr++;
//                }
//            }
//        }
//        return reducedData;
//    }
}