Pytanie Oblicz liczbę dni powszednich między dwiema datami w Javie


Czy każdy może wskazać mi fragment kodu Java, w którym mogę uzyskać informacje o biznesie (z wyjątkiem soboty i niedzieli) między dwiema datami.


44
2018-01-05 01:14


pochodzenie


joda-time.sourceforge.net - marcog


Odpowiedzi:


public static int getWorkingDaysBetweenTwoDates(Date startDate, Date endDate) {
    Calendar startCal = Calendar.getInstance();
    startCal.setTime(startDate);        

    Calendar endCal = Calendar.getInstance();
    endCal.setTime(endDate);

    int workDays = 0;

    //Return 0 if start and end are the same
    if (startCal.getTimeInMillis() == endCal.getTimeInMillis()) {
        return 0;
    }

    if (startCal.getTimeInMillis() > endCal.getTimeInMillis()) {
        startCal.setTime(endDate);
        endCal.setTime(startDate);
    }

    do {
       //excluding start date
        startCal.add(Calendar.DAY_OF_MONTH, 1);
        if (startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
            ++workDays;
        }
    } while (startCal.getTimeInMillis() < endCal.getTimeInMillis()); //excluding end date

    return workDays;
}

Data rozpoczęcia i data zakończenia są wyłączne, tylko dni między podanymi   daty zostaną policzone. Data rozpoczęcia i data zakończenia nie będą uwzględniane.


39
2018-01-05 01:20



Dostaję dziwne odpowiedzi, jeśli startDate jest włączony lub po zakończeniu endDate. Jeśli startDate to 1970-01-01 21:27:00, a endDate to 1970-01-01 18:00:00, funkcja zwraca 1 zamiast 0 dni między tą samą datą. Również jeśli startDate to 2011-01-05 21:27:00 i endDate to 2011-01-04 00:00:00 funkcja zwraca 2 zamiast -1 lub 1 (jeśli oczekiwane są wartości bezwzględne). - Joseph Gordon
Powyższy fragment kodu służy do obliczania dni roboczych między dwiema datami bez czasu. - Piyush Mattoo
Działa, ale czas wykonania zależy od liczby dni: - / - ymajoros
Aby dołączyć startdate, umieść startCal.add (Calendar.DAY_OF_MONTH, 1) po warunku if. - Harish
5-liner bez pętli: stackoverflow.com/a/44942039/480894 - Roland


Rozwiązanie bez pętli:

static long days(Date start, Date end){
    //Ignore argument check

    Calendar c1 = Calendar.getInstance();
    c1.setTime(start);
    int w1 = c1.get(Calendar.DAY_OF_WEEK);
    c1.add(Calendar.DAY_OF_WEEK, -w1);

    Calendar c2 = Calendar.getInstance();
    c2.setTime(end);
    int w2 = c2.get(Calendar.DAY_OF_WEEK);
    c2.add(Calendar.DAY_OF_WEEK, -w2);

    //end Saturday to start Saturday 
    long days = (c2.getTimeInMillis()-c1.getTimeInMillis())/(1000*60*60*24);
    long daysWithoutWeekendDays = days-(days*2/7);

    // Adjust days to add on (w2) and days to subtract (w1) so that Saturday
    // and Sunday are not included
    if (w1 == Calendar.SUNDAY && w2 != Calendar.SATURDAY) {
        w1 = Calendar.MONDAY;
    } else if (w1 == Calendar.SATURDAY && w2 != Calendar.SUNDAY) {
        w1 = Calendar.FRIDAY;
    } 

    if (w2 == Calendar.SUNDAY) {
        w2 = Calendar.MONDAY;
    } else if (w2 == Calendar.SATURDAY) {
        w2 = Calendar.FRIDAY;
    }

    return daysWithoutWeekendDays-w1+w2;
}

47
2018-01-05 03:17



