/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.utils;
import java.io.Serializable;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.time.DateUtils;
/**
* A date period, which can be configured not to take into account the time part in calculations by setting the useTime flag in false. Note that the
* useTime in false doesn't mean that the begin and end dates have a zero time. The useTime only interfere in calculations. <br>
* Also not that useTime==false is the default behavior. So by default the begin and end dates are truncated. If you don't want this behavior and need
* to use the times, you must explicitly set useTime to true.<br>
* You may also set the inclusiveBegin (default true) and inclusiveEnd (default true) flags.
*
* @author luis
*/
public class Period implements Serializable, Cloneable {
private static final long serialVersionUID = 6246167529034823152L;
public static Period begginingAt(final Calendar begin) {
return new Period(begin, null);
}
public static Period between(final Calendar begin, final Calendar end) {
return new Period(begin, end);
}
public static Period betweenOneYear(final int year) {
final Calendar begin = new GregorianCalendar(year, 0, 1);
final Calendar end = new GregorianCalendar(year, 11, 31, 23, 59, 59);
return between(begin, end);
}
public static Period day(Calendar day) {
day = DateHelper.truncate(day);
return new Period(day, day);
}
public static Period endingAt(final Calendar end) {
return new Period(null, end);
}
/**
* creates a period representing a day. If you set the setUseTime() flag afterwards, then it will represent a point in time.
* @param time
* @return
*/
public static Period exact(final Calendar time) {
return new Period(time, time);
}
private Calendar begin;
private Calendar end;
private boolean useTime;
private boolean inclusiveBegin = true;
private boolean inclusiveEnd = true;
public Period() {
}
public Period(final Calendar begin, final Calendar end) {
setBegin(begin);
setEnd(end);
}
@Override
public Period clone() {
Period newPeriod;
try {
newPeriod = (Period) super.clone();
} catch (final CloneNotSupportedException e) {
// this should never happen, since it is Cloneable
throw new InternalError(e.getMessage());
}
newPeriod.begin = begin == null ? null : (Calendar) begin.clone();
newPeriod.end = end == null ? null : (Calendar) end.clone();
return newPeriod;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Period)) {
return false;
}
final Period p = (Period) obj;
return new EqualsBuilder().append(begin, p.begin).append(end, p.end).isEquals();
}
public Calendar getBegin() {
return begin;
}
public Quarter getBeginQuarter() {
return getQuarter(begin);
}
/**
* Returns the number of days between the begin and end date. If either is null, returns -1.
* @see DateHelper#daysBetween(Calendar, Calendar)
*/
public int getDays() {
if (begin == null || end == null) {
return -1;
}
return DateHelper.daysBetween(begin, end);
}
/**
* @return The difference between beginDate and endDate from period in seconds
*/
public long getDifference() {
if (begin == null || end == null) {
throw new IllegalStateException("Not a complete period: " + this);
}
final double millis = end.getTimeInMillis() - begin.getTimeInMillis();
return (long) Math.ceil(millis / DateUtils.MILLIS_PER_SECOND);
}
public Calendar getEnd() {
return end;
}
public Quarter getEndQuarter() {
return getQuarter(end);
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(begin).append(end).toHashCode();
}
/**
* Checks whether the given date is included in this period. When the useTime flag is true, the time of the parameter (date) and the time of the
* begin and end dates of the period are taken into account to compute the result. If the exact begin date/time is included depends on the
* <code>inclusiveBegin</code> flag; by default, this is true, so begin date/time is considered to be <strong>included</strong> in the period. If
* the exact end date/time is included depends on the <code>inclusiveEnd</code> flag; which behaves in the same way as the inclusiveBegin flag.
* Both are true by default.<br>
* When the useTime flag is false, the dates are truncated to compute the result, and the inclusiveBegin and inclusiveEnd flags are ignored.
*/
public boolean includes(final Calendar date) {
if (date == null) {
return false;
} else if (begin == null && end == null) {
return true;
} else {
if (useTime) {
if (begin == null) {
return (inclusiveEnd) ? !end.before(date) : date.before(end);
} else if (end == null) {
return (inclusiveBegin) ? !date.before(begin) : begin.before(date);
} else {
final boolean beginOK = (inclusiveBegin) ? !date.before(begin) : begin.before(date);
final boolean endOK = (inclusiveEnd) ? !end.before(date) : date.before(end);
return beginOK && endOK;
}
} else {
final Calendar tDate = DateUtils.truncate(date, Calendar.DATE);
Calendar tBegin = begin;
Calendar tEnd = end;
if (begin != null) {
tBegin = DateUtils.truncate(begin, Calendar.DATE);
}
if (end != null) {
// If not using time, we'll assume the end of the interval is
// the instant before the next day.
tEnd = DateHelper.truncateNextDay(end);
}
if (tBegin == null) {
// it's included if the date is an instant before the next day.
return tDate.before(tEnd);
} else if (tEnd == null) {
// it's included if the date is not before the begin
return !tDate.before(tBegin);
} else {
return !tDate.before(tBegin) && tDate.before(tEnd);
}
}
}
}
public boolean isInclusiveBegin() {
return inclusiveBegin;
}
public boolean isInclusiveEnd() {
return inclusiveEnd;
}
public boolean isUseTime() {
return useTime;
}
public void setBegin(final Calendar begin) {
this.begin = begin;
}
public void setEnd(final Calendar end) {
this.end = end;
}
public void setInclusiveBegin(final boolean inclusiveBegin) {
this.inclusiveBegin = inclusiveBegin;
}
public void setInclusiveEnd(final boolean inclusiveEnd) {
this.inclusiveEnd = inclusiveEnd;
}
public void setUseTime(final boolean useTime) {
this.useTime = useTime;
}
@Override
public String toString() {
return "begin: " + FormatObject.formatObject(begin, "<null>") + ", end: " + FormatObject.formatObject(end, "<null>");
}
public Period useTime() {
useTime = true;
return this;
}
private Quarter getQuarter(final Calendar cal) {
final int month = cal.get(Calendar.MONTH);
int quarter = month / 3;
quarter++;
return Quarter.getQuarter(quarter);
}
}