JEphem Informatic Trail Source code Time.java
//*********************************************************************************
// class tig.Time
// Software released under the General Public License (version 2 or later), available at
// http://www.gnu.org/copyleft/gpl.html
//*********************************************************************************
package tig;

import tig.GeneralConstants;
import tig.DateFormatException;
import tig.HourFormatException;
import tig.Formats;

/**********************************************************************************
Class containing utility static methods for date manipulation.
<BR>The manipulation of dates is based on the use of 'julian days'.
<BR>The internationalization of this class is hard coded. The reason is that <CODE>parseDate</CODE>
and <CODE>formatHourDate</CODE> methods are locale dependant and must be modified if new languages are added.
<BR>Internationalized error messages have been hard coded to keep this class independant
from properties files.

@author Thierry Graff
@history oct 04 2001 : creation from jephem.astro.spaceTime.Time.java.
@history oct 15 2001 : added jdToDate and formatHourDate.
@history feb 04 2002 : Separated date and hour parsing and formating.

@todo : WARNING : coherence of exceptions thrown by the different methods
**********************************************************************************/
public abstract class Time implements GeneralConstants{


  //=================================================================================
  //                                PRIVATE CONSTANTS
  //=================================================================================
  // codes used for languages
  private final static String EN = "en";
  private final static String FR = "fr";

  // Error message about lang
  private final static String ERROR_LANG = "Parameter 'lang' must be \"en\" or \"fr\"";

  //**************************** isLeapYear *************************************
  /**
  Tests if <CODE>y</CODE> corresponds to a leap year.
  @param y positive number representing the year to test.
  @return true if <CODE>y</CODE> is positive and corresponds to a leap year.
  */
  public static boolean isLeapYear(int y){
    if (y < 0) return false;
    if ((y % 4 == 0 && !(y % 100 == 0)) || (y % 400 == 0))
      return true;
    else
      return false;
  }// end isLeapYear

  //*************************** format methods from an int[] ***************************
  /** Formats a date and an hour into a displayable String.
  @param hourDate An array containing integers representing : year, month, day, hour, minut, second.
  @param lang language code, as specified by ISO-639 norm (supported languages : "en" and "fr").
  @return if lang='fr', format is "JJ/MM/AAAA HH:MM:SS" ;
  <BR>if lang='en', format is "MM/JJ/AAAA HH:MM:SS".</LI>

  @throws IllegalArgumentException if :
  <LI>the length of 'hourDate' is different from 6 ;</LI>
  <LI>the language is different from "en" or "fr".</LI>
  */
  public static String formatHourDate(int[] hourDate, String lang){
    // parameters checking
    if(hourDate.length != 6)
      throw new IllegalArgumentException("Parameter 'hourDate' must contain 6 integers");
    if(!(lang.equals(EN) || lang.equals(FR)))
      throw new IllegalArgumentException(ERROR_LANG);

    String strDate = formatDate(new int[]{hourDate[0], hourDate[1], hourDate[2]}, lang);
    String strHour = formatHour(new int[]{hourDate[3], hourDate[4], hourDate[5]}, lang);
    return strDate + " " + strHour;
  }// end formatHourDate(int[] hourDate, String lang)

  /** Formats a date into a displayable String.
  @param date An array containing 3 integers representing : year, month, day.
  @param lang language code, as specified by ISO-639 norm (supported languages : "en" and "fr").
  @return if lang='fr', format is "JJ/MM/AAAA" ;
  <BR>if lang='en', format is "MM/JJ/AAAA".</LI>

  @throws IllegalArgumentException if :
  <LI>the length of 'date' is different from 3 ;</LI>
  <LI>the language is different from "en" or "fr".</LI>
  */
  public static String formatDate(int[] date, String lang){
    // parameters checking
    if(date.length != 3)
      throw new IllegalArgumentException("Parameter 'date' must contain 3 integers");
    if(!(lang.equals(EN) || lang.equals(FR)))
      throw new IllegalArgumentException(ERROR_LANG);

    String[] strDate = new String[3];
    strDate[0] = String.valueOf(date[0]);
    for (int i = 1; i < 3; i++)
      strDate[i] = Formats.addZero(date[i]);
    if (lang.equals(EN)){
      return strDate[1] + "/" + strDate[2] + "/" + strDate[0];
    }
    else{ //if (lang.equals(FR)){
      return strDate[2] + "/" + strDate[1] + "/" + strDate[0];
    }
  }// end formatDate(int[] date, String lang)