Naprawdę pod wrażeniem Twojego rozwiązania. Próbowałem osiągnąć to samo, ale skończyłem z przełącznikami, aby złapać daty rozpoczęcia i zakończenia w weekend, zamiast robić przesunięcie daty. To jest o wiele bardziej eleganckie. - Joseph Gordon
@Shengyuan: Wiem, że jest trochę za późno, ale czy mógłbyś wyjaśnić dwie ostatnie linijki: long days = (c2.getTimeInMillis()-c1.getTimeInMillis())/(1000*60*60*24);  long daysWithoutSunday = days-(days*2/7); - Vrushank
Pierwszy wiersz to łączna data między datą początkową a końcową. Druga linia to łączna liczba dat minus liczba weekendów. - iceberg
Niespójne wyniki: od 2018-03-09 do 2018-03-10 (piątek i sobota) dają 0 dni. Między 2018-03-09 i 2018-03-11 (piątek i niedziela) daje 1 dzień. - Roland
Moje rozwiązanie jest poprawne: stackoverflow.com/a/44942039/480894 - Roland


Użyłem rozwiązania Shengyuan Lu, ale musiałem naprawić sytuację, w której metoda jest wywoływana, gdy jedna z dat jest w sobotę, a druga w niedzielę - w przeciwnym razie odpowiedź jest wyłączona o jeden dzień:

static long days(Date start, Date end){
    //Ignore argument check

    Calendar c1 = GregorianCalendar.getInstance();
    c1.setTime(start);
    int w1 = c1.get(Calendar.DAY_OF_WEEK);
    c1.add(Calendar.DAY_OF_WEEK, -w1 + 1);

    Calendar c2 = GregorianCalendar.getInstance();
    c2.setTime(end);
    int w2 = c2.get(Calendar.DAY_OF_WEEK);
    c2.add(Calendar.DAY_OF_WEEK, -w2 + 1);

    //end Saturday to start Saturday 
    long days = (c2.getTimeInMillis()-c1.getTimeInMillis())/(1000*60*60*24);
    long daysWithoutSunday = days-(days*2/7);

    if (w1 == Calendar.SUNDAY) {
        w1 = Calendar.MONDAY;
    }
    if (w2 == Calendar.SUNDAY) {
        w2 = Calendar.MONDAY;
    }
    return daysWithoutSunday-w1+w2;
}

10
2017-11-03 23:13



dlaczego po prostu nie zmienisz rozwiązania Shengyuan Lu? - ymajoros


Rozwiązanie bez pętli w 5 liniach kodu

Dni między są zdefiniowane w taki sam sposób jak ChronoUnit.DAYS.between(start, end) co oznacza, że ​​są 4 dni od poniedziałku do piątku. Ponieważ jesteśmy zainteresowani tylko w dni powszednie, musimy odjąć weekendy, dlatego od piątku do wtorku będzie 2 dni robocze (po prostu oblicz endDay - startDay i odejmij 2 Na weekend). Dodaj 1 do wyniku, jeśli chcesz włącznie wynik, tj. nie dni między.

Przedstawiam dwa rozwiązania.

Pierwsze rozwiązanie (5-liniowy, krótki i tajemniczy):

import java.time.*;
import java.time.temporal.*;

public static long calcWeekDays1(final LocalDate start, final LocalDate end) {
    final DayOfWeek startW = start.getDayOfWeek();
    final DayOfWeek endW = end.getDayOfWeek();

    final long days = ChronoUnit.DAYS.between(start, end);
    final long daysWithoutWeekends = days - 2 * ((days + startW.getValue())/7);

    //adjust for starting and ending on a Sunday:
    return daysWithoutWeekends + (startW == DayOfWeek.SUNDAY ? 1 : 0) + (endW == DayOfWeek.SUNDAY ? 1 : 0);
}

Drugie rozwiązanie:

public static long calcWeekDays2(final LocalDate start, final LocalDate end) {
    final int startW = start.getDayOfWeek().getValue();
    final int endW = end.getDayOfWeek().getValue();

    final long days = ChronoUnit.DAYS.between(start, end);
    long result = days - 2*(days/7); //remove weekends

    if (days % 7 != 0) { //deal with the rest days
        if (startW == 7) {
            result -= 1;
        } else if (endW == 7) {  //they can't both be Sunday, otherwise rest would be zero
            result -= 1;
        } else if (endW < startW) { //another weekend is included
            result -= 2;
        }
    }

    return result;
}

