Representing Time

When working with Date and Calendar objects, a thing that appears to be missing is a way to represent a 'quantity' of time. Although the Calendar object is very versatile, it doesn't really provide this capability.

The class that follows provides a means to do this. I am defining a time container that provides resolution from milliseconds to weeks. A Time object may be computed from 2 date or calendar objects; the time object would represent the difference between the two. Time objects may be added and subtracted from one another, and the object may be manipulated on a field by field basis. The signature of the class is heavily inspired by the Calendar class. The time object supports adding/setting with BigDecimals and doubles both for convienence and for accuracy.

Rather than yak about it anymore, here's the code:

package com.willcode4beer.beertime;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;

/**
 * Immutable representation of a quantity of time.
 
 * This can be used to represent the difference between two Date
 * objects or as a quantity to add to a Date.
 
 */
public class Time implements Serializable, Comparable
{
  /**
   * This is the internal value for this Time object.
   */
  private long millisecs = 0;

  /*
   * Time conversion values. These are used to convert to/from milliseconds
   * and the type specified.
   */
  private final static long CONV_SECS = 1000;
  private final static long CONV_MINS = CONV_SECS * 60;
  private final static long CONV_HOURS = CONV_MINS * 60;
  private final static long CONV_DAYS = CONV_HOURS * 24;
  private final static long CONV_WEEKS = CONV_DAYS * 7;

  private final static BigDecimal BD_CONV_SECS = new BigDecimal(CONV_SECS);
  private final static BigDecimal BD_CONV_MINS =
    BigDecimal.valueOf(CONV_SECS * 60);
  private final static BigDecimal BD_CONV_HOURS =
    BigDecimal.valueOf(CONV_MINS * 60);
  private final static BigDecimal BD_CONV_DAYS =
    BigDecimal.valueOf(CONV_HOURS * 24);
  private final static BigDecimal BD_CONV_WEEKS =
    BigDecimal.valueOf(CONV_DAYS * 7);

  /**
   * Default constructor represents a zero ammount of time.
   */
  public Time()
  {
  }

  /**
   * Translates a number of milliseconds into Time.
   @param millisecs
   */
  public Time(long millisecs)
  {
    this.millisecs = millisecs;
  }

  /**
   * Create a Time object based on the ammount of the field provided.
   * for example, new Time(TimeField.HOURS, new BigDecimal("3.5"));
   * creates a time object representing three and a half hours.
   @param field see the fields in TimeField
   @param ammount the quantity of the given unit of time.
   */
  public Time(TimeField field, BigDecimal ammount)
  {
    Time time = new Time();
    this.millisecs = time.addTime(field, ammount).millisecs;
  }

  /**
   * Create a time object based on the ammount of the field provided.
   * for example, new Time(TimeField.HOURS, 3.5);
   * creates a time object representing three and a half hours.
   @param field see the fields in TimeField
   @param ammount the quantity of the given unit of time.
   */
  public Time(TimeField field, double ammount)
  {
    Time time = new Time();
    this.millisecs = time.addTime(field, ammount).millisecs;
  }

  /**
   * Returns the difference between two Date objects (as a positive number).
   */
  public static Time getAbsDiff(Date date1, Date date2)
  {
    return new Time(Math.abs(date1.getTime() - date2.getTime()));
  }

  /**
   * Returns the difference between two Date objects.
   * The result will be negative if the first Date is after the 2nd Date.
   */
  public static Time getDiff(Date first, Date second)
  {
    return new Time(second.getTime() - first.getTime());
  }

  /**
   * Returns the difference between two Calendar objects (as a positive number).
   */
  public static Time getAbsDiff(Calendar date1, Calendar date2)
  {
    return new Time(
      Math.abs(date1.getTimeInMillis() - date2.getTimeInMillis()));
  }

  /**
   * Returns the difference between two Calendar objects.
   * The result will be negative if the first Calendar is after the 2nd Calendar.
   */
  public static Time getDiff(Calendar first, Calendar second)
  {
    return new Time(second.getTimeInMillis() - first.getTimeInMillis());
  }

  /**
   * Returns the integer value of the field specified.
   */
  public int get(TimeField field)
  {
    BigDecimal delta = null;
    switch (field.type)
    {
      case TimeField.FIELD_MILLISECONDS :
        break;
      case TimeField.FIELD_SECONDS :
        delta = BD_CONV_SECS;
        break;
      case TimeField.FIELD_MINUTES :
        delta = BD_CONV_MINS;
        break;
      case TimeField.FIELD_HOURS :
        delta = BD_CONV_HOURS;
        break;
      case TimeField.FIELD_DAYS :
        delta = BD_CONV_DAYS;
        break;
      case TimeField.FIELD_WEEKS :
        delta = BD_CONV_WEEKS;
        break;
      default :
        throw new IllegalArgumentException("Unsupported TimeField");
    }
    return new BigDecimal(millisecs)
      .divide(delta, BigDecimal.ROUND_UNNECESSARY)
      .intValue();
  }

