Skip to content

Commit

Permalink
Extended ILocalDatePeriod; #23
Browse files Browse the repository at this point in the history
  • Loading branch information
phax committed Jan 28, 2021
1 parent 44decad commit 30a378e
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import java.time.LocalDate;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.datetime.PDTFactory;
import com.helger.datetime.domain.IHasStartAndEnd;

Expand All @@ -30,27 +32,78 @@
*/
public interface ILocalDatePeriod extends IHasStartAndEnd <LocalDate>
{
/**
* Check if the provided query date is between start and end. A
* <code>null</code> start means "since forever". A <code>null</code> end
* means "until eternity and beyond".
*
* @param aStart
* Start date. May be <code>null</code>.
* @param bInclStart
* <code>true</code> if "start date" = "query date" should be a match,
* <code>false</code> if not.
* @param aEnd
* End date may be <code>null</code>.
* @param bInclEnd
* <code>true</code> if "end date" = "query date" should be a match,
* <code>false</code> if not.
* @param aQuery
* Date to query whether it is inside or not. May not be
* <code>null</code>.
* @return <code>true</code> if query date time &ge; start date time and &le;
* end date time
* @since 10.0.0
*/
static boolean isInside (@Nullable final LocalDate aStart,
final boolean bInclStart,
@Nullable final LocalDate aEnd,
final boolean bInclEnd,
@Nonnull final LocalDate aQuery)
{
ValueEnforcer.notNull (aQuery, "QueryDT");

if (aStart != null && (bInclStart ? aQuery.compareTo (aStart) < 0 : aQuery.compareTo (aStart) <= 0))
return false;

if (aEnd != null && (bInclEnd ? aQuery.compareTo (aEnd) > 0 : aQuery.compareTo (aEnd) >= 0))
return false;

return true;
}

/**
* Check if the provided date is inside this period, assuming that start and
* end are included in/part of the range.
*
* @param bInclBoundaries
* <code>true</code> if "start date" = "query date" should be a match
* i.e. if "end date" = "query date" should be a match,
* <code>false</code> if this should not be a match.
* @param aDate
* Date to check. May not be <code>null</code>.
* @return <code>true</code> if it is contained, <code>false</code> otherwise.
* @see #isInside(LocalDate, boolean, LocalDate, boolean, LocalDate)
* @since 10.0.0
*/
default boolean isInPeriod (final boolean bInclBoundaries, @Nonnull final LocalDate aDate)
{
return isInside (getStart (), bInclBoundaries, getEnd (), bInclBoundaries, aDate);
}

/**
* Check if the provided date is inside this period, assuming that start and
* end are included in/part of the range.
*
* @param aDate
* Date to check. May not be <code>null</code>.
* @return <code>true</code> if it is contained, <code>false</code> otherwise.
* @see #isInPeriod(boolean, LocalDate)
* @see #isNowInPeriodIncl()
* @since 8.6.5
*/
default boolean isInPeriodIncl (@Nonnull final LocalDate aDate)
{
final LocalDate aStart = getStart ();
if (aStart != null && aDate.compareTo (aStart) < 0)
return false;

final LocalDate aEnd = getEnd ();
if (aEnd != null && aDate.compareTo (aEnd) > 0)
return false;

return true;
return isInPeriod (true, aDate);
}

/**
Expand All @@ -59,6 +112,7 @@ default boolean isInPeriodIncl (@Nonnull final LocalDate aDate)
*
* @return <code>true</code> if the current date is contained,
* <code>false</code> otherwise.
* @see #isInPeriod(boolean, LocalDate)
* @see #isInPeriodIncl(LocalDate)
* @since 8.6.5
*/
Expand All @@ -74,20 +128,13 @@ default boolean isNowInPeriodIncl ()
* @param aDate
* Date to check. May not be <code>null</code>.
* @return <code>true</code> if it is contained, <code>false</code> otherwise.
* @see #isInPeriod(boolean, LocalDate)
* @see #isNowInPeriodExcl()
* @since 8.6.5
*/
default boolean isInPeriodExcl (@Nonnull final LocalDate aDate)
{
final LocalDate aStart = getStart ();
if (aStart != null && aDate.compareTo (aStart) <= 0)
return false;

final LocalDate aEnd = getEnd ();
if (aEnd != null && aDate.compareTo (aEnd) >= 0)
return false;

return true;
return isInPeriod (false, aDate);
}

/**
Expand All @@ -96,11 +143,77 @@ default boolean isInPeriodExcl (@Nonnull final LocalDate aDate)
*
* @return <code>true</code> if the current date is contained,
* <code>false</code> otherwise.
* @see #isInPeriod(boolean, LocalDate)
* @see #isInPeriodExcl(LocalDate)
* @since 8.6.5
*/
default boolean isNowInPeriodExcl ()
{
return isInPeriodExcl (PDTFactory.getCurrentLocalDate ());
}

/**
* Check if the provided range 1 has an overlap with the provided range 2.
*
* @param aStart1
* Start date of the first range. May be <code>null</code>.
* @param aEnd1
* End date of the first range. May be <code>null</code> meaning it's
* validity is "until eternity and beyond".
* @param aStart2
* Start date of the second range. May be <code>null</code>.
* @param aEnd2
* End date of the second range. May be <code>null</code> meaning it's
* validity is "until eternity and beyond".
* @param bInclBoundaries
* <code>true</code> if "start date" = "query date" should be a match
* i.e. if "end date" = "query date" should be a match,
* <code>false</code> if this should not be a match.
* @return <code>true</code> if the 2 ranges have at least one point in time
* (with duration 0) that they share.
* @since 10.0.0
*/
static boolean hasOverlap (@Nullable final LocalDate aStart1,
@Nullable final LocalDate aEnd1,
@Nullable final LocalDate aStart2,
@Nullable final LocalDate aEnd2,
final boolean bInclBoundaries)
{
if (aStart2 != null && isInside (aStart1, bInclBoundaries, aEnd1, bInclBoundaries, aStart2))
{
// New start date is in valid range
return true;
}
// New start date is out of range
if (aEnd2 != null && isInside (aStart1, bInclBoundaries, aEnd1, bInclBoundaries, aEnd2))
{
// New end date is in valid range
return true;
}

// start1 - end1: 5.1. - 15.1.
// start2 - end2: 1.1. - 31.1.
if (aStart1 != null && isInside (aStart2, bInclBoundaries, aEnd2, bInclBoundaries, aStart1))
return true;

if (aEnd1 != null && isInside (aStart2, bInclBoundaries, aEnd2, bInclBoundaries, aEnd1))
return true;

return false;
}

default boolean isOverlappingWith (@Nonnull final ILocalDatePeriod aPeriod, final boolean bInclBoundaries)
{
return hasOverlap (getStart (), getEnd (), aPeriod.getStart (), aPeriod.getEnd (), bInclBoundaries);
}

default boolean isOverlappingWithIncl (@Nonnull final ILocalDatePeriod aPeriod)
{
return isOverlappingWith (aPeriod, true);
}

default boolean isOverlappingWithExcl (@Nonnull final ILocalDatePeriod aPeriod)
{
return isOverlappingWith (aPeriod, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
import java.time.LocalDate;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import com.helger.commons.equals.EqualsHelper;
import com.helger.commons.hashcode.HashCodeGenerator;
import com.helger.commons.string.ToStringGenerator;

/**
* Default implementation of {@link ILocalDatePeriod}.
*
*
* @author Philip Helger
*/
@Immutable
public class LocalDatePeriod implements ILocalDatePeriod
{
private final LocalDate m_aStart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,126 @@ public void testBasic ()
assertFalse (p.isInPeriodExcl (d2));
assertFalse (p.isInPeriodExcl (aNow));
}

@Test
public void testIsInside ()
{
final LocalDate aNow = PDTFactory.getCurrentLocalDate ();
assertTrue (ILocalDatePeriod.isInside (null, true, null, true, aNow));
assertTrue (ILocalDatePeriod.isInside (null, false, null, false, aNow));
}

@Test
public void testDates ()
{
final LocalDate aNow = PDTFactory.getCurrentLocalDate ();
final LocalDate dt0 = aNow.minusDays (100);
final LocalDate dt1 = aNow.minusDays (10);
final LocalDate dt2 = aNow.minusDays (2);
final LocalDate dt3 = aNow.plusDays (2);
final LocalDate dt4 = aNow.plusDays (10);
final LocalDate dt5 = aNow.plusDays (100);

final ILocalDatePeriod r1 = new LocalDatePeriod (dt2, dt3);
assertFalse (r1.isInPeriodIncl (dt0));
assertFalse (r1.isInPeriodIncl (dt1));
assertTrue (r1.isInPeriodIncl (dt2));
assertTrue (r1.isInPeriodIncl (aNow));
assertTrue (r1.isInPeriodIncl (dt3));
assertFalse (r1.isInPeriodIncl (dt4));
assertFalse (r1.isInPeriodIncl (dt5));

assertFalse (r1.isInPeriodExcl (dt0));
assertFalse (r1.isInPeriodExcl (dt1));
assertFalse (r1.isInPeriodExcl (dt2));
assertTrue (r1.isInPeriodExcl (aNow));
assertFalse (r1.isInPeriodExcl (dt3));
assertFalse (r1.isInPeriodExcl (dt4));
assertFalse (r1.isInPeriodExcl (dt5));

final ILocalDatePeriod r2 = new LocalDatePeriod (dt1, dt4);
assertFalse (r2.isInPeriodIncl (dt0));
assertTrue (r2.isInPeriodIncl (dt1));
assertTrue (r2.isInPeriodIncl (dt2));
assertTrue (r2.isInPeriodIncl (aNow));
assertTrue (r2.isInPeriodIncl (dt3));
assertTrue (r2.isInPeriodIncl (dt4));
assertFalse (r2.isInPeriodIncl (dt5));

assertFalse (r2.isInPeriodExcl (dt0));
assertFalse (r2.isInPeriodExcl (dt1));
assertTrue (r2.isInPeriodExcl (dt2));
assertTrue (r2.isInPeriodExcl (aNow));
assertTrue (r2.isInPeriodExcl (dt3));
assertFalse (r2.isInPeriodExcl (dt4));
assertFalse (r2.isInPeriodExcl (dt5));

assertTrue (r1.isOverlappingWithIncl (r1));
assertTrue (r1.isOverlappingWithIncl (r2));
assertTrue (r2.isOverlappingWithIncl (r1));
assertTrue (r2.isOverlappingWithIncl (r2));

assertFalse (r1.isOverlappingWithExcl (r1));
assertTrue (r1.isOverlappingWithExcl (r2));
assertTrue (r2.isOverlappingWithExcl (r1));
assertFalse (r2.isOverlappingWithExcl (r2));

assertFalse (new LocalDatePeriod (dt0, dt1).isOverlappingWithIncl (new LocalDatePeriod (dt2, dt3)));
assertFalse (new LocalDatePeriod (dt0, dt1).isOverlappingWithExcl (new LocalDatePeriod (dt2, dt3)));

assertTrue (new LocalDatePeriod (dt0, dt1).isOverlappingWithIncl (new LocalDatePeriod (dt0, dt3)));
assertTrue (new LocalDatePeriod (dt0, dt1).isOverlappingWithExcl (new LocalDatePeriod (dt0, dt3)));

assertTrue (new LocalDatePeriod (dt0, dt1).isOverlappingWithIncl (new LocalDatePeriod (dt1, dt3)));
assertFalse (new LocalDatePeriod (dt0, dt1).isOverlappingWithExcl (new LocalDatePeriod (dt1, dt3)));

// Second period is partially open
assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithIncl (new LocalDatePeriod (null, dt5)));
assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithExcl (new LocalDatePeriod (null, dt5)));

assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithIncl (new LocalDatePeriod (null, dt2)));
assertFalse (new LocalDatePeriod (dt2, dt3).isOverlappingWithExcl (new LocalDatePeriod (null, dt2)));

assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithIncl (new LocalDatePeriod (dt0, null)));
assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithExcl (new LocalDatePeriod (dt0, null)));

assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithIncl (new LocalDatePeriod (dt3, null)));
assertFalse (new LocalDatePeriod (dt2, dt3).isOverlappingWithExcl (new LocalDatePeriod (dt3, null)));

assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithIncl (new LocalDatePeriod (null, null)));
assertTrue (new LocalDatePeriod (dt2, dt3).isOverlappingWithExcl (new LocalDatePeriod (null, null)));

// First period is partially open
assertTrue (new LocalDatePeriod (null, dt5).isOverlappingWithIncl (new LocalDatePeriod (dt2, dt3)));
assertTrue (new LocalDatePeriod (null, dt5).isOverlappingWithExcl (new LocalDatePeriod (dt2, dt3)));

assertTrue (new LocalDatePeriod (null, dt2).isOverlappingWithIncl (new LocalDatePeriod (dt2, dt3)));
assertFalse (new LocalDatePeriod (null, dt2).isOverlappingWithExcl (new LocalDatePeriod (dt2, dt3)));

assertTrue (new LocalDatePeriod (dt0, null).isOverlappingWithIncl (new LocalDatePeriod (dt2, dt3)));
assertTrue (new LocalDatePeriod (dt0, null).isOverlappingWithExcl (new LocalDatePeriod (dt2, dt3)));

assertTrue (new LocalDatePeriod (dt3, null).isOverlappingWithIncl (new LocalDatePeriod (dt2, dt3)));
assertFalse (new LocalDatePeriod (dt3, null).isOverlappingWithExcl (new LocalDatePeriod (dt2, dt3)));