8
2017-07-06 07:10



Jeśli zarówno data rozpoczęcia, jak i zakończenia przypada w sobotę lub obie w niedzielę, wtedy daje zły wynik. - shanti
@shanti powinno to być teraz naprawione. Jeszcze raz dziękuję za wskazanie tego. - Roland


Nie mam rozwiązania opartego na Javie, ale mam wersję PHP, mam nadzieję, że pomoże:

function getDate($days) {   
    for ($i = 0; $i < $days; $i ++) {                                      
        if (date('N' , strtotime('+' . ($i + 1) . ' days')) > 5) {  
            $days++;                                                        
        }                                                                   
    }                                                                       

    return date('l, F jS', strtotime('+' . $days . ' days', time()));
}

4
2018-01-05 01:20



Ogólnie oczekuje się, że opublikujesz w tym samym języku co Pytanie lub coś bliskiego, na przykład Groovy <-> Java. - Basil Bourque


java.time

Nowoczesny sposób jest z klasami java.time.

LocalDate

The LocalDate Klasa reprezentuje wartość tylko dla daty bez pory dnia i bez strefy czasowej.

LocalDate start = LocalDate.of( 2016 , 1 , 23 );
LocalDate stop = start.plusMonths( 1 );

DayOfWeek enum

The DayOfWeek  enum zapewnia singel instancja dla każdego z kilku dni tygodnia.

DayOfWeek dow = start.getDayOfWeek();
if( dow.equals( DayOfWeek.SATURDAY ) || dow.equals( DayOfWeek.SUNDAY ) ) …

Możemy zebrać żądane daty to List.

int initialCapacity = Duration.between( start , stop ).toDays() ;
List<LocalDate> dates = new ArrayList<>( initialCapacity );
…
if( dow.equals( DayOfWeek.SATURDAY ) || dow.equals( DayOfWeek.SUNDAY ) ) {
    dates.add( date );
    …

Na EnumSet jest niezwykle wydajną, szybką i mało pamięciową implementacją Set. Możemy użyć EnumSet zamiast tego if wypowiedź widziana powyżej.

Set<DayOfWeek> weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) ;
…
if( weekend.contains( dayOfWeek ) ) …

Połącz to wszystko.

LocalDate date = start ;
while( date.isBefore( stop ) ) {
    if( ! weekend.contains( date.getDayOfWeek() ) ) { // If not weekend, collect this LocalDate.
        dates.add( date ) ;
    }
    // Prepare for next loop.
    date = date.plusDays( 1 ); // Increment to next day.
}

nextWorkingDay TemporalAdjuster

Inne podejście wykorzystuje ThreeTen-Extra projekt dodać klasy, które działają z java.time.

The Temporals klasa dodaje dodatkowe implementacje TemporalAdjuster do manipulowania wartościami daty i czasu. Chcemy nextWorkingDay Regulator do zwiększania daty podczas przeskakiwania w sobotę i niedzielę.

LocalDate start = LocalDate.of( 2016 , 1 , 23 );
LocalDate stop = start.plusMonths( 1 );

int initialCapacity = Duration.between( start , stop ).toDays() ;
List<LocalDate> dates = new ArrayList<>( initialCapacity );

LocalDate date = start.minusDays( 1 );  // Start a day ahead.
while( date.isBefore( stop ) ) {
    date = date.with( org.threeten.extra.Temporals.nextWorkingDay() );
    // Double-check ending date as the `nextWorkingDay` adjuster could move us past the stop date.
    if( date.isBefore( stop ) ) { 
        dates.add( date ) ;
    }
}

Wydajność

Ciekawi mnie wydajność różnych metod w różnych odpowiedziach na tej stronie. Rozważam tylko współczesność java.time kod, a nie kod wykorzystujący kłopotliwe dziedzictwo Date/Calendar klasy.

Oto cztery metody, z których każda zwraca liczbę dni, które upłynęły.

Jeden wykorzystuje sprytne podejście matematyczne widoczne w Odpowiedz Roland.