  /**
   * Returns a BigDecimal representation of the value of the Time object.
   * The field parameter matches the fields in the Calendar class.
   * For example calling: getTime(TimeField.HOURS) will return a BigDecimal
   * with a value of this time object in hours.
   */
  public BigDecimal getBigDecimal(TimeField field)
  {
    BigDecimal delta = null;
    switch (field.type)
    {
      case TimeField.FIELD_MILLISECONDS :
        break;
      case TimeField.FIELD_SECONDS :
        delta = BD_CONV_SECS;
        break;
      case TimeField.FIELD_MINUTES :
        delta = BD_CONV_MINS;
        break;
      case TimeField.FIELD_HOURS :
        delta = BD_CONV_HOURS;
        break;
      case TimeField.FIELD_DAYS :
        delta = BD_CONV_DAYS;
        break;
      case TimeField.FIELD_WEEKS :
        delta = BD_CONV_WEEKS;
        break;
      default :
        throw new IllegalArgumentException("Unsupported TimeField");
    }
    return new BigDecimal(millisecs).divide(
      delta,
      BigDecimal.ROUND_UNNECESSARY);
  }

  /**
   * Returns a double representation of the value of the Time object.
   * The field parameter matches the fields in the Calendar class.
   * For example calling: getTime(TimeField.HOURS) will return a double
   * with a value of this time object in hours.
   */
  public double getDouble(TimeField field)
  {
    double delta = 0;
    switch (field.type)
    {
      case TimeField.FIELD_MILLISECONDS :
        delta = 1;
        break;
      case TimeField.FIELD_SECONDS :
        delta = CONV_SECS;
        break;
      case TimeField.FIELD_MINUTES :
        delta = CONV_MINS;
        break;
      case TimeField.FIELD_HOURS :
        delta = CONV_HOURS;
        break;
      case TimeField.FIELD_DAYS :
        delta = CONV_DAYS;
        break;
      case TimeField.FIELD_WEEKS :
        delta = CONV_WEEKS;
        break;
      default :
        throw new IllegalArgumentException("Unsupported TimeField");
    }
    return millisecs / delta;
  }

  /**
   * Returns the total number of milliseconds represented by this Time.
   * This simplifies adding time to Date and Calendar objects.
   */
  public long getTime()
  {
    return millisecs;
  }

  /**
   * Retunrs the sum of this time object and the provided one.
   */
  public Time addTime(Time time)
  {
    return new Time(millisecs + time.millisecs);
  }

  /**
   * Adds the double provided as the type defined by the field
   @param field Should match values from TimeField
   @param ammount the amount to increase this Time by
   */
  public Time addTime(TimeField field, double ammount)
  {
    double delta = 0;
    switch (field.type)
    {
      case TimeField.FIELD_MILLISECONDS :
        delta = 1;
        break;
      case TimeField.FIELD_SECONDS :
        delta = CONV_SECS;
        break;
      case TimeField.FIELD_MINUTES :
        delta = CONV_MINS;
        break;
      case TimeField.FIELD_HOURS :
        delta = CONV_HOURS;
        break;
      case TimeField.FIELD_DAYS :
        delta = CONV_DAYS;
        break;
      case TimeField.FIELD_WEEKS :
        delta = CONV_WEEKS;
        break;
      default :
        throw new IllegalArgumentException("Unsupported TimeField");
    }
    return new Time(((long) (delta * ammount)) + millisecs);
  }

  /**
   * Adds the BigDecimal provided as the type defined by the field
   @param field Should match values from TimeField
   @param ammount the amount to increase this Time by
   */
  public Time addTime(TimeField field, BigDecimal ammount)
  {
    BigDecimal delta = ammount;
    switch (field.type)
    {
      case TimeField.FIELD_MILLISECONDS :
        break;
      case TimeField.FIELD_SECONDS :
        delta = ammount.multiply(BD_CONV_SECS);
        break;
      case TimeField.FIELD_MINUTES :
        delta = ammount.multiply(BD_CONV_MINS);
        break;
      case TimeField.FIELD_HOURS :
        delta = ammount.multiply(BD_CONV_HOURS);
        break;
      case TimeField.FIELD_DAYS :
        delta = ammount.multiply(BD_CONV_DAYS);
        break;
      case TimeField.FIELD_WEEKS :
        delta = ammount.multiply(BD_CONV_WEEKS);
        break;
      default :
        throw new IllegalArgumentException("Unsupported TimeField");
    }
    return new Time(delta.add(new BigDecimal(millisecs)).longValue());
  }

  /**
   @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj)
  {
    boolean equal = false;
    if (obj == this)
    {
      equal = true;
    else
    {
      if (obj instanceof Time)
      {
        equal = (((Time)obj).millisecs == millisecs);
      }
    }
    return equal;
  }

  /**
   @see java.lang.Object#hashCode()
   */
  public int hashCode()
  {
    return (int) (millisecs * 37);
  }

  /**
   @see java.lang.Comparable#compareTo(java.lang.Object)
   */
  public int compareTo(Object obj)
  {
    int ret = ret = (-1);
    if (obj instanceof Time)
    {
      ret = (int) (millisecs - ((Time)obj).millisecs);
    }
    return ret;
  }

}


Sponsors:

About willCode4Beer