mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-16 05:15:22 +00:00
8146218: Add LocalDate.datesUntil method producing Stream<LocalDate
Reviewed-by: scolebourne, rriggs, sherman
This commit is contained in:
parent
7f0667866d
commit
c2f738c53d
@ -100,6 +100,8 @@ import java.time.temporal.ValueRange;
|
||||
import java.time.zone.ZoneOffsetTransition;
|
||||
import java.time.zone.ZoneRules;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.LongStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A date without a time-zone in the ISO-8601 calendar system,
|
||||
@ -1715,6 +1717,89 @@ public final class LocalDate
|
||||
return Period.of(Math.toIntExact(years), months, days);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sequential ordered stream of dates. The returned stream starts from this date
|
||||
* (inclusive) and goes to {@code endExclusive} (exclusive) by an incremental step of 1 day.
|
||||
* <p>
|
||||
* This method is equivalent to {@code datesUntil(endExclusive, Period.ofDays(1))}.
|
||||
*
|
||||
* @param endExclusive the end date, exclusive, not null
|
||||
* @return a sequential {@code Stream} for the range of {@code LocalDate} values
|
||||
* @throws IllegalArgumentException if end date is before this date
|
||||
* @since 9
|
||||
*/
|
||||
public Stream<LocalDate> datesUntil(LocalDate endExclusive) {
|
||||
long end = endExclusive.toEpochDay();
|
||||
long start = toEpochDay();
|
||||
if (end < start) {
|
||||
throw new IllegalArgumentException(endExclusive + " < " + this);
|
||||
}
|
||||
return LongStream.range(start, end).mapToObj(LocalDate::ofEpochDay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sequential ordered stream of dates by given incremental step. The returned stream
|
||||
* starts from this date (inclusive) and goes to {@code endExclusive} (exclusive).
|
||||
* <p>
|
||||
* The n-th date which appears in the stream is equal to {@code this.plus(step.multipliedBy(n))}
|
||||
* (but the result of step multiplication never overflows). For example, if this date is
|
||||
* {@code 2015-01-31}, the end date is {@code 2015-05-01} and the step is 1 month, then the
|
||||
* stream contains {@code 2015-01-31}, {@code 2015-02-28}, {@code 2015-03-31}, and
|
||||
* {@code 2015-04-30}.
|
||||
*
|
||||
* @param endExclusive the end date, exclusive, not null
|
||||
* @param step the non-zero, non-negative {@code Period} which represents the step.
|
||||
* @return a sequential {@code Stream} for the range of {@code LocalDate} values
|
||||
* @throws IllegalArgumentException if step is zero, or {@code step.getDays()} and
|
||||
* {@code step.toTotalMonths()} have opposite sign, or end date is before this date
|
||||
* and step is positive, or end date is after this date and step is negative
|
||||
* @since 9
|
||||
*/
|
||||
public Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step) {
|
||||
if (step.isZero()) {
|
||||
throw new IllegalArgumentException("step is zero");
|
||||
}
|
||||
long end = endExclusive.toEpochDay();
|
||||
long start = toEpochDay();
|
||||
long until = end - start;
|
||||
long months = step.toTotalMonths();
|
||||
long days = step.getDays();
|
||||
if ((months < 0 && days > 0) || (months > 0 && days < 0)) {
|
||||
throw new IllegalArgumentException("period months and days are of opposite sign");
|
||||
}
|
||||
if (until == 0) {
|
||||
return Stream.empty();
|
||||
}
|
||||
int sign = months > 0 || days > 0 ? 1 : -1;
|
||||
if (sign < 0 ^ until < 0) {
|
||||
throw new IllegalArgumentException(endExclusive + (sign < 0 ? " > " : " < ") + this);
|
||||
}
|
||||
if (months == 0) {
|
||||
long steps = (until - sign) / days; // non-negative
|
||||
return LongStream.rangeClosed(0, steps).mapToObj(
|
||||
n -> LocalDate.ofEpochDay(start + n * days));
|
||||
}
|
||||
// 48699/1600 = 365.2425/12, no overflow, non-negative result
|
||||
long steps = until * 1600 / (months * 48699 + days * 1600) + 1;
|
||||
long addMonths = months * steps;
|
||||
long addDays = days * steps;
|
||||
long maxAddMonths = months > 0 ? MAX.getProlepticMonth() - getProlepticMonth()
|
||||
: getProlepticMonth() - MIN.getProlepticMonth();
|
||||
// adjust steps estimation
|
||||
if (addMonths * sign > maxAddMonths
|
||||
|| (plusMonths(addMonths).toEpochDay() + addDays) * sign >= end * sign) {
|
||||
steps--;
|
||||
addMonths -= months;
|
||||
addDays -= days;
|
||||
if (addMonths * sign > maxAddMonths
|
||||
|| (plusMonths(addMonths).toEpochDay() + addDays) * sign >= end * sign) {
|
||||
steps--;
|
||||
}
|
||||
}
|
||||
return LongStream.rangeClosed(0, steps).mapToObj(
|
||||
n -> this.plusMonths(months * n).plusDays(days * n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats this date using the specified formatter.
|
||||
* <p>
|
||||
|
||||
@ -119,6 +119,8 @@ import java.time.temporal.UnsupportedTemporalTypeException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
@ -2385,4 +2387,204 @@ public class TCKLocalDate extends AbstractDateTimeTest {
|
||||
assertSame(isoEra,IsoEra.CE);
|
||||
assertSame(LocalDate.MIN.getEra(),IsoEra.BCE);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// datesUntil()
|
||||
// ----------------------------------------------------------------
|
||||
@Test
|
||||
public void test_datesUntil() {
|
||||
assertEquals(
|
||||
date(2015, 9, 29).datesUntil(date(2015, 10, 3)).collect(
|
||||
Collectors.toList()), Arrays.asList(date(2015, 9, 29),
|
||||
date(2015, 9, 30), date(2015, 10, 1), date(2015, 10, 2)));
|
||||
assertEquals(date(2015, 9, 29).datesUntil(date(2015, 10, 3), Period.ofDays(2))
|
||||
.collect(Collectors.toList()), Arrays.asList(date(2015, 9, 29),
|
||||
date(2015, 10, 1)));
|
||||
assertEquals(date(2015, 1, 31).datesUntil(date(2015, 6, 1), Period.ofMonths(1))
|
||||
.collect(Collectors.toList()), Arrays.asList(date(2015, 1, 31),
|
||||
date(2015, 2, 28), date(2015, 3, 31), date(2015, 4, 30),
|
||||
date(2015, 5, 31)));
|
||||
}
|
||||
|
||||
@Test(expectedExceptions=NullPointerException.class)
|
||||
public void test_datesUntil_nullEnd() {
|
||||
LocalDate date = date(2015, 1, 31);
|
||||
date.datesUntil(null);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions=NullPointerException.class)
|
||||
public void test_datesUntil_nullEndStep() {
|
||||
LocalDate date = date(2015, 1, 31);
|
||||
date.datesUntil(null, Period.ofDays(1));
|
||||
}
|
||||
|
||||
@Test(expectedExceptions=NullPointerException.class)
|
||||
public void test_datesUntil_nullStep() {
|
||||
LocalDate date = date(2015, 1, 31);
|
||||
date.datesUntil(date, null);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void test_datesUntil_endBeforeStart() {
|
||||
date(2015, 1, 31).datesUntil(date(2015, 1, 30));
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void test_datesUntil_endBeforeStartPositiveStep() {
|
||||
date(2015, 1, 31).datesUntil(date(2015, 1, 30), Period.of(1, 0, 0));
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void test_datesUntil_endAfterStartNegativeStep() {
|
||||
date(2015, 1, 30).datesUntil(date(2015, 1, 31), Period.of(0, -1, -1));
|
||||
}
|
||||
|
||||
@Test(expectedExceptions=IllegalArgumentException.class)
|
||||
public void test_datesUntil_zeroStep() {
|
||||
LocalDate date = date(2015, 1, 31);
|
||||
date.datesUntil(date, Period.ZERO);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions=IllegalArgumentException.class)
|
||||
public void test_datesUntil_oppositeSign() {
|
||||
LocalDate date = date(2015, 1, 31);
|
||||
date.datesUntil(date, Period.of(1, 0, -1));
|
||||
}
|
||||
|
||||
@Test(expectedExceptions=IllegalArgumentException.class)
|
||||
public void test_datesUntil_oppositeSign2() {
|
||||
LocalDate date = date(2015, 1, 31);
|
||||
date.datesUntil(date, Period.of(0, -1, 1));
|
||||
}
|
||||
|
||||
@DataProvider(name="datesUntil")
|
||||
public Object[][] provider_datesUntil() {
|
||||
return new Object[][] {
|
||||
{MIN_DATE, MIN_DATE},
|
||||
{MIN_DATE, MAX_DATE},
|
||||
{MAX_DATE, MAX_DATE},
|
||||
{date(2015,10,1), date(2015,10,2)},
|
||||
{date(2015,10,1), date(2015,11,1)},
|
||||
{date(2015,10,31), date(2015,11,1)},
|
||||
{date(2015,10,1), MAX_DATE},
|
||||
{MIN_DATE, date(2015,10,1)}
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "datesUntil")
|
||||
public void test_datesUntil_count(LocalDate start, LocalDate end) {
|
||||
assertEquals(start.datesUntil(end).count(), start.until(end, ChronoUnit.DAYS));
|
||||
assertEquals(start.datesUntil(end, Period.ofDays(1)).count(),
|
||||
start.until(end, ChronoUnit.DAYS));
|
||||
}
|
||||
|
||||
@DataProvider(name="datesUntilSteps")
|
||||
public Object[][] provider_datesUntil_steps() {
|
||||
List<Object[]> data = new ArrayList<>(Arrays.asList(new Object[][] {
|
||||
{MIN_DATE, MAX_DATE, Period.ofYears(Year.MAX_VALUE)},
|
||||
{MIN_DATE, MAX_DATE, Period.ofDays(2)},
|
||||
{MIN_DATE, MAX_DATE, Period.of(1,2,3)},
|
||||
{MIN_DATE, MAX_DATE, Period.of(1,2,1000000)},
|
||||
{MIN_DATE, MAX_DATE, Period.of(1,1000000,3)},
|
||||
{MIN_DATE, MAX_DATE, Period.of(1000000,2,3)},
|
||||
{MIN_DATE, MIN_DATE.plusMonths(1), Period.ofMonths(1)},
|
||||
{MIN_DATE, date(Year.MIN_VALUE, 2, 2), Period.ofMonths(1)},
|
||||
{MIN_DATE, date(Year.MIN_VALUE, 8, 9), Period.of(0, 1, 1)},
|
||||
{MIN_DATE, MAX_DATE.minusYears(1), Period.ofYears(Year.MAX_VALUE)},
|
||||
{MAX_DATE.minusMonths(1), MAX_DATE, Period.ofMonths(1)},
|
||||
{date(Year.MAX_VALUE, 2, 20), MAX_DATE, Period.of(0, 1, 1)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.ofYears(1)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.ofDays(365)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.ofDays(366)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.ofDays(4)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.of(0,1,2)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.ofMonths(1)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.ofMonths(12)},
|
||||
{date(2015,1,1), date(2016,1,2), Period.ofMonths(12)},
|
||||
{date(2015,1,1), date(2016,1,1), Period.of(0, 11, 30)},
|
||||
{date(2015,1,1), date(2015,12,31), Period.of(0, 11, 30)},
|
||||
{date(2015,1,31), date(2015,12,31), Period.ofMonths(2)},
|
||||
{date(2015,1,31), date(2015,12,1), Period.ofMonths(2)},
|
||||
{date(2015,1,31), date(2015,11,30), Period.ofMonths(2)},
|
||||
{date(2015,1,31), date(2030,11,30), Period.of(1,30,365)},
|
||||
{date(2015,1,31), date(2043,1,31), Period.of(4,0,0)},
|
||||
{date(2015,1,31), date(2043,2,1), Period.of(4,0,0)},
|
||||
{date(2015,1,31), date(2043,1,31), Period.of(3,11,30)},
|
||||
{date(2015,1,31), date(2043,2,1), Period.of(3,11,30)},
|
||||
{date(2015,1,31), date(2043,1,31), Period.of(0,0,1460)},
|
||||
{date(2015,1,31), date(2043,1,31), Period.of(0,0,1461)},
|
||||
{date(2015,1,31), date(2043,2,1), Period.of(0,0,1461)},
|
||||
{date(2015,1,31), MAX_DATE, Period.of(10,100,1000)},
|
||||
{date(2015,1,31), MAX_DATE, Period.of(1000000,10000,100000)},
|
||||
{date(2015,1,31), MAX_DATE, Period.ofDays(10000000)},
|
||||
{date(2015,1,31), MAX_DATE, Period.ofDays(Integer.MAX_VALUE)},
|
||||
{date(2015,1,31), MAX_DATE, Period.ofMonths(Integer.MAX_VALUE)},
|
||||
{date(2015,1,31), MAX_DATE, Period.ofYears(Integer.MAX_VALUE)}
|
||||
}));
|
||||
LocalDate start = date(2014, 1, 15);
|
||||
LocalDate end = date(2015, 3, 4);
|
||||
for (int months : new int[] { 0, 1, 2, 3, 5, 7, 12, 13 }) {
|
||||
for (int days : new int[] { 0, 1, 2, 3, 5, 10, 17, 27, 28, 29, 30, 31, 32, 57, 58, 59,
|
||||
60, 61, 62, 70, 80, 90 }) {
|
||||
if (months > 0 || days > 0)
|
||||
data.add(new Object[] { start, end, Period.of(0, months, days) });
|
||||
}
|
||||
}
|
||||
for (int days = 27; days < 100; days++) {
|
||||
data.add(new Object[] { start, start.plusDays(days), Period.ofMonths(1) });
|
||||
}
|
||||
return data.toArray(new Object[data.size()][]);
|
||||
}
|
||||
|
||||
@Test(dataProvider="datesUntilSteps")
|
||||
public void test_datesUntil_step(LocalDate start, LocalDate end, Period step) {
|
||||
assertEquals(start.datesUntil(start, step).count(), 0);
|
||||
long count = start.datesUntil(end, step).count();
|
||||
assertTrue(count > 0);
|
||||
// the last value must be before the end date
|
||||
assertTrue(start.plusMonths(step.toTotalMonths()*(count-1)).plusDays(step.getDays()*(count-1)).isBefore(end));
|
||||
try {
|
||||
// the next after the last value must be either invalid or not before the end date
|
||||
assertFalse(start.plusMonths(step.toTotalMonths()*count).plusDays(step.getDays()*count).isBefore(end));
|
||||
} catch (ArithmeticException | DateTimeException e) {
|
||||
// ignore: possible overflow for the next value is ok
|
||||
}
|
||||
if(count < 1000) {
|
||||
assertTrue(start.datesUntil(end, step).allMatch(date -> !date.isBefore(start) && date.isBefore(end)));
|
||||
List<LocalDate> list = new ArrayList<>();
|
||||
for(long i=0; i<count; i++) {
|
||||
list.add(start.plusMonths(step.toTotalMonths()*i).plusDays(step.getDays()*i));
|
||||
}
|
||||
assertEquals(start.datesUntil(end, step).collect(Collectors.toList()), list);
|
||||
}
|
||||
|
||||
// swap end and start and negate the Period
|
||||
count = end.datesUntil(start, step.negated()).count();
|
||||
assertTrue(count > 0);
|
||||
// the last value must be after the start date
|
||||
assertTrue(end.minusMonths(step.toTotalMonths()*(count-1)).minusDays(step.getDays()*(count-1)).isAfter(start));
|
||||
try {
|
||||
// the next after the last value must be either invalid or not after the start date
|
||||
assertFalse(end.minusMonths(step.toTotalMonths()*count).minusDays(step.getDays()*count).isAfter(start));
|
||||
} catch (ArithmeticException | DateTimeException e) {
|
||||
// ignore: possible overflow for the next value is ok
|
||||
}
|
||||
if(count < 1000) {
|
||||
assertTrue(end.datesUntil(start, step.negated()).allMatch(date -> date.isAfter(start) && !date.isAfter(end)));
|
||||
List<LocalDate> list = new ArrayList<>();
|
||||
for(long i=0; i<count; i++) {
|
||||
list.add(end.minusMonths(step.toTotalMonths()*i).minusDays(step.getDays()*i));
|
||||
}
|
||||
assertEquals(end.datesUntil(start, step.negated()).collect(Collectors.toList()), list);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_datesUntil_staticType() {
|
||||
// Test the types of the Stream and elements of the stream
|
||||
LocalDate date = date(2015, 2, 10);
|
||||
Stream<LocalDate> stream = date.datesUntil(date.plusDays(5));
|
||||
long sum = stream.mapToInt(LocalDate::getDayOfMonth).sum();
|
||||
assertEquals(sum, 60, "sum of 10, 11, 12, 13, 14 is wrong");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user