private long countWeekDaysMath ( LocalDate start , LocalDate stop ) {
    // Code taken from Answer by Roland.
    // https://stackoverflow.com/a/44942039/642706
    long count = 0;
    final DayOfWeek startW = start.getDayOfWeek();
    final DayOfWeek stopW = stop.getDayOfWeek();

    final long days = ChronoUnit.DAYS.between( start , stop );
    final long daysWithoutWeekends = days - 2 * ( ( days + startW.getValue() ) / 7 );

    //adjust for starting and ending on a Sunday:
    count = daysWithoutWeekends + ( startW == DayOfWeek.SUNDAY ? 1 : 0 ) + ( stopW == DayOfWeek.SUNDAY ? 1 : 0 );

    return count;
}

Dwa podejścia do używania widziane w mojej odpowiedzi: (a) Odwiedzaj każdą datę, zwiększając jeden po drugim w konwencjonalnej pętli.

private long countWeekDaysVisit ( LocalDate start , LocalDate stop ) {
    // Code taken from Answer by Basil Bourque.
    // https://stackoverflow.com/a/40369140/642706
    long count = 0;
    Set < DayOfWeek > weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
    LocalDate ld = start;
    while ( ld.isBefore( stop ) ) {
        if ( ! weekend.contains( ld.getDayOfWeek() ) ) { // If not weekend, collect this LocalDate.
            count++;
        }
        // Prepare for next loop.
        ld = ld.plusDays( 1 ); // Increment to next day.
    }
    return count;
}

... i, (b) Korzystanie z TemporalAdjuster realizacja org.threeten.extra.Temporals.nextWorkingDay().

private long countWeekDaysAdjuster ( LocalDate start , LocalDate stop ) {
    // Code taken from Answer by Basil Bourque.
    // https://stackoverflow.com/a/40369140/642706
    long count = 0;
    Set < DayOfWeek > weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
    TemporalAdjuster nextWorkingDayTA = org.threeten.extra.Temporals.nextWorkingDay();
    LocalDate ld = start;
    if ( weekend.contains( ld.getDayOfWeek() ) ) {
        ld = ld.with( nextWorkingDayTA );
    }
    while ( ld.isBefore( stop ) ) {
        count++;
        // Prepare for next loop.
        ld = ld.with( nextWorkingDayTA ); // Increment to next working day (non-weekend day).
    }
    return count;
}

Ostatni korzysta z podejścia strumieniowego Java widocznego w Odpowiedz przez Ravindra Ranwala.

private long countWeekDaysStream ( LocalDate start , LocalDate stop ) {
    // Code taken from the Answer by Ravindra Ranwala.
    // https://stackoverflow.com/a/51010738/642706
    long count = 0;
    Set < DayOfWeek > weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
    final long weekDaysBetween = start.datesUntil( stop )
                                     .filter( d -> ! weekend.contains( d.getDayOfWeek() ) )
                                     .count();
    return count;
}

I uprząż testowa.

Ostrzeżenia: 

  • Cóż, zwykłe zastrzeżenia dotyczące mikro-benchmarkingu są niewiarygodne, podatne na nieuzasadnione lub nierealistyczne wnioski.
  • Szkoda, że ​​nie nauczyłem się korzystać z JMH ramy mikro-benchmarkingowe.
  • Nie próbowałem optymalizować żadnego z tych kodów. Na przykład w prawdziwej pracy, TemporalAdjuster może być zbuforowany poza naszą metodą.

Szelki testowe.

LocalDate start = LocalDate.of( 2018 , Month.JANUARY , 1 );
LocalDate stop = start.plusYears( 1 );

int runs = 100_000;

long go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
    long count = this.countWeekDaysMath( start , stop );
}
long elapsedMath = ( System.nanoTime() - go );

go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
    long count = this.countWeekDaysVisit( start , stop );
}
long elapsedVisit = ( System.nanoTime() - go );

go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
    long count = this.countWeekDaysStream( start , stop );
}
long elapsedAdjuster = ( System.nanoTime() - go );

