2016-07-06 42 views
24

Czy istnieje możliwość uzyskania wszystkich dat między dwiema datami w nowym interfejsie API java.time?Java 8 LocalDate - Jak uzyskać wszystkie daty między dwiema datami?

Powiedzmy mam ten fragment kodu:

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

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

teraz muszę wszystkie dniach między startDate i endDate.

Myślałam aby uzyskać daysBetween z dwiema datami i iteracyjnego:

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

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

Czy istnieje lepszy sposób, aby uzyskać daty?

+0

Można równie dobrze zwiększyć wartość początkową, o ile jest mniejsza niż data zakończenia. (Zakładając, że start jest zawsze mniejszy niż koniec na początku.) – Fildor

+0

Nie sądzę, że istnieje inny sposób niż iteracja. –

+0

Mogę sobie wyobrazić, że może być jakiś wymyślny strumień Java8 - sposób na uzyskanie listy tych dat ... ale nie jestem zbyt zaznajomiony z tym nowym API. – Fildor

Odpowiedz

28

Zakładając, że przede wszystkim chcą iteracyjne nad zakresu dat, warto byłoby stworzyć DateRange klasy, która jest iterable. Który pozwoli Ci napisać:

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

Coś jak:

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; 
    } 
} 

Wydaje się rozsądne, aby dodać równa & metody hashcode, pozyskiwaniu, może mieć statyczny fabryczne + prywatny konstruktor, aby dopasować styl kodowania interfejsu API czasu Java itp.

+0

To czyste podejście do logiki we własnej klasie. Dziękuję za wskazanie mnie w tym kierunku! – Patrick

+0

@Flown Mogłem (patrz komentarz do metody toList). Jest to wyciąg z większej klasy i myślę, że powodem nie było wykonanie (nie jestem pewien, czy to naprawdę ważne, żeby być szczerym). – assylias

+0

@assylias Widziałem komentarz później i skasowałem mój komentarz. Czy naprawdę uważasz, że wydajność jest w tym przypadku problemem? – Flown

6

Możesz użyć .isAfter i .plusDays, aby zrobić to w pętli. Nie powiedziałbym, że lepiej jak nie zrobiłem ogromnej ilości badań w temacie, ale mogę śmiało powiedzieć, że używa on API Java 8 i jest alternatywą lekką.

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

Wyjście

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

Example Here

+0

Dlaczego nie pętla 'for'? Masz wstępną instrukcję, warunek i operację inkrementacji, która dokładnie pasuje do przypadku użycia pętli 'for', np.' Dla (LocalDate startDate = LocalDate.now(), endDate = startDate.plusMonths (1);! StartDate .isAfter (endDate); startDate = startDate.plusDays (1)) {System.out.println (startDate); } ' – Holger

+0

@Holger Zadam ci przeciwne pytanie. Dlaczego pętla 'for'? Co zyskujesz z pętli 'for'? Wydajność czy preferencje? –

+2

Jak już wspomniano, pętla 'for' jest zamierzonym konstruktem składni dla tego przypadku użycia. Posiadanie dobrze zdefiniowanego wzorca dla wartości początkowej, warunku i inkrementu powoduje, że kod jest wyraźny. Te trzy powiązane rzeczy są również zawsze razem, niezależnie od rozmiaru ciała pętli. W pętli 'while' nie ma możliwości rozróżnienia pomiędzy operacją przyrostu pętli, zwykle będącą gdzieś na końcu, a rzeczywistą akcją w ciele pętli, ponadto zmienne pętli muszą być zadeklarowane poza pętlą, uzyskując większy zakres niż zamierzony. – Holger

30

pierwsze można użyć TemporalAdjuster dostać ostatni dzień miesiąca. Następnie Stream API oferuje Stream::iterate, które jest właściwym narzędziem dla Twojego problemu.

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

Dzięki za bardzo dobre rozwiązanie. Użyłby go, gdy nadal potrzebuję Listy. – Patrick

+2

@Patrick nie określiłeś, jak powinny wyglądać wyniki, było to tylko założenie. Nie musisz też przechowywać wyników, możesz zamiast tego użyć ['Stream :: forEach'] (https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream. html # forEach-java.util.function.Consumer-) w celu przetworzenia wyników. – Flown

+1

Powinien to być '.limit (ChronoUnit.DAYS.between (start, end) + 1)', aby uwzględnić ostatni dzień każdego miesiąca. – user405935

3

Można utworzyć obiekty stream z obiektów LocalDate. Miałem ten problem zbyt i opublikowałem moje rozwiązanie jako java-timestream on github.

Korzystanie z przykładem ...

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

Jest to mniej więcej odpowiednik innych rozwiązań proponowanych tutaj, ale dba o wszystkie najświeższe matematyki i wiedząc, kiedy przestać. Możesz podać konkretne lub względne daty zakończenia i określić, ile czasu należy pominąć w każdej iteracji (domyślnie powyżej jest jeden dzień).

1

W mojej bibliotece czasu Time4J, napisałem zoptymalizowany spliter do skonstruowania strumienia dat kalendarzowych z dobrymi cechami równoległości.Dostosowany do przypadków użycia:

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

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

Ten krótki podejście może być interesującym punktem początkowym, jeśli są również zainteresowani powiązanych funkcji, takich odstępach czasu zegara na datę kalendarzową (dzielone strumienie) lub innych cech interwałowych i chcą uniknąć niewygodne ręcznie napisany kod, zobacz także API z DateInterval.

Powiązane problemy