/*
* $Id$
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.commons.types;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import org.jcoderz.commons.util.Constants;
import org.jcoderz.commons.util.HashCodeUtil;
/**
* Immutable holder of a Date running through our system.
*
* This class also holds some time related constants to be used in the code.
*
* Years before 1 are not fully supported and might result in wrong string
* representations.
*
* @author Andreas Mandel
*/
public final class Date
implements Comparable, Serializable
{
/** Date Formater to use for DATE_TIME_FORMAT_WITH_MILLIS format. */
public static final ThreadLocal DATE_TIME_FORMAT_WITH_MILLIS_FORMATER
= new ThreadLocal()
{
/**
* Thread local date formater.
* @see java.lang.ThreadLocal#initialValue()
*/
protected Object initialValue ()
{
final DateFormat formater
= new SimpleDateFormat(DATE_TIME_FORMAT_WITH_MILLIS,
Constants.SYSTEM_LOCALE);
formater.setTimeZone(TIME_ZONE);
formater.setLenient(false);
return formater;
}
};
/** Date Formater to use for DATE_TIME_FORMAT format. */
public static final ThreadLocal DATE_TIME_FORMAT_FORMATER
= new ThreadLocal()
{
/**
* Thread local date formater.
* @see java.lang.ThreadLocal#initialValue()
*/
protected Object initialValue ()
{
final DateFormat formater
= new SimpleDateFormat(DATE_TIME_FORMAT,
Constants.SYSTEM_LOCALE);
formater.setTimeZone(TIME_ZONE);
formater.setLenient(false);
return formater;
}
};
/**
* The name of this type.
*/
public static final String TYPE_NAME = "Date";
/**
* Timezone used by the protocol.
*/
public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
/** Number of nano seconds in a milli second. */
public static final long NANOS_PER_MILLI = 1000000L;
/** Number of milli seconds in a second. */
public static final int MILLIS_PER_SECOND = 1000;
/** Number of nano seconds in a second. */
public static final long NANOS_PER_SECOND
= NANOS_PER_MILLI * MILLIS_PER_SECOND;
/**
* Number of seconds in a minute.
*/
public static final int SECONDS_PER_MINUTE = 60;
/**
* Number of milli seconds in a minute.
*/
public static final int MILLIS_PER_MINUTE
= MILLIS_PER_SECOND * SECONDS_PER_MINUTE;
/**
* Number of milli seconds in an hour.
*/
public static final int MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60;
/**
* Number of milliseconds per day.
*/
public static final int MILLIS_PER_DAY = MILLIS_PER_MINUTE * 60 * 24;
/**
* Number of milliseconds per week.
*/
public static final int MILLIS_PER_WEEK = MILLIS_PER_DAY * 7;
/**
* Number of months per year.
*/
public static final int MONTH_PER_YEAR = 12;
/**
* This date represents the 1.1.1970 00:00:00.000.
*/
public static final Date OLD_DATE = new Date(0);
/**
* This date represents the largest possible date
* (9999 * 365 * 24 * 60 * 60 * 1000).
*/
public static final Date FUTURE_DATE = new Date(315328464000000L);
/**
* The format used to write schema dateTime types.
*/
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
/**
* The format used to write schema dateTime types, if milli seconds
* are not equal 0.
*/
public static final String DATE_TIME_FORMAT_WITH_MILLIS
= "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
/**
* The format used to parse and write schema date types.
*/
public static final String DATE_FORMAT = "yyyy-MM-dd'Z'";
/**
* use this serialVersionUID for serialization.
*/
static final long serialVersionUID = -5924234143761665938L;
/**
* The number of (milli)seconds since January 1, 1970, 00:00:00 GMT
* represented by this date.
* The millis are always reset to 0.
*/
private final long mTime;
/**
* Holds the string representation once it was computed.
*/
private transient String mString = null;
/**
* Creates a new instance of Date taking the given long as ms from
* 1.1.1970.
*
* @param time the number of milliseconds.
*
*/
public Date (long time)
{
mTime = time;
}
/**
* Creates a new instance of Date taking the given long as ms from
* 1.1.1970.
*
* @param time the number of milliseconds.
* @return a newly generated Date object taking the given long as ms from
* .1.1970.
* @see java.util.Date#Date()
*/
public static Date fromLong (long time)
{
return new Date(time);
}
/**
* Creates a new instance of Date from a java.util.Date Object.
* @param date the date as {@link java.sql.Date java.util.Date}.
* @return a newly generated Date object representing the same time as the
* given date.
*/
public static Date fromUtilDate (java.util.Date date)
{
return new Date(date.getTime());
}
/**
* Creates a new instance of Date holding the value as found in the given
* java.sql.Date.
* @param date the date as {@link java.sql.Date java.sql.Date}.
* @return a newly generated Date object representing the same time as the
* given date.
*/
public static Date fromSqlDate (java.sql.Date date)
{
return new Date(date.getTime());
}
/**
* Creates a new instance of Date holding the value as found in the given
* java.sql.Timestamp.
* @param timestamp the date as
* {@link java.sql.Timestamp java.sql.Timestamp}.
* @return a newly generated Date object representing the same time as the
* given date.
*/
public static Date fromSqlTimestamp (java.sql.Timestamp timestamp)
{
return new Date(timestamp.getTime());
}
/**
* Parses the given String with the given format pattern.
* NULL is returned if an empty string "" is given as parameter. (This is
* to allow empty elements as used in some places.)
* @param date the date in given notation
* @param pattern the pattern to be used for parsing.
* @return a newly generated Date object representing the time given in the
* date or NULL, if the given parameter was an empty string "".
* @throws ParseException if the given date is not in the format of the
* given pattern.
* @see SimpleDateFormat
*/
public static Date fromString (String date, String pattern)
throws ParseException
{
Date result = null;
if (date.length() != 0)
{
final SimpleDateFormat dateFormat = new SimpleDateFormat(pattern,
Constants.SYSTEM_LOCALE);
dateFormat.setLenient(false);
dateFormat.setTimeZone(TIME_ZONE);
result = Date.fromUtilDate(dateFormat.parse(date));
}
return result;
}
/**
* Parses the given String as returned by the toString method.
* @param date the date in String representation
* @return a newly generated Date object representing the time given in the
* date, or null if the input string was null.
* @throws ParseException if the given date is not in the format of the
* Date.toString() format.
*/
public static Date fromString (String date)
throws ParseException
{
Date result;
if (date != null && date.length() != 0)
{
try
{
result = Date.fromUtilDate(
((DateFormat) DATE_TIME_FORMAT_WITH_MILLIS_FORMATER.get()).
parse(date));
}
catch (ParseException x)
{
result = Date.fromUtilDate(
((DateFormat) DATE_TIME_FORMAT_FORMATER.get()).
parse(date));
}
}
else
{
result = null;
}
return result;
}
/**
* Returns a Date object that holds the current time.
* @return a newly generated Date object representing current time.
*/
public static Date now ()
{
return new Date(System.currentTimeMillis());
}
/**
* Returns a Date object that holds the current time plus the given
* milliseconds in the future.
* @param offset the number of millis to step into the future.
* @return a newly generated Date object.
*/
public static Date nowPlus (long offset)
{
return new Date(System.currentTimeMillis() + offset);
}
/**
* Returns the number of days since the Unix Epoch (1970/01/01).
* @return the number of days since the Unix Epoch (1970/01/01).
*/
public static int getDaysSinceEpoch ()
{
return (int) (System.currentTimeMillis() / MILLIS_PER_DAY);
}
/**
* Returns the number of days between Unix Epoch (1970/01/01) and
* <code>d</code>.
* @param d the date until when the number of days should be computed
* @return the number of days between Unix Epoch and <code>d</code>.
*/
public static int getDaysSinceEpoch (Date d)
{
return (int) (d.getTime() / MILLIS_PER_DAY);
}
/**
* Returns the earliest of the two dates.
* @param a date a to compare.
* @param b date b to compare.
* @return the earliest of the two dates.
*/
public static Date earliest (Date a, Date b)
{
return a.before(b) ? a : b;
}
/**
* Returns the latest of the two dates.
* @param a date a to compare.
* @param b date b to compare.
* @return the earliest of the two dates.
*/
public static Date latest (Date a, Date b)
{
return a.before(b) ? b : a;
}
/**
* Returns a Date object that holds the time of this object plus the given
* milliseconds in the future.
* @param offset the number of millis to step into the future.
* @return a newly generated Date object.
*/
public Date plus (long offset)
{
return new Date(mTime + offset);
}
/**
* Returns a Date object that holds the time of this object minus the given
* milliseconds in the past.
* @param offset the number of millis to step back from this date.
* @return a newly generated Date object.
*/
public Date minus (long offset)
{
return new Date(mTime - offset);
}
/**
* Returns the date represented by this object as newly generated
* {@link java.util.Date java.util.Date} object.
* @return the date represented by this object as newly generated
* {@link java.util.Date java.util.Date} object.
*/
public java.util.Date toUtilDate ()
{
return new java.util.Date(mTime);
}
/**
* Returns the date represented by this object as newly generated
* {@link java.sql.Date java.sql.Date} object.
* @return the date represented by this object as newly generated
* {@link java.sql.Date java.sql.Date} object.
*/
public java.sql.Date toSqlDate ()
{
return new java.sql.Date(mTime);
}
/**
* Returns the date represented by this object as newly generated
* {@link java.sql.Timestamp java.sql.Timestamp} object.
* @return the date represented by this object as newly generated
* {@link java.sql.Timestamp java.sql.Timestamp} object.
*/
public java.sql.Timestamp toSqlTimestamp ()
{
return new java.sql.Timestamp(mTime);
}
/**
* Returns a hash code value for this object. The result is the
* exclusive OR of the two halves of the primitive <tt>long</tt>
* value returned by the {@link Date#getTime()}
* method. That is, the hash code is the value of the expression:
* <blockquote><pre>
* (int)(this.getTime()^(this.getTime() >>> 32))</pre></blockquote>
*
* @return a hash code value for this object.
*/
public int hashCode ()
{
return HashCodeUtil.hash(HashCodeUtil.SEED, mTime);
}
/**
* Compares two dates for equality.
* @param obj the object to compare with.
* @return <code>true</code> if the objects are the same;
* <code>false</code> otherwise.
*/
public boolean equals (Object obj)
{
return ((obj instanceof Date) && (((Date) obj).mTime == mTime));
}
/**
* Returns the date as formatted string using the given pattern.
* @param pattern the pattern describing the date and time format
* @return the date as formatted string using the given pattern.
* @see SimpleDateFormat
*/
public String toString (String pattern)
{
final SimpleDateFormat dateFormat
= new SimpleDateFormat(pattern, Constants.SYSTEM_LOCALE);
dateFormat.setTimeZone(TIME_ZONE);
return dateFormat.format(new java.util.Date(mTime));
}
/**
* Returns the String representation format is according schema dateTime
* representation.
* @return the String representation format is according schema dateTime
* representation.
*/
public String toString ()
{
if (mString == null)
{
mString
= ((DateFormat) DATE_TIME_FORMAT_WITH_MILLIS_FORMATER.get()).
format(toUtilDate());
}
return mString;
}
/**
* Returns the String representation format is according schema date
* representation.
* @return the String representation format is according schema date
* representation.
*/
public String toDateString ()
{
// To gain speed we might use a thread local SimpleDateFormat
return toString(DATE_FORMAT);
}
/**
* Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
* represented by this Date object.
* @return the number of milliseconds since January 1, 1970, 00:00:00 GMT
* represented by this Date object.
*/
public long getTime ()
{
return mTime;
}
/**
* Compares this Date to another Object.
*
* @param o the <code>Object</code> to be compared.
* @return the value <code>0</code> if the argument is a Date
* equal to this Date; a value less than <code>0</code> if the
* argument is a Date after this Date; and a value greater than
* <code>0</code> if the argument is a Date before this Date.
* @exception ClassCastException if the argument is not a
* <code>org.jcoderz.ipp.Date</code>.
* @exception NullPointerException if the argument is <code>null</code>.
* @see java.lang.Comparable
*/
public int compareTo (Object o)
throws NullPointerException, ClassCastException
{
final Date other = ((Date) o);
int result = 0;
if (before(other))
{
result = -1;
}
else if (after(other))
{
result = 1;
}
return result;
}
/**
* Checks if the this date is before the given date.
* @param other the date to compare with.
* @return true, if the this date is before the given date.
*/
public boolean before (Date other)
{
return mTime < other.mTime;
}
/**
* Checks if the this date is before or equal to the given date.
* @param other the date to compare with.
* @return true, if the this date is before or equal to the given date.
*/
public boolean beforeOrEqual (Date other)
{
return mTime <= other.mTime;
}
/**
* Checks if this date is after the given date.
* @param other the date to compare with.
* @return true, if this date is after the given date.
*/
public boolean after (Date other)
{
return mTime > other.mTime;
}
/**
* Checks if this date is after or equal to the given date.
* @param other the date to compare with.
* @return true, if this date is after or equal to the given date.
*/
public boolean afterOrEqual (Date other)
{
return mTime >= other.mTime;
}
/**
* Returns the elapsed number of milliseconds from this date to now.
* @return the elapsed number of milliseconds from this date to now.
*/
public long elapsedMillis ()
{
return System.currentTimeMillis() - mTime;
}
/**
* Returns the elapsed number of milliseconds from this date to other.
* The result is negative if <tt>other</tt> is before this date.
* @param other the date to compare with this date.
* @return the elapsed number of milliseconds from this date to other.
*/
public long elapsedMillis (Date other)
{
return other.mTime - mTime;
}
/**
* Read resolve method to ensure the class invariants.
* @return a Date instance that surely fulfils the class invariants.
*/
private Object readResolve ()
{
return new Date(mTime);
}
}