go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
    long count = this.countWeekDaysStream( start , stop );
}
long elapsedStream = ( System.nanoTime() - go );

System.out.println( "math: " + elapsedMath + " each: " + ( elapsedMath / runs ) );
System.out.println( "visit: " + elapsedVisit + " each: " + ( elapsedVisit / runs ) );
System.out.println( "adjuster: " + elapsedAdjuster + " each: " + ( elapsedAdjuster / runs ) );
System.out.println( "stream: " + elapsedStream + " each: " + ( elapsedStream / runs ) );

Po uruchomieniu na moim MacBook Pro (Sierra) z Oracle JDK 10.0.1 i ThreeTen-Extra w wersji 1.3.2, wyniki są konsekwentnie zbliżone do poniższych. The rozwiązanie matematyczne to maleńki ułamek pozostałych na kilkaset nanogramów w stosunku do kilku tysięcy, jak można się było spodziewać. Z pozostałych trzech, TemporalAdjuster jest najdłuższy, zawsze ponad 10 000 nanosów. Wizyty i strumienie mają się dobrze poniżej 10 000 nanogramów, a odwiedziny są zauważalnie szybsze niż strumienie. Jak widać w innych przykładach w Internecie, Strumienie w Javie zazwyczaj tworzą krótki kod, a często działają znacznie dłużej, o 20% dłużej w tym przypadku.

matematyka: 18313309 każda: 183

wizyta: 708420626 każda: 7084

regulator: 1002157240 każdy: 10021

strumień: 924724750 każdy: 9247


Informacje o java.time

The java.time framework jest wbudowany w Javę 8 i później. Te klasy zastępują kłopotliwe stare dziedzictwo klasy czasu i daty, takie jak java.util.Date, Calendar& SimpleDateFormat.

The Joda-Time projekt, teraz w tryb konserwacji, zaleca migrację do java.time.

Aby dowiedzieć się więcej, zobacz Tutorial Oracle. I wyszukaj Stack Overflow dla wielu przykładów i objaśnień. Specyfikacja jest JSR 310.

Gdzie można uzyskać klasy java.time?

  • Java SE 8 i SE 9 i później
    • Wbudowany.
    • Część standardowego interfejsu API języka Java z pakietem implementacji.
    • Java 9 dodaje kilka drobnych funkcji i poprawek.
  • Java SE 6 i SE 7
    • Duża część funkcji java.time została przeniesiona do wersji Java 6 i 7 w ThreeTen-Backport.
  • Android
    • The ThreeTenABP projekt dostosowuje się ThreeTen-Backport (wspomniano powyżej) specjalnie dla Androida.
    • Widzieć Jak używać….

The ThreeTen-Extra projekt rozszerza java.time o dodatkowe klasy. Ten projekt jest poligonem wskazującym na możliwe przyszłe dodatki do java.time. Możesz znaleźć tutaj kilka przydatnych klas, takich jak Interval, YearWeek, YearQuarter, i jeszcze.


3
2017-11-01 21:44



zamiast LocalDate date = start ;, Dlaczego nie LocalDate date = LocalDate.from(start) aby uniknąć mutowania argumentu? - Simon
@Simon The java.time klasy są niezmienny, przez projekt. - Basil Bourque


The do  while w rozwiązaniu Piyush jest źle, powinno być:

do {
    if (startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
        ++workDays;
    }
    startCal.add(Calendar.DAY_OF_MONTH, 1);
} while (startCal.getTimeInMillis() < endCal.getTimeInMillis());

2
2018-03-27 04:56





To jest mój przykład bez pętli. Algorytm jest taki sam jak 远 声 远 Shengyuan Lus ale użyłem niektórych funkcji JodaTime.

