Skip to content

Commit

Permalink
Improved period logic
Browse files Browse the repository at this point in the history
  • Loading branch information
benfortuna committed Jul 6, 2020
1 parent 696c62c commit 769f301
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 325 deletions.
34 changes: 24 additions & 10 deletions src/main/java/net/fortuna/ical4j/model/Period.java
Expand Up @@ -259,7 +259,7 @@ private static TemporalAmountAdapter parseDuration(String value) {
*/
public final TemporalAmount getDuration() {
if (duration == null) {
return TemporalAmountAdapter.from(getStart(), getEnd()).getDuration();
return TemporalAmountAdapter.from(start, end).getDuration();
}
return duration.getDuration();
}
Expand Down Expand Up @@ -301,7 +301,8 @@ public final boolean includes(final Date date, final boolean inclusive) {
*/
public final boolean includes(final Temporal date) {
Objects.requireNonNull(date, "date");
return toInterval().encloses(Interval.of(Instant.from(date), Duration.ZERO));
return start.equals(date) || end.equals(date)
|| toInterval().encloses(Interval.of(Instant.from(date), Duration.ZERO));
}

/**
Expand Down Expand Up @@ -348,31 +349,42 @@ public final Period<T> add(final Period<T> period) {
* If the specified period is completely contained in this period, the resulting list will contain two periods.
* Otherwise it will contain one.
*
* If the specified period does not interest this period a list containing this period is returned.
* If the specified period does not intersect this period a list containing this period is returned.
*
* If this period is completely contained within the specified period an empty period list is returned.
*
* @param period a period to subtract from this one
* @return a list containing zero, one or two periods.
*/
public final PeriodList<T> subtract(final Period<T> period) {
Interval thisInterval = TemporalAdapter.isFloating(getStart()) ? toInterval(ZoneId.systemDefault()) : toInterval();
Interval thatInterval = TemporalAdapter.isFloating(getStart()) ? period.toInterval(ZoneId.systemDefault()) : period.toInterval();
if (period.equals(this)) {
return new PeriodList<>(dateFormat);
} else if (!period.intersects(this) || start instanceof LocalDate) {
return new PeriodList<>(Collections.singletonList(this), dateFormat);
}
return subtractInterval(period);
}

private PeriodList<T> subtractInterval(Period<T> period) {
Interval thisInterval = TemporalAdapter.isFloating(getStart())
? toInterval(ZoneId.systemDefault()) : toInterval();
Interval thatInterval = TemporalAdapter.isFloating(getStart())
? period.toInterval(ZoneId.systemDefault()) : period.toInterval();

if (thatInterval.encloses(thisInterval)) {
return new PeriodList<>(period.dateFormat);
} else if (thatInterval.overlaps(thisInterval)) {
} else if (!thatInterval.overlaps(thisInterval)) {
return new PeriodList<>(Collections.singletonList(this));
}

final List<Period<T>> result = new ArrayList<>();

T newPeriodStart;
T newPeriodEnd;
if (thatInterval.getStart().isBefore(thisInterval.getStart())) {
if (!thatInterval.getStart().isAfter(thisInterval.getStart())) {
newPeriodStart = period.getEnd();
newPeriodEnd = getEnd();
} else if (thatInterval.getEnd().isAfter(thisInterval.getEnd())) {
} else if (!thatInterval.getEnd().isBefore(thisInterval.getEnd())) {
newPeriodStart = getStart();
newPeriodEnd = period.getStart();
} else {
Expand All @@ -394,6 +406,9 @@ public final PeriodList<T> subtract(final Period<T> period) {
* @return true if this period consumes no time, otherwise false
*/
public final boolean isEmpty() {
if (start instanceof LocalDate) {
return start.equals(end);
}
return toInterval().isEmpty();
}

Expand Down Expand Up @@ -460,8 +475,7 @@ public Interval toInterval() {
public Interval toInterval(ZoneId zoneId) {
if (start instanceof LocalDate) {
throw new UnsupportedOperationException("Unable to create Interval from date-only temporal.");
}
else if (start instanceof Instant) {
} else if (start instanceof Instant) {
return Interval.of((Instant) start, (Instant) end);
} else {
// calculate zone offset based on current applicable rules
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/net/fortuna/ical4j/model/PeriodList.java
Expand Up @@ -36,6 +36,7 @@
import org.threeten.extra.Interval;

import java.io.Serializable;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.Temporal;
import java.util.Arrays;
Expand Down Expand Up @@ -155,6 +156,9 @@ public final PeriodList<T> normalise() {
boolean normalised = false;
for (Period<T> period1 : periods) {
period = period1;
if (period.getStart() instanceof LocalDate) {
continue;
}
if (period.isEmpty()) {
period = prevPeriod;
normalised = true;
Expand Down Expand Up @@ -210,7 +214,7 @@ public final PeriodList<T> add(final PeriodList<T> periods) {
if (periods != null) {
final PeriodList<T> newList = new PeriodList<>(dateFormat);
newList.getPeriods().addAll(this.periods);
newList.getPeriods().addAll(this.periods);
newList.getPeriods().addAll(periods.periods);
return newList.normalise();
}
return this;
Expand All @@ -234,8 +238,13 @@ public final PeriodList<T> subtract(final PeriodList<T> subtractions) {
PeriodList<T> tmpResult = new PeriodList<>(dateFormat);

for (final Period<T> subtraction : subtractions.getPeriods()) {
for (final Period<T> period : result.getPeriods()) {
tmpResult.addAll(period.subtract(subtraction).getPeriods());
if (subtraction.getStart() instanceof LocalDate) {
tmpResult.addAll(result.getPeriods().stream()
.filter(p -> !p.equals(subtraction)).collect(Collectors.toList()));
} else {
for (final Period<T> period : result.getPeriods()) {
tmpResult.addAll(period.subtract(subtraction).getPeriods());
}
}
result = tmpResult;
tmpResult = new PeriodList<>();
Expand Down
Expand Up @@ -168,8 +168,14 @@ else if (value.matches("([+-])?P.*(W|D)")) {
}

public static TemporalAmountAdapter from(Temporal start, Temporal end) {
TemporalAmount duration = Duration.between(start, end);
return new TemporalAmountAdapter(duration);
if (start instanceof LocalDate) {
return from((LocalDate) start, (LocalDate) end);
}
return new TemporalAmountAdapter(Duration.between(start, end));
}

public static TemporalAmountAdapter from(LocalDate start, LocalDate end) {
return new TemporalAmountAdapter(Period.between(start, end));
}

public static TemporalAmountAdapter fromDateRange(Date start, Date end) {
Expand Down
99 changes: 99 additions & 0 deletions src/test/groovy/net/fortuna/ical4j/model/PeriodListSpec.groovy
Expand Up @@ -2,8 +2,77 @@ package net.fortuna.ical4j.model

import spock.lang.Specification

import java.time.LocalDate

class PeriodListSpec extends Specification {

static Period<LocalDate> monthJanuary, monthFebruary, monthMarch, monthApril,
monthMay, monthJune, monthJuly, monthAugust, monthSeptember, monthOctober,
monthNovember, monthDecember, head1994, tail1994

def setupSpec() {
// create ranges that are intervals
LocalDate begin1994 = LocalDate.now().withYear(1994).withMonth(1).withDayOfMonth(1);
LocalDate end1994 = begin1994.withMonth(12).withDayOfMonth(31);
LocalDate jan1994 = end1994.withMonth(1).withDayOfMonth(22);
LocalDate feb1994 = jan1994.withMonth(2).withDayOfMonth(15);
LocalDate mar1994 = feb1994.withMonth(3).withDayOfMonth(4);
LocalDate apr1994 = mar1994.withMonth(4).withDayOfMonth(12);
LocalDate may1994 = apr1994.withMonth(5).withDayOfMonth(19);
LocalDate jun1994 = may1994.withMonth(6).withDayOfMonth(21);
LocalDate jul1994 = jun1994.withMonth(7).withDayOfMonth(28);
LocalDate aug1994 = jul1994.withMonth(8).withDayOfMonth(20);
LocalDate sep1994 = aug1994.withMonth(9).withDayOfMonth(17);
LocalDate oct1994 = sep1994.withMonth(10).withDayOfMonth(29);
LocalDate nov1994 = oct1994.withMonth(11).withDayOfMonth(11);
LocalDate dec1994 = nov1994.withMonth(12).withDayOfMonth(2);

monthJanuary = new Period<>(jan1994, feb1994);
monthFebruary = new Period<>(feb1994, mar1994);
monthMarch = new Period<>(mar1994, apr1994);
monthApril = new Period<>(apr1994, may1994);
monthMay = new Period<>(may1994, jun1994);
monthJune = new Period<>(jun1994, jul1994);
monthJuly = new Period<>(jul1994, aug1994);
monthAugust = new Period<>(aug1994, sep1994);
monthSeptember = new Period<>(sep1994, oct1994);
monthOctober = new Period<>(oct1994, nov1994);
monthNovember = new Period<>(nov1994, dec1994);
monthDecember = new Period<>(dec1994, end1994);
head1994 = new Period<>(begin1994, jan1994);
tail1994 = new Period<>(dec1994, end1994);

// create sets that contain the ranges
List<Period<LocalDate>> oddMonths = new ArrayList<>();
oddMonths.add(monthJanuary);
oddMonths.add(monthMarch);
oddMonths.add(monthMay);
oddMonths.add(monthJuly);
oddMonths.add(monthSeptember);
oddMonths.add(monthNovember);
List<Period<LocalDate>> tailSet = new ArrayList<>();
tailSet.add(tail1994);

/*
* assertNull("Removing null from a null set should return null", empty1.subtract(null)); assertNull("Removing
* from a null set should return null", normalizer.subtractDateRanges(null, headSet));
*/
PeriodList<LocalDate> evenMonths = new PeriodList<>();
evenMonths.add(monthFebruary);
evenMonths.add(monthApril);
evenMonths.add(monthJune);
evenMonths.add(monthAugust);
evenMonths.add(monthOctober);
evenMonths.add(monthDecember);

PeriodList<LocalDate> headSet = new PeriodList<>();
headSet.add(head1994);

PeriodList<LocalDate> empty1 = new PeriodList<>();
PeriodList<LocalDate> empty2 = new PeriodList<>();

}

def 'test hashcode equality'() {
given: 'a period list'
PeriodList list1 = PeriodList.parse('20140803T120100/P1D')
Expand All @@ -17,4 +86,34 @@ class PeriodListSpec extends Specification {
and: 'hashcode equality'
list1.hashCode() == list2.hashCode()
}

def 'test addition'() {
given: 'two period lists added'
def sum = new PeriodList(periodList1).add(new PeriodList(periodList2))

expect: 'result is as expected'
sum == new PeriodList(expectedSum)

where:
periodList1 | periodList2 | expectedSum
[monthNovember, monthDecember] | [monthNovember, monthJuly] | [monthJuly, monthNovember, monthDecember]
[monthOctober, monthNovember, monthDecember] | [monthNovember] | [monthOctober, monthNovember, monthDecember]
[monthNovember, monthDecember] | [monthOctober, monthNovember] | [monthOctober, monthNovember, monthDecember]
}

def 'test subtraction'() {
given: 'a period list subtracted from another'
def sum = new PeriodList(periodList1).subtract(new PeriodList(periodList2))

expect: 'result is as expected'
sum == new PeriodList(expectedSum)

where:
periodList1 | periodList2 | expectedSum
[monthNovember, monthDecember] | [monthNovember] | [monthDecember]
[monthOctober, monthNovember, monthDecember] | [monthNovember] | [monthOctober, monthDecember]
[monthNovember, monthDecember] | [monthOctober, monthNovember] | [monthDecember]
[monthSeptember, monthOctober, monthNovember, monthDecember] | [monthOctober, monthNovember] | [monthSeptember, monthDecember]
[monthSeptember, monthOctober, monthNovember, monthDecember] | [monthApril, monthMay] | [monthSeptember, monthOctober, monthNovember, monthDecember]
}
}

0 comments on commit 769f301

Please sign in to comment.