/* * $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.util.Calendar; import org.jcoderz.commons.ArgumentMalformedException; import org.jcoderz.commons.util.Assert; import org.jcoderz.commons.util.HashCodeUtil; /** * A <code>Period</code> data type represents a period in time. * <p> * There are two types of periods. One that is day bound and the other one * is milli-second bound. The day bound period has just set the hours, minutes, * seconds, and milli-seconds set to zero. In other words, the first one is * in day resolution whereas the second one is in milli-second resolution. * <p> * TODO: Refine * * @author Michael Rumpf */ public final class Period implements Serializable { /** The name of this type. */ public static final String TYPE_NAME = "Period"; /** use this serialVersionUID for serialization. */ static final long serialVersionUID = 128446267044986064L; private static final String DATE_PARAMETER = "date"; /** Lazy init hash code. */ private transient int mHashCode = 0; /** The start time of the period. */ private final Date mStartTime; /** The end time of the period. */ private final Date mEndTime; /** * This is for easy creation of objects from two longs. * * @param start The start date. * @param end The end date. */ private Period (Date start, Date end) { Assert.notNull(start, "start date"); Assert.notNull(end, "end date"); if (end.compareTo(start) < 0) { throw new ArgumentMalformedException(TYPE_NAME, "start: " + start + " end:" + end, "The end date must be larger or equal to the start date!"); } mStartTime = start; mEndTime = end; } /** * A factory method to create a period from two timestamps of the type * {@link org.jcoderz.commons.types.Date}. * * @param start The start time to create the period from. * @param end The end time to create the period from. * @return The period created by the two timestamps. * @throws ArgumentMalformedException when the end date is before the start * date. */ public static Period createPeriod (final Date start, final Date end) throws ArgumentMalformedException { return new Period(start, end); } /** * A factory method to create a period from two timestamps where the * hours, minutes, seconds, and milli-seconds are stripped off. * * @param start The start time to create the period from. * @param end The end time to create the period from. * @return The period created by the two timestamps. * @throws ArgumentMalformedException when the end date is before the start * date. */ public static Period createDayPeriod (Date start, Date end) throws ArgumentMalformedException { Assert.notNull(start, "start date"); Assert.notNull(end, "end date"); final Calendar s = getCalendarInstance(start); int year = s.get(Calendar.YEAR); int month = s.get(Calendar.MONTH); int day = s.get(Calendar.DAY_OF_MONTH); s.set(year, month, day, s.getMinimum(Calendar.HOUR_OF_DAY), s.getMinimum(Calendar.MINUTE), s.getMinimum(Calendar.SECOND)); s.set(Calendar.MILLISECOND, s.getMinimum(Calendar.MILLISECOND)); final Calendar e = getCalendarInstance(end); year = e.get(Calendar.YEAR); month = e.get(Calendar.MONTH); day = e.get(Calendar.DAY_OF_MONTH); e.set(year, month, day, e.getMaximum(Calendar.HOUR_OF_DAY), e.getMaximum(Calendar.MINUTE), e.getMaximum(Calendar.SECOND)); e.set(Calendar.MILLISECOND, e.getMaximum(Calendar.MILLISECOND)); return new Period( Date.fromUtilDate(s.getTime()), Date.fromUtilDate(e.getTime())); } /** * A factory method to create a day period. This period starts and ends on * the same day as the given <code>date</code>. The duration of this period * is exactly {@link Date#MILLIS_PER_DAY} milliseconds. * Example: for the date 2004-09-03T11:52:03.437Z this method will return the * period 2004-09-03T00:00:00.000Z-2004-09-03T23:59:59.999Z. * * @param date The date that falls within the returned period. * @return The period created by the two timestamps. * @throws ArgumentMalformedException when the parameter <code>date</code> is * null. */ public static Period createDayPeriod (Date date) throws ArgumentMalformedException { return Period.createDayPeriod(date, date); } /** * A factory method to create a month period. This period starts and ends on * the same month as the given <code>date</code>. The duration of this period * is exactly the duration of the month of the <code>date</code>. * Example: for the date 2004-09-03T11:52:03.437Z this method will return a * period 2004-09-01T00:00:00.000Z-2004-09-30T23:59:59.999Z. * * @param date The date that falls within the returned period. * @return The period created by the two timestamps. * @throws ArgumentMalformedException when the parameter <code>date</code> is * null. */ public static Period createMonthPeriod (Date date) throws ArgumentMalformedException { Assert.notNull(date, DATE_PARAMETER); final Calendar c = getCalendarInstance(date); c.set(Calendar.DAY_OF_MONTH, c.getMinimum(Calendar.DAY_OF_MONTH)); final Date s = new Date(c.getTimeInMillis()); c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH)); final Date e = new Date(c.getTimeInMillis()); return Period.createDayPeriod(s, e); } /** * Returns the start date of the period. * * @return A Date marking the start of the period. */ public Date getStartTime () { return mStartTime; } /** * Returns the end date of the period. * * @return A Date marking the end of the period. */ public Date getEndTime () { return mEndTime; } /** * Returns the intersection of two periods. * * @param other The period that will be intersected with this instance. * @return A new period that is the intersection between the two periods. * Null is returned in case there is no intersection between the two periods. */ public Period intersection (final Period other) { final Date start = Date.latest(mStartTime, other.mStartTime); final Date end = Date.earliest(mEndTime, other.mEndTime); final Period result; if (start.beforeOrEqual(end)) { result = new Period(start, end); } else { result = null; } return result; } /** * Returns the union of two periods. * * @param other The period to create a union period with this instance. * @return A new period that contains the intersection between the two * periods or null if the two periods do not intersect. */ public Period union (final Period other) { final Period result; if (overlap(other)) { final Date start = Date.earliest(mStartTime, other.mStartTime); final Date end = Date.latest(mEndTime, other.mEndTime); result = new Period(start, end); } else { result = null; } return result; } /** * Checks whether a specified date falls into the period defined by the * instance. * * @param date The date that is checked whether it lies in the period. * @return true when the date falls into the period, false otherwise. */ public boolean isIncluded (final Date date) { return mStartTime.beforeOrEqual(date) && mEndTime.afterOrEqual(date); } /** * Checks whether a specified period falls into the period defined by the * instance. * * @param other The period that is checked whether it lies in this period. * @return true when the period falls into the period, false otherwise. */ public boolean isIncluded (final Period other) { return mStartTime.beforeOrEqual(other.getStartTime()) && mEndTime.afterOrEqual(other.getEndTime()); } /** * Checks whether a specified period overlaps with this period. * * @param period The period to check for overlap with this period. * @return true when the periods overlap, false otherwise. */ public boolean overlap (final Period period) { return isIncluded(period.mStartTime) || period.isIncluded(mStartTime); } /** * Returns the start time for the next period. * * @return the start time for the next period. */ public Date getNextPeriodStartTime () { return mEndTime.plus(1); } /** * Returns the end time of the previous period. * * @return the end time of the previous period. */ public Date getPrevPeriodEndTime () { return mStartTime.minus(1); } /** * Returns the duration of this Period in milliseconds. * * @return The duration of this Period in milliseconds. */ public long duration () { return mEndTime.getTime() - mStartTime.getTime(); } /** * Returns the next Period. The duration of the next period is exactly the * same as duration of this Period. The next period starts 1 millisecond * after the end time of this period. * * @return The next Period. */ public Period next () { return new Period(getNextPeriodStartTime(), getNextPeriodStartTime().plus(duration())); } /** * Returns the previous Period. The duration of the previous period is * exactly the same as duration of this Period. The previous period ends 1 * millisecond before the start time of this period. * * @return The previous Period. */ public Period previous () { return new Period(getPrevPeriodEndTime().minus(duration()), getPrevPeriodEndTime()); } /** * Returns a Period object whose start/end time lies exactly * <code>offset</code> milliseconds after the start/end time of this period. * * @param offset The offset in milliseconds. * * @return The Period object whose start/end time lies exactly * <code>offset</code> milliseconds after the start/end time of this period. */ public Period plus (long offset) { return new Period(mStartTime.plus(offset), mEndTime.plus(offset)); } /** * Returns the next clock hour period, that lies after the * given <code>date</code>. * * Example: for the date 2004-09-03T11:52:03 this method will return the * period 2004-09-03T12:00:00.000Z-2004-09-03T12:59:59.999Z. * * @param date The date to get the next clock hour period. * * @return The next clock hour period, that lies after the given * <code>date</code>. */ public static Period nextHour (Date date) { Assert.notNull(date, DATE_PARAMETER); final Calendar s = getCalendarInstance(date); s.add(Calendar.HOUR, 1); resetMinorFields(s); return createPeriod(s, Date.MILLIS_PER_HOUR); } /** * Returns the next clock hour period, that lies after this period. * This method returns Period.nextHour(getEndTime()). * * @return the next clock hour period, that lies after this period. */ public Period nextHour () { return Period.nextHour(mEndTime); } /** * Returns the previous clock hour period, that lies befor the * given <code>date</code>. * * Example: for the date 2004-09-03T11:52:03 this method will return the * period 2004-09-03T10:00:00.000Z-2004-09-03T10:59:59.999Z. * * @param date The date to get the previous clock hour period. * * @return the previous clock hour period, that lies befor the * given <code>date</code>. */ public static Period previousHour (Date date) { Assert.notNull(date, DATE_PARAMETER); final Calendar s = getCalendarInstance(date); s.add(Calendar.HOUR, -1); resetMinorFields(s); return createPeriod(s, Date.MILLIS_PER_HOUR); } /** * Returns the next clock hour period, that lies this this period. * This method returns Period.previousHour(getSrartTime()). * * @return the next clock hour period, that lies before this period. */ public Period previousHour () { return Period.previousHour(mStartTime); } /** * Returns the day period, that lies after the given <code>date</code>. * * Example: for the date 2004-09-03T11:52:03 this method will return the * period 2004-09-04T00:00:00.000Z-2004-09-04T23:59:59.999Z. * * @param date The date to get the next day period. * * @return The day period, that lies after the given <code>date</code>. */ public static Period nextDay (Date date) { Assert.notNull(date, DATE_PARAMETER); final Date nextDay = date.plus(Date.MILLIS_PER_DAY); return Period.createDayPeriod(nextDay, nextDay); } /** * Returns the day period, that lies after this period. * This method returns Period.nextDay(getEndTime()). * @see #nextDay(Date) * * @return The day period, that lies after the given <code>date</code>. */ public Period nextDay () { return Period.nextDay(mEndTime); } /** * Returns the day period, that lies before the given <code>date</code>. * * Example: for the date 2004-09-03T11:52:03 this method will return the * period 2004-09-02T00:00:00.000Z-2004-09-02T23:59:59.999Z. * * @param date The date to get the previous day period. * * @return The day period, that lies before the given <code>date</code>. */ public static Period previousDay (Date date) { Assert.notNull(date, DATE_PARAMETER); final Date prevDay = date.minus(Date.MILLIS_PER_DAY); return Period.createDayPeriod(prevDay, prevDay); } /** * Returns the day period, that lies before this period. * This method returns Period.previousDay(getStartTime()). * @see #previousDay(Date) * * @return The the day period, that lies before this period. */ public Period previousDay () { return Period.previousDay(mStartTime); } /** * Returns the month period, that lies after the given <code>date</code>. * * Example: for the date 2004-09-03T11:52:03 this method will return the * period 2004-10-01T00:00:00.000Z-2004-10-31T23:59:59.999Z. * * @param date The date to get the next month period. * * @return The the month period, that lies after the given <code>date</code>. */ public static Period nextMonth (Date date) { Assert.notNull(date, DATE_PARAMETER); final Calendar c = getCalendarInstance(date); c.add(Calendar.MONTH, 1); return Period.createMonthPeriod(new Date(c.getTimeInMillis())); } /** * Returns the month period, that lies after this period. * This method returns Period.nextMonth(getEndTime()). * @see #nextMonth(Date) * * @return The the month period, that lies after this period. */ public Period nextMonth () { return Period.nextMonth(mEndTime); } /** * Returns the month period, that lies before the given <code>date</code>. * * Example: for the date 2004-09-03T11:52:03 this method will return the * period 2004-08-01T00:00:00.000Z-2004-08-31T23:59:59.999Z. * * @param date The date to get the previous month period. * * @return The the month period, that lies before the given * <code>date</code>. */ public static Period previousMonth (Date date) { Assert.notNull(date, DATE_PARAMETER); final Calendar c = getCalendarInstance(date); c.add(Calendar.MONTH, -1); return Period.createMonthPeriod(new Date(c.getTimeInMillis())); } /** * Returns the month period, that lies before this period. * This method returns Period.previousMonth(getStartTime()). * @see #previousMonth(Date) * * @return The the month period, that lies before this period. */ public Period previousMonth () { return Period.previousMonth(mStartTime); } /** * Returns a Period object whose start/end time lies exactly * <code>offset</code> milliseconds before the start/end time of this period. * * @param offset The offset in milliseconds. * * @return The Period object whose start/end time lies exactly * <code>offset</code> milliseconds before the start/end time of this period. */ public Period minus (long offset) { return new Period(mStartTime.minus(offset), mEndTime.minus(offset)); } /** * Checks if this period is before the given period. * @param other the period to compare with. * @return true, if this period is before the given period. */ public boolean before (final Period other) { Assert.notNull(other, "other"); return mEndTime.before(other.getStartTime()); } /** * Checks if this period is after the given period. * @param other the period to compare with. * @return true, if this period is after the given period. */ public boolean after (final Period other) { Assert.notNull(other, "other"); return mStartTime.after(other.getEndTime()); } /** {@inheritDoc} */ public boolean equals (Object o) { final boolean result; if (o instanceof Period) { final Period other = (Period) o; result = mStartTime.equals(other.mStartTime) && mEndTime.equals(other.mEndTime); } else { result = false; } return result; } /** {@inheritDoc} */ public int hashCode () { if (mHashCode == 0) { mHashCode = HashCodeUtil.SEED; mHashCode = HashCodeUtil.hash(mHashCode, mStartTime); mHashCode = HashCodeUtil.hash(mHashCode, mEndTime); } return mHashCode; } /** {@inheritDoc} */ public String toString () { return mStartTime.toString() + "-" + mEndTime.toString(); } private static void resetMinorFields (final Calendar s) { s.set(Calendar.MILLISECOND, s.getMinimum(Calendar.MILLISECOND)); s.set(Calendar.SECOND, s.getMinimum(Calendar.SECOND)); s.set(Calendar.MINUTE, s.getMinimum(Calendar.MINUTE)); } private static Period createPeriod (final Calendar s, final long periodDuration) { final Date start = new Date(s.getTimeInMillis()); return new Period(start, start.plus(periodDuration - 1)); } static Calendar getCalendarInstance (final Date date) { final Calendar s = Calendar.getInstance(Date.TIME_ZONE); s.setLenient(false); s.clear(); s.setTimeInMillis(date.getTime()); return s; } }