// Time.java

/**
 * Objects of this class represent a time
 * within a 24-hour day, ranging from
 * 00:00:00 to 23:59:59 when specified as
 * an hour, minute, and second; or ranging
 * from from 0 to 86399 when specified as
 * a total number of seconds since midnight.
 */
public class Time  {

   /** The hour in a 24-hour time */
   private byte hour;

   /** The minute in a 24-hour time */
   private byte minute;

   /** The second in a 24-hour time */
   private byte second;

   /**
    * Creates a Time object representing a time specified
    * as a total number of seconds since midnight.
    *
    * @param totalSeconds number of seconds since midnight.
    *       Must be in range 0 to 86499, inclusive.
    * @exception IllegalArgumentException if totalSeconds
    *       is out of range.
    */
   public Time(int totalSeconds)
   {
      if ( totalSeconds < 0 || totalSeconds >= 86400 )
          throw new IllegalArgumentException(
                 "Time: seconds since midnight "
                 + totalSeconds
                 + " must be >=0 and < 86400.");

      // Calculate values to assign to instance variables:
      hour = (byte) (totalSeconds / 3600);
      minute = (byte) (totalSeconds / 60 % 60);
      second = (byte) (totalSeconds % 60);
   }  // constructor(int)

   /**
    * Creates a Time object representing a time specified
    * as an hour, minute, and second in 24-hour time..
    *
    * @param h the hour.  Must be in range 0 to 23, inclusive.
    * @param m the minute.  Must be in range 0 to 59, inclusive.
    * @param s the second.  Must be in range 0 to 59, inclusive.
    * @exception IllegalArgumentException if any
    *      of the parameters are out of range.
    */
   public Time(int hour, int minute, int second)
   {
       if ( hour < 0 || hour >= 24 )
          throw new IllegalArgumentException(
                 "Time: hour " + hour
                 + " must be >=0 and < 24.");
       if ( minute < 0 || minute >= 60 )
          throw new IllegalArgumentException(
                 "Time: minute " + minute
                 + " must be >=0 and < 60.");
       if ( second < 0 || second >= 60 )
          throw new IllegalArgumentException(
                 "Time: second " + second
                 + " must be >=0 and < 60.");

       // Initialize instance variables:
       this.hour = (byte) hour;
       this.minute = (byte) minute;
       this.second = (byte) second;
   }  // constructor(int, int, int)

   /**
    * Returns the hour in a specification of this Time
    * as an hour, minute, and second in 24-hour time.
    */
   public byte getHour()  { return hour; }

   /**
    * Returns the minute in a specification of this Time
    * as an hour, minute, and second in 24-hour time.
    */
   public byte getMinute()  { return minute; }

   /**
    * Returns the second in a specification of this Time
    * as an hour, minute, and second in 24-hour time.
    */
   public byte getSecond()  { return second; }

   /**
    * Returns an integer specifying this Time
    * as a total number of seconds since midnight.
    */
   public int getTotalSeconds()
   {
      return (hour * 3600 + minute * 60 + second);
   } // method getTotalSeconds

   /**
    * Determines whether this Time is equal in value
    * to the parameter object.  A Time object if equal
    * to another object if the other object is also of
    * class Time and the two objects represent the
    * same time in a day.
    *
    * @param o the object to which this Time is
    *          being compared for equality.
    * @return true if this Time is equal to the
    *          parameter object, false otherwise.
    */
   public boolean equals(Object other)
   {
      return ( other != null
                  && getClass() == other.getClass()
                  && hour == ((Time) other).hour
                  && minute == ((Time) other).minute
                  && second == ((Time) other).second );
   }  // method equals

   /**
    * If a class defines the equals method, it must
    * also define a hashCode method such that
    * two objects that are "equal" according to the
    * equals method ALSO have the same hashCode.
    * 
    * @return a hash code for this object
    */
   public int hashCode()
   {
      // Exact details are unimportant as long
      // as two "equal" objects have the same
      // hash code.  One simple implementation,
      // good enough for this course, would be:

      return toString().hashCode();

      // This is okay ONLY IF toString() has
      // been defined in such a way that any two
      // objects of this class which are equal
      // according to the equals method also
      // return exactly equal strings when the
      // toString() method is called.
   }  // method hashCode

   /**
    * Compares this Time object to the parameter
    * Time object.  Times begin at midnight, so
    * the "earliest" time is midnight and the
    * "latest" time is one second before the
    * following midnight.
    *
    * @param other the Time with which this Time
    *              is being compared
    * @return a negative integer if this Time precedes 
    *         <code>other</code>, a positive integer if 
    *         <code>other</code> precedes this Time, or
    *         zero if the two times are equal.
    */
   public int compareTo(Time other)
   {
      if ( hour != other.hour )
         return ( hour - other.hour );
      if ( minute != other.minute )
         return ( minute - other.minute );
      return ( second - other.second );
   }  // method compareTo

   /**
    * Returns a String representing this Time
    * as a 24-hour time in HH:MM:SS format.
    */
   public String toString()
   {
      return padToTwoDigits(hour) + ":" +
             padToTwoDigits(minute) + ":" +
             padToTwoDigits(second);
   }  // method toString

   /**
    * Returns a String representing this Time
    * as a 12-hour time in HH:MM:SS format,
    * followed by " AM" or " PM".
    */
   public String to12HourString()
   {
      String AMorPM = ( hour < 12 ) ? " AM" : " PM";

      byte h = (byte) (hour % 12);
      if ( h == 0 )
         h = 12;

      return padToTwoDigits(h) + ":" +
             padToTwoDigits(minute) + ":" +
             padToTwoDigits(second) + AMorPM;
   }  // method to12HourString

   /**
    * Returns a String representation of the parameter
    * as a base-10 integer with a minimum of two digits,
    * padded with a leading zero if necessary.
    *
    * @param b the number to be formatted.
    * @return a 2-digit base-10 String representation of
    *       b, padded with a leading zero if necessary.
    */
   private static String padToTwoDigits(byte b)
   {
      return ( b < 10 ) ? ("0" + b) : Byte.toString(b);
   }  // method padToTwoDigits
}  // class Time