public static int getNumberOfBusinessDays(@Nonnull LocalDate from, @Nonnull LocalDate to) {
    int fromDateDayOfWeek = from.getDayOfWeek();
    int toDateDayOfWeek = to.getDayOfWeek();

    int daysWithoutWeekends = 5 * Weeks.weeksBetween(
            from.withDayOfWeek(DateTimeConstants.MONDAY), to).getWeeks();

    if (fromDateDayOfWeek == DateTimeConstants.SUNDAY) {
        fromDateDayOfWeek = DateTimeConstants.SATURDAY;
    }
    if (toDateDayOfWeek == DateTimeConstants.SUNDAY) {
        toDateDayOfWeek = DateTimeConstants.SATURDAY;
    }

    return daysWithoutWeekends - (fromDateDayOfWeek - toDateDayOfWeek);
}

2
2017-07-27 07:23





Niemal wszystkie rozwiązania są dość przestarzałe i narracyjne. Jednak tutaj jest znacznie skondensowane i czytelne rozwiązanie.

W tym podejściu używa się Strumień Java dostarczone przez LocalDate::datesUntil metoda wbudowana w Javę 9 i później.

LocalDate startDate = LocalDate.of(2018, 5, 2);
LocalDate endDate = LocalDate.now();
Set<DayOfWeek> weekend = EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
final long weekDaysBetween = startDate.datesUntil(endDate)
        .filter(d -> !weekend.contains(d.getDayOfWeek()))
        .count();

.datesUntil zwraca sekwencyjny uporządkowany strumień dat. The   Zwrócony strumień rozpoczyna się od tej daty (włącznie) i przechodzi do   endExclusive (wyłączny) krokiem 1-dniowym.

Następnie wszystkie soboty i niedziele są odfiltrowywane. Ostatnim krokiem jest uzyskanie liczby pozostałych dni tygodnia.

Java-9 została wydana rok temu, ponieważ używanie jej teraz wydaje mi się rozsądne.


2
2018-06-24 14:15



Podoba mi się ta odpowiedź na najprostszy i najkrótszy kod. Ale najprawdopodobniej jest to najwolniejsza: (A) rozważana jest każda data, podczas gdy niektóre inne odpowiedzi próbowały być sprytne w obliczaniu sumy bez odwiedzania każdej daty. (B) Czajenia Java są często wolniejsze od alternatywnego kodu, czasami przez wiele razy. Jeśli więc używasz dużych ilości danych lub często pracujesz, przetestuj wydajność. W przeciwnym razie użyłbym tego podejścia, ponieważ niewielkie ilości danych działały rzadziej. - Basil Bourque
@BasilBourque Zgadzam się z tobą do pewnego stopnia. Tak, to rozwiązanie uwzględnia każdą datę. Ale to podejście jest znacznie bardziej równoległe przyjazne. Możemy więc uzyskać równoległe przyspieszenie, wykonując je równolegle w razie potrzeby. - Ravindra Ranwala
Równoległe rozmieszczenie rdzeni skraca czas wykonania (zegar), ale robi to nie zmniejszyć obciążenie procesora, zakładając, że masz rdzenie dostępne w środowisku uruchomieniowym. Rzeczywiście, równoległe dodaje do obciążenia procesora, w celu koordynacji pracy równoległej. - Basil Bourque
Ten wątek przykuł moją ciekawość. Więc wypróbowałem swoje podejście do strumieni Java w stosunku do matematyki Rolanda i moje dwa podejścia odwiedzając każdą datę i używając TemporalAdjuster. Matematyka Rolanda wygrywa z dystansu, oczywiście, bez porównania. Twoje podejście strumieniowe jest około 20% wolniejsze niż odwiedzanie każdej daty w konwencjonalnej pętli. Ale mój TemporalAdjuster podejście było zawsze najwolniejsze. Zobacz cały mój kod i wyniki w sekcji "Wydajność" dodane do moja odpowiedź. - Basil Bourque
@BasilBourque Tak, wykonanie równoległe wymaga więcej cykli procesora, aby szybciej obliczyć wynik. Jednak był to świetny benchmark. - Ravindra Ranwala


The startCal.add powinien dodać na Calendar.DATE pole, a nie Calendar.DAY_OF_MONTH, Uzyskiwałem dziwne wyniki z ponad Decemeber / styczeń.


1
2017-08-28 06:47



Nie sądzę, że to prawda, Calendar.DATE jest synonimem Calendar.DAY_OF_MONTH - mbosecke