2016-07-06 1 views
24

Есть ли возможность получить все даты между двумя датами в новом java.time API?Java 8 LocalDate - Как получить все даты между двумя датами?

Скажем, у меня есть эта часть кода:

@Test 
public void testGenerateChartCalendarData() { 
    LocalDate startDate = LocalDate.now(); 

    LocalDate endDate = startDate.plusMonths(1); 
    endDate = endDate.withDayOfMonth(endDate.lengthOfMonth()); 
} 

Теперь мне нужно все даты между startDate и endDate.

Я думал, чтобы получить daysBetween из двух дат и перебирать:

long daysBetween = ChronoUnit.DAYS.between(startDate, endDate); 

for(int i = 0; i <= daysBetween; i++){ 
    startDate.plusDays(i); //...do the stuff with the new date... 
} 

Есть ли лучший способ получить даты?

+0

Вы также можете увеличить начальную дату, если она меньше, чем enddate. (Предполагая, что начало всегда меньше конца для начала.) – Fildor

+0

Я не думаю, что есть другой способ, чем повторить. –

+0

Я мог представить, что может появиться какой-то причудливый поток Java8, чтобы получить список этих дат ... но я не слишком хорошо знаком с этим новым API. – Fildor

ответ

28

Предполагая, что вы в основном хотите перебрать диапазон дат, было бы целесообразно создать DateRange класс, итерация. Это позволит вам написать:

for (LocalDate d : DateRange.between(startDate, endDate)) ... 

Что-то вроде:

public class DateRange implements Iterable<LocalDate> { 

    private final LocalDate startDate; 
    private final LocalDate endDate; 

    public DateRange(LocalDate startDate, LocalDate endDate) { 
    //check that range is valid (null, start < end) 
    this.startDate = startDate; 
    this.endDate = endDate; 
    } 

    @Override 
    public Iterator<LocalDate> iterator() { 
    return stream().iterator(); 
    } 

    public Stream<LocalDate> stream() { 
    return Stream.iterate(startDate, d -> d.plusDays(1)) 
       .limit(ChronoUnit.DAYS.between(startDate, endDate) + 1); 
    } 

    public List<LocalDate> toList() { //could also be built from the stream() method 
    List<LocalDate> dates = new ArrayList<>(); 
    for (LocalDate d = startDate; !d.isAfter(endDate); d = d.plusDays(1)) { 
     dates.add(d); 
    } 
    return dates; 
    } 
} 

Это имело бы смысл добавить равно & методы Hashcode, добытчики, может иметь статический завод + частный конструктор, чтобы соответствовать стилю кодирования API Java time и т. д.

+0

Его чистый подход к логике в своем классе. Спасибо, что указал мне в этом направлении! – Patrick

+0

@Flown Я мог (см. Комментарий к методу toList). Это выдержка из более крупного класса, и я думаю, что причина не в производительности (не уверен, имеет ли значение реальность, если честно). – assylias

+0

@assylias Я увидел комментарий позже и удалил свой комментарий. Вы действительно думаете, что производительность является проблемой в этом случае? – Flown

6

Вы можете использовать .isAfter и .plusDays, чтобы сделать это над циклом. Я бы не сказал лучше, так как я не проделал огромного количества исследований по этой теме, но могу с уверенностью сказать, что он использует API Java 8 и представляет собой небольшую альтернативу .

LocalDate startDate = LocalDate.now(); 
LocalDate endDate = startDate.plusMonths(1); 
while (!startDate.isAfter(endDate)) { 
System.out.println(startDate); 
startDate = startDate.plusDays(1); 
} 

Выход

2016-07-05 
2016-07-06 
... 
2016-08-04 
2016-08-05 

Example Here

+0

Почему бы не цикл 'for'? У вас есть начальная инструкция, условие и операция приращения, которая точно соответствует прецеденту цикла 'for', то есть' for (LocalDate startDate = LocalDate.now(), endDate = startDate.plusMonths (1);! StartDate .isAfter (endDate); startDate = startDate.plusDays (1)) {System.out.println (startDate); } ' – Holger

+0

@Holger Я задам вам противоположный вопрос. Почему цикл 'for'? Что вы получаете от цикла 'for'? Производительность или предпочтение? –

+2

Как сказано, цикл 'for' является предполагаемой синтаксической конструкцией для этого варианта использования. Наличие четко определенного шаблона для начального значения, условия и приращения делает код ясным. Эти три связанные вещи также всегда вместе, независимо от размера тела петли. В цикле 'while' нет способа различать операцию инкремента цикла, обычно находящуюся где-то в конце, и действительное действие внутри тела цикла, далее переменные цикла должны быть объявлены вне цикла, что дает чем предполагалось. – Holger

30

Сначала вы можете использовать TemporalAdjuster, чтобы получить последний день месяца. Следующий API Stream предлагает Stream::iterate, который является правильным инструментом для вашей проблемы.

LocalDate start = LocalDate.now(); 
LocalDate end = LocalDate.now().plusMonths(1).with(TemporalAdjusters.lastDayOfMonth()); 
List<LocalDate> dates = Stream.iterate(start, date -> date.plusDays(1)) 
    .limit(ChronoUnit.DAYS.between(start, end)) 
    .collect(Collectors.toList()); 
System.out.println(dates.size()); 
System.out.println(dates); 
+0

Спасибо за ваше очень хорошее решение. Будет использовать его, когда мне все еще нужен Список. – Patrick

+2

@Patrick, вы не указали, как должны выглядеть результаты, это было только предположение. Вам также не нужно сохранять результаты, вместо этого вы можете использовать ['Stream :: forEach'] (https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream. html # forEach-java.util.function.Consumer-) для обработки результатов. – Flown

+1

Это должен быть '.limit (ChronoUnit.DAYS.between (начало, конец) + 1)', чтобы включить последний день каждого месяца. – user405935

3

Вы можете создать stream из LocalDate объектов. У меня тоже была эта проблема, и я опубликовал свое решение, как java-timestream on github.

Используя ваш пример ...

LocalDateStream 
    .from(LocalDate.now()) 
    .to(1, ChronoUnit.MONTHS) 
    .stream() 
    .collect(Collectors.toList()); 

Это более или менее эквивалентно других решений, предлагаемых здесь, но она берет на себя все о дате математике и зная, когда остановиться. Вы можете указать конкретные или относительные даты окончания и указать, сколько времени пропустить каждую итерацию (по умолчанию это один день).

1

В моей библиотеке времени Time4J, я написал оптимизированный разделитель для создания потока календарных дат с хорошими характеристиками распараллеливания.Адаптированный к вашему потребительному случае:

LocalDate start = ...; 
LocalDate end = ...; 

Stream<LocalDate> stream = 
    DateInterval.between(start, end) // closed interval, else use .withOpenEnd() 
    .streamDaily() 
    .map(PlainDate::toTemporalAccessor); 

Этот короткий подход может быть интересной точкой начала, если вы также заинтересованы в соответствующих функциях, таких как интервалы часов на календарную дату (Разделенные потоки) или других характеристики интервальных и хотите, чтобы избежать неловко рукописный код, см. также API DateInterval.