assertTrue (new LocalDatePeriod (null, null).isOverlappingWithIncl (new LocalDatePeriod (dt2, dt3)));
assertTrue (new LocalDatePeriod (null, null).isOverlappingWithExcl (new LocalDatePeriod (dt2, dt3)));

// Both sides partially open
assertTrue (new LocalDatePeriod (dt1, null).isOverlappingWithIncl (new LocalDatePeriod (null, dt3)));
assertTrue (new LocalDatePeriod (dt1, null).isOverlappingWithExcl (new LocalDatePeriod (null, dt3)));

assertTrue (new LocalDatePeriod (dt1, null).isOverlappingWithIncl (new LocalDatePeriod (null, dt1)));
assertFalse (new LocalDatePeriod (dt1, null).isOverlappingWithExcl (new LocalDatePeriod (null, dt1)));

assertTrue (new LocalDatePeriod (dt1, null).isOverlappingWithIncl (new LocalDatePeriod (null, null)));
assertTrue (new LocalDatePeriod (dt1, null).isOverlappingWithExcl (new LocalDatePeriod (null, null)));

assertTrue (new LocalDatePeriod (null, null).isOverlappingWithIncl (new LocalDatePeriod (null, dt1)));
assertTrue (new LocalDatePeriod (null, null).isOverlappingWithExcl (new LocalDatePeriod (null, dt1)));

// No period, no overlap
assertFalse (new LocalDatePeriod (null, null).isOverlappingWithIncl (new LocalDatePeriod (null, null)));
assertFalse (new LocalDatePeriod (null, null).isOverlappingWithExcl (new LocalDatePeriod (null, null)));
}
}

0 comments on commit 30a378e

Please sign in to comment.