  /** Formats an hour into a displayable String.
  @param hour An array containing 3 integers representing : hour, minut, second.
  @return A String formatted "HH:MM:SS", for lang='fr' or 'en';

  @throws IllegalArgumentException if :
  <LI>the length of 'hour' is different from 3 ;</LI>
  <LI>the language is different from "en" or "fr".</LI>
  */
  public static String formatHour(int[] hour, String lang){
    // parameters checking
    if(hour.length != 3)
      throw new IllegalArgumentException("Parameter 'hour' must contain 3 integers");
    if(!(lang.equals(EN) || lang.equals(FR)))
      throw new IllegalArgumentException(ERROR_LANG);

    String[] strHour = new String[3];
    for (int i = 0; i < 3; i++)
      strHour[i] = Formats.addZero(hour[i]);
    return strHour[0] + ":" +  strHour[1] + ":" + strHour[2];
  }// end formatHour(int[] hour, String lang)

  //*************************** format methods from a String ***************************
  /** Formats a date the same way {@link #formatHourDate(int[], String)} does.
  @throws DateFormatException if parameter 'strDate' is not correct.
  @throws HourFormatException if parameter 'strHour' is not correct.
  */
  public static String formatHourDate(String strDate, String strHour, String lang)
      throws DateFormatException, HourFormatException{
    try{
      int[] hourDate = parseHourDate(strDate, strHour, lang);
      return formatHourDate(hourDate, lang);
    }
    catch(HourFormatException e){ throw e;}
    catch(DateFormatException e){ throw e;}
  }// end formatHourDate(String strHourDate, String lang)

  /** Formats a date the same way {@link #formatDate(int[], String)} does.
  @throws DateFormatException if parameter 'strDate' is not correct.
  */
  public static String formatDate(String strDate, String lang)
      throws DateFormatException{
    try{
      int[] date = parseDate(strDate, lang);
      return formatDate(date, lang);
    }
    catch(DateFormatException e){ throw e;}
  }// end formatDate

  /** Formats a date the same way {@link #formatHour(int[], String)} does.
  @throws HourFormatException if parameter 'strHour' is not correct.
  */
  public static String formatHour(String strHour, String lang)
      throws HourFormatException{
    try{
      int[] hour = parseHour(strHour, lang);
      return formatHour(hour, lang);
    }
    catch(HourFormatException e){ throw e;}
  }// end formatHour

  //************************************** parse methods **************************************

  /** Transforms a date and an hour representated as Strings to an array of integers containing
  (in this order :  years, months, days, hours, minutes, seconds.
  <BR>This method just calls {@link #parseHour(String, String)}	and  {@link #parseDate(String, String)}.
  See their documentation for informations about parameter formats and exception thrown.
  */
  public static int[] parseHourDate(String strDate, String strHour, String lang)
                throws DateFormatException, HourFormatException, IllegalArgumentException{
    // check language
    if (!lang.equals(EN) && !lang.equals(FR))
      throw new IllegalArgumentException("Parameter 'lang' must be 'en' or 'fr'");

    try{
      int[] date = parseDate(strDate, lang);
      int[] hour = parseHour(strHour, lang);
      return new int[]{date[0], date[1], date[2], hour[0], hour[1], hour[2]};
    }
    catch(DateFormatException e){throw e;}
    catch(HourFormatException e){throw e;}
    catch(IllegalArgumentException e){throw e;}
  }// end parseHourDate

  /** Transforms a date representated as a String to an array of integers containing the year,
  month, day.
  <BR><B>How to express the date in 'strDate'</B> :
  <LI>Years, months and days must be separated by any non-numeric separator.</LI>
  <LI>Years, months and days can be expressed with any number of digits ; years can be preceeded by a
  minus sign (if you want to express a negative year, first put a separator, and then the '-').</LI>
  <LI>For English ('lang' = "en"), the order in 'strDate' must be months, days, years.</LI>
  <LI>For French ('lang' = "fr"), the order in 'strDate' must be days, months, years.</LI>

  <BR><BR><B>Example of valid inputs and results</B>.
  <BR>French :
  <TABLE BORDER="1">
  <TR><TH>strDate</TH><TH>years</TH><TH>months</TH><TH>days</TH></TR>
  <TR><TD>18/07/1998</TD> <TD>18</TD> <TD>7</TD> <TD>1998</TD></TR>
  <TR><TD>18.007:1998</TD> <TD>18</TD> <TD>7</TD> <TD>1998</TD></TR>
  <TR><TD>9-10 2000</TD> <TD>9</TD> <TD>10</TD> <TD>2000</TD></TR>
  <TR><TD>9 10 1</TD> <TD>9</TD> <TD>10</TD> <TD>1</TD></TR>
  <TR><TD>9 10 -16</TD> <TD>9</TD> <TD>10</TD> <TD>-16</TD></TR>
  <TR><TD>9-10--16</TD> <TD>9</TD> <TD>10</TD> <TD>-16</TD></TR>
  <TR><TD>9-10-16</TD> <TD>9</TD> <TD>10</TD> <TD>16</TD></TR>
  </TABLE>
  <BR>English :
  <TABLE BORDER="1">
  <TR><TH>strDate</TH><TH>year</TH><TH>Month</TH><TH>Day</TH></TR>
  <TR><TD>07/18/1998</TD> <TD>18</TD> <TD>7</TD> <TD>1998</TD></TR>
  <TR><TD>007:18.1998</TD> <TD>18</TD> <TD>7</TD> <TD>1998</TD></TR>
  <TR><TD>9-10 2000</TD> <TD>10</TD> <TD>9</TD> <TD>2000</TD></TR>
  <TR><TD>9 10 1</TD> <TD>9</TD> <TD>10</TD> <TD>1</TD></TR>
  <TR><TD>9 10 -16</TD> <TD>10</TD> <TD>9</TD> <TD>-16</TD></TR>
  <TR><TD>9-10--16</TD> <TD>10</TD> <TD>9</TD> <TD>-16</TD></TR>
  <TR><TD>9-10-16</TD> <TD>10</TD> <TD>9</TD> <TD>16</TD></TR>
  </TABLE>

  @param strDate A String containing the date, as specified above.

  @param lang language code, as specified by ISO-639 norm (supported languages : "en" and "fr").
  @return an array of integers in which are stored (in this order) years, months, days.
  @throws IllegalArgumentException if parameter 'lang' is different from 'en' or 'fr'.
  @throws DateFormatException if parameter 'strDate' is not correctly formatted.
  */
  public static int[] parseDate(String strDate, String lang)
                throws DateFormatException, IllegalArgumentException{

    // check language
    if (!lang.equals(EN) && !lang.equals(FR))
      throw new IllegalArgumentException("Parameter 'lang' must be 'en' or 'fr'");

    try{
      int[] results = new int[3];
      int len,i;
      String str1=BLANK, str2=BLANK, str3=BLANK, yearSep=BLANK;

      strDate = strDate.trim();

      // *** 1 - Date parsing
      len = strDate.length();
      i=0;
      // str1
      while(i 1 && yearSep.charAt(yearSep.length() - 1)=='-')
        results[0] = -results[0];

      //*** 2 - Check date validity.
      checkDate(results[0], results[1], results[2]);
      return results;
    }
    catch(DateFormatException e){throw e;}
  }// end parseDate

  /** Transforms an hour representated as a String to an array of integers containing
  (in this order : hours, minutes, seconds.
  <BR>Parameter 'lang' has no effect.
  <BR><B>How to express the hour in 'strHour'</B> :
  <LI>Hours, minuts and seconds must be separated by any non-numeric character</LI>
  <LI>Any non-numeric digit at the beginning or at the end of 'strHour" are skipped</LI>
  <LI>If 'strHour' is empty, hours, minuts, seconds will be considered as equal to 0.</LI>
  <LI>Seconds or {minutes and seconds} can be omitted, they will be considered as equal to 0.</LI>

  <BR><BR><B>Example of valid inputs and results</B>.
  <TABLE BORDER="1">
  <TR><TH>strHour</TH><TH>hours</TH><TH>minuts</TH><TH>seconds</TH></TR>
  <TR><TD>(empty string)</TD> <TD>0</TD> <TD>0</TD> <TD>0</TD></TR>
  <TR><TD>12</TD> <TD>12</TD> <TD>0</TD> <TD>0</TD></TR>
  <TR><TD>12h30m</TD> <TD>12</TD> <TD>30</TD> <TD>0</TD></TR>
  <TR><TD>-12:30:40s</TD> <TD>12</TD> <TD>30</TD> <TD>40</TD></TR>
  <TR><TD>000012h000030m00040</TD> <TD>12</TD> <TD>30</TD> <TD>40</TD></TR>
  </TABLE>

  @param strHour A String containing the hour, as specified above..
  @param lang language code, as specified by ISO-639 norm (supported languages : 'en' and 'fr').
  @return an array of integers in which are stored (in this order) hours, minuts, seconds.
  @throws HourFormatException if parameter 'strHour' is not correctly formatted.
  @throws IllegalArgumentException if parameter 'lang' is different from 'en' or 'fr'.
  */
  public static int[] parseHour(String strHour, String lang)
                throws HourFormatException, IllegalArgumentException{
    // check language
    if (!lang.equals(EN) && !lang.equals(FR))
      throw new IllegalArgumentException("Parameter 'lang' must be 'en' or 'fr'");

    try{
      int[] results = new int[3];
      int len,i;
      String str1, str2, str3;

      strHour = strHour.trim();

      //*** Hour parsing
      len = strHour.length();
      if (len == 0){ // hour not specified
        results[0] = 0; results[1] = 0; results[2] = 0; // hours, minuts, seconds
        return results;
      }
      str1=str2=str3=BLANK;
      i=0;
      // remove useless characters at the begining of String.
      while(iex : 23.75 means 23rd, 18h00m00s.
  */
  public static double calcDecDay(int d, int h, int m, int s){
    return d + h/24.0 + m/1440.0 + s/86400.0;
  }//end calcDecDay

  //=================================================================================
  //                                PRIVATE METHODS
  //=================================================================================

  //********************** checkHour ****************************
  /** Check hour validity. */
  private static void checkHour(int h, int m, int s) throws HourFormatException{
    if (h < 0 || h > 23) throw new HourFormatException("Hours must be between 0 and 23");
    if (m < 0 || m > 59) throw new HourFormatException("Minutes must be between 0 and 59");
    if (s < 0 || s > 59) throw new HourFormatException("Seconds must be between 0 and 59");
  }// end checkHour

  //********************** checkDate ****************************
  /** Check date validity. Parameters : year, month, day. */
  private static void checkDate(int y, int m, int d) throws DateFormatException{
    int[] dpm = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // days per month
    // month validity
    if (m < 1 || m > 12) throw new DateFormatException("Months must be between 1 and 12");
    // day validity
    if (m == 2){ // february
      if (isLeapYear(y)){
        if (d > 29) throw new DateFormatException("Days < 29 in february of a leap year");
      }
      else{
        if (d > 28) throw new DateFormatException("Days < 28 in february of a normal year");
      }
    }
    else{ // other months
      if (d > dpm[m-1]) throw new DateFormatException("Days of this can't be greater than "+ dpm[m-1]);
    }
  }// end checkDate


  //=================================================================================
  //=================================================================================
  //                            TESTS
  //=================================================================================
  //=================================================================================
/*
  // **************** For tests only ****************
  public static void main(String[] args){
    // no complete argument checking
    if(args[0].equalsIgnoreCase("testParseDate"))
      testParseDate(args[1], args[2]);
    else if(args[0].equalsIgnoreCase("testParseHour"))
      testParseHour(args[1], args[2]);
    else if(args[0].equalsIgnoreCase("testParseHourDate"))
      testParseHourDate(args[1], args[2], args[3]);
    else if(args[0].equalsIgnoreCase("testFormatDate"))
      testFormatDate(args[1], args[2]);
    else if(args[0].equalsIgnoreCase("testFormatHour"))
      testFormatHour(args[1], args[2]);
    else if(args[0].equalsIgnoreCase("testFormatHourDate"))
      testFormatHourDate(args[1], args[2], args[3]);
    else
      System.out.println("first argument must be 'testParseDate' or 'testParseDate'" +
      " or 'testFormatDate' or 'testFormatHour' or 'testFormatHourDate'");
  }// end main

  // **************** For tests only ****************
  private static void testParseDate(String strDate, String lang){
    try{
      String[] str = {"year", "month", "day"};
      int[] res = parseDate(strDate, lang);
      for(int i = 0; i < 3; i++)
        System.out.println(str[i] + " = " + res[i]);
    }
    catch(Exception e){e.printStackTrace();}
  }// end testParseDate

  // **************** For tests only ****************
  private static void testParseHour(String strHour, String lang){
    try{
      String[] str = {"h", "m", "s"};
      int[] res = parseHour(strHour, lang);
      for(int i = 0; i < 3; i++)
        System.out.println(str[i] + " = " + res[i]);
    }
    catch(Exception e){e.printStackTrace();}
  }// end testParseHour

  // **************** For tests only ****************
  private static void testParseHourDate(String strHour, String strDate, String lang){
    try{
      String[] str = {"year", "month", "day", "h", "m", "s"};
      int[] res = parseHourDate(strHour, strDate, lang);
      for(int i = 0; i < 6; i++)
        System.out.println(str[i] + " = " + res[i]);
    }
    catch(Exception e){e.printStackTrace();}
  }// end testParseHour

  // **************** For tests only ****************
  private static void testFormatDate(String strDate, String lang){
    try{
      System.out.println(formatDate(strDate, lang));
    }
    catch(Exception e){e.printStackTrace();}
  }// end testFormatDate

  // **************** For tests only ****************
  private static void testFormatHour(String strHour, String lang){
    try{
      System.out.println(formatHour(strHour, lang));
    }
    catch(Exception e){e.printStackTrace();}
  }// end testFormatDate

  // **************** For tests only ****************
  private static void testFormatHourDate(String strHour, String strDate, String lang){
    try{
      System.out.println(formatHourDate(strHour, strDate, lang));
    }
    catch(Exception e){e.printStackTrace();}
  }// end testFormatHourDate
*/
}//end abstract class Strings