Soru Java'da iki tarih arasındaki hafta içi gün sayısını hesaplayın


Herhangi biri beni iki tarih arasında iş yapabilen (Sat ve Sun hariç) bazı Java snippet'lerine yönlendirebilir.


44
2018-01-05 01:14


Menşei


joda-time.sourceforge.net - marcog


Cevaplar:


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

Başlangıç ​​tarihi ve bitiş tarihi özeldir, Sadece verilen günler   tarihler sayılacaktır. Başlangıç ​​tarihi ve bitiş tarihi dahil edilmeyecektir.


39
2018-01-05 01:20



StartDate açıksa veya bitiş tarihinden sonra bazı garip cevaplar aldım. StartDate öğesi 1970-01-01 21:27:00 ve endDate öğesi 1970-01-01 18:00:00 ise, işlev aynı tarih arasında 0 gün yerine 1 değerini döndürür. Ayrıca startDate 2011-01-05 21:27:00 ve endDate 2011-01-04 00:00:00 ise, işlev -1 veya 1 yerine 2 döndürür (mutlak değerler bekleniyorsa). - Joseph Gordon
Yukarıdaki kod snippet'i, iki tarih arasındaki çalışma günlerini zaman olmadan hesaplamak içindir. - Piyush Mattoo
Çalışır, ancak yürütme süresi gün sayısına bağlıdır: - / - ymajoros
Başlangıç ​​koşulunu, start koşulundan sonra startCal.add (Takvim.DAY_OF_MONTH, 1) eklemek. - Harish
Döngü olmadan 5-liner: stackoverflow.com/a/44942039/480894 - Roland


Çözüm döngü olmadan:

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



Çözümünüzden gerçekten etkilendim. Aynı şeyi başarmaya çalışıyordum ama tarih değiştirmeyi yapmak yerine hafta sonu başlangıç ​​ve bitiş tarihlerini yakalamak için anahtarlarla sarılıyorum. Bu çok daha şık. - Joseph Gordon
@Shengyuan: Biliyorum biraz geç oldu ama lütfen son 2 satırı açıklayabilir misiniz: long days = (c2.getTimeInMillis()-c1.getTimeInMillis())/(1000*60*60*24);  long daysWithoutSunday = days-(days*2/7); - Vrushank
İlk satır, başlangıç ​​tarihi ile bitiş tarihi arasındaki toplam tarih sayısıdır. İkinci satır toplam tarih sayısı eksi hafta sonu sayımıdır. - iceberg
Tutarsız sonuçlar: 2018-03-09 ve 2018-03-10 arasında (Cuma ve Cumartesi) 0 gün. 2018-03-09 ve 2018-03-11 (Cuma ve Pazar) arasında 1 gün verilir. - Roland
Benim çözümüm doğru: stackoverflow.com/a/44942039/480894 - Roland


Shengyuan Lu'nun çözümünü kullandım, ancak tarihlerden biri cumartesi ve diğeri Pazar günü olduğunda yöntemin çağrıldığı dava için bir düzeltme yapmam gerekiyordu - aksi halde cevap bir günlüğüne kapalıdır:

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



neden Shengyuan Lu'nun çözümünü değiştirmiyorsun? - ymajoros


5 satır kodda döngü olmadan çözüm

Arasındaki günler ile aynı şekilde tanımlanır ChronoUnit.DAYS.between(start, end) yani var 4 Pazartesi ve Cuma günleri arasında. Sadece hafta içi günler ile ilgilendiğimiz için hafta sonlarını çıkarmamız gerekiyor, bu yüzden Cuma gününden Salı gününe kadar 2 hafta içi (sadece hesapla endDay - startDay ve çıkar 2 haftasonu için). Eklemek 1 eğer istersen sonuca dahil sonuç, yani günler arası değil.

Ben iki çözüm sunarım.

İlk çözüm (5-liner, kısa ve şifreli):

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

İkinci çözüm:

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



Hem başlangıç ​​hem de bitiş tarihi Cumartesi günü veya Pazar günü her ikisi de varsa, yanlış sonuç verir. - shanti
@shanti şimdi düzeltilmelidir. Bunu işaret ettiğin için tekrar teşekkürler. - Roland


Java tabanlı bir çözümüm yok, ama bir PHP var, umarım yardımcı olur:

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



Genel olarak, Soru ile aynı dilde ya da Groovy <-> Java gibi yakın bir yerde yayınlamanız beklenir. - Basil Bourque


java.time

Modern yol java.time sınıfları ile.

LocalDate

LocalDate sınıf, zaman-gün ve saat dilimi olmayan bir tarih-değer değerini temsil eder.

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

DayOfWeek enum

DayOfWeek  enum sağlar singleton Haftanın şiddet günlerinin her biri için.

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

İstenen tarihleri ​​toplayabiliriz 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 );
    …

bir EnumSet son derece etkili, hızlı ve düşük bellekli bir uygulamadır Set. Kullanabiliriz EnumSet onun yerine if Yukarıda görülen ifade.

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

Hepsini bir araya getirin.

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

Başka bir yaklaşım ThreeTen-Ekstra java.time ile çalışan sınıflar eklemek için proje.

Temporals sınıf ek uygulamalar ekler TemporalAdjuster tarih-saat değerlerini değiştirmek için. İstiyoruz nextWorkingDay Cumartesi ve Pazar günü boyunca atlama sırasında tarih artırmaya ayarlayıcı.

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

performans

Bu sayfadaki çeşitli Cevaplardaki çeşitli yaklaşımların performansını merak ediyorum. Sadece modern düşünüyorum java.time kod, zahmetli mirası kullanarak kod değil Date/Calendar sınıflar.

İşte her biri geçen gün sayısını döndüren dört yöntem.

Birinde görülen akıllı matematik tabanlı yaklaşımı kullanır. Tarafından cevap 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;
}

Benim bu Cevabımda görülen iki kullanım yaklaşımı: (a) Her tarihi ziyaret edin, geleneksel bir döngüde birer birer artış.

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

… Ve, (b) TemporalAdjuster uygulama 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;
}

Sonunda görülen Java Akışları yaklaşımı kullanır Yanıtı: 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;
}

Ve test demeti.

Uyarılar: 

  • Eh, mikro kıyaslama konusunda olağandışı uyarılar güvenilmez, haksız ya da gerçekçi olmayan sonuçlara eğilimlidir.
  • Keşke kullanmayı öğrenseydim JMH mikro kıyaslama çerçevesi.
  • Bu kodun herhangi birini optimize etmeyi denemekten rahatsız olmadım. Örneğin, gerçek işte TemporalAdjuster bizim yöntemimizin dışında önbelleğe alınabilir.

Kablo demeti

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

MacBook Pro'mda (Sierra) Oracle JDK 10.0.1 ile çalışırken ThreeTen-Ekstra 1.3.2 sürümü, sürekli olarak aşağıdakilere yakın sonuçlar elde ediyorum. matematik çözümü diğerlerinin küçük bir kısmıdır Açıkça bekleyeceğimiz gibi birkaç yüz nanosun birkaç bin karşısında. Diğer üçünün TemporalAdjuster en uzunHer zaman 10.000'in üzerinde nanos. Ziyaret ve akış her ikisi de 10.000 nanenin altında iyi bir şekilde geliyor ve ziyaret, akarsulardan çok daha hızlı. İnternetteki diğer örneklerde görüldüğü gibi, Java Akımları genellikle önemli derecede daha uzun süre çalışırken şık kısa kod yaparBu durumda yaklaşık% 20 daha uzundur.

matematik: 18313309 her biri: 183

ziyaret: 708420626 her biri: 7084

ayarlayıcı: 1002157240 her biri: 10021

akış: 924724750 her biri: 9247


Java.time hakkında

java.time çerçeve Java 8 ve daha sonra yerleşiktir. Bu dersler sıkıntılı eskileri destekliyor miras gibi tarih-saat sınıfları java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time proje, şimdi Bakım Modujava.time'a geçişi önerir.

Daha fazla bilgi için bkz. Oracle Eğitimi. Ve birçok örnek ve açıklamalar için Stack Overflow'u arayın. Şartname JSR 310.

Java.time sınıflarını nereden edinebilirim?

ThreeTen-Ekstra Proje java.time ek sınıfları ile genişletir. Bu proje java.time için gelecekteki olası eklemeler için kanıtlanmış bir zemin. Burada bazı yararlı sınıflar bulabilirsiniz. Interval, YearWeek, YearQuarter, ve Daha.


3
2017-11-01 21:44



yerine LocalDate date = start ;, neden olmasın LocalDate date = LocalDate.from(start) argümanı mutasyona uğratmamak için? - Simon
@Simon java.time sınıflar değişmez, tasarım gereği. - Basil Bourque


do  while Piyush çözümünde yanlış olmalı, olmalıdır:

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





Bu benim döngü olmadan benim örneğim. Algoritma aynıdır Eng 声 远 Shengyuan Lus Bir tane ama JodaTime'ın bazı özelliklerini kullandım.

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





Hemen hemen tüm çözümler oldukça modası geçmiş ve anlatıdır. Ancak burada çok yoğunlaştırılmış ve okunabilir bir çözüm var.

Bu yaklaşım bir Java Akışı tarafından sağlanan LocalDate::datesUntil Java 9 ve daha sonra yerleşik bir yöntem.

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 Sıralı sıralı bir tarih akışı döndürür.   döndürülen akış bu tarihten itibaren başlar (dahil) ve gider   1 gün artan adım ile son (özel).

Daha sonra tüm cumartesi ve pazar günleri filtrelenir. Son adım, kalan hafta günlerinin sayısını almaktır.

Java-9 bir yıl önce piyasaya sürüldü, çünkü şimdi kullanmak benim için makul görünüyor.


2
2018-06-24 14:15



En basit ve en kısa kod için bu Cevabı beğendim. Ancak bu muhtemelen en yavaş: (A) Her tarih dikkate alınırken, diğer bazı cevaplar, her bir tarihi ziyaret etmeden toplamı hesaplarken akıllı olmaya çalışıyorlardı. (B) Java Buharları genellikle alternatif bir koddan daha yavaştır, bazen bir çok katlanır. Dolayısıyla, büyük miktarda veri kullanıyor veya sık sık çalışıyorsanız, performansı test edin. Aksi takdirde, bu yaklaşımı az miktarda veri için daha az sıklıkta kullanıyorum. - Basil Bourque
@BasilBourque Size belirli ölçüde katılıyorum. Evet bu çözüm her tarihi dikkate alır. Ancak bu yaklaşım çok daha uyumlu dostudur. Böylece, gerektiğinde paralel olarak çalıştırarak paralel hız kazanabiliriz. - Ravindra Ranwala
Çekirdekler arasında paralellik sağlamak, geçen yürütme süresini kısaltır (duvar saati saati) değil Çalışma zamanında çekirdeğiniz varsayarak, CPU üzerindeki yükü azaltın. Gerçekten, paralellik ekler Paralel çalışmayı koordine etmek için CPU yüküne. - Basil Bourque
Bu konu benim merakımı yakaladı. Bu yüzden, Roland'ın matematik temelli yaklaşımına ve her bir randevuyu ziyaret eden iki yaklaşımımla Java Streams yaklaşımınızı denedim ve TemporalAdjuster. Roland'ın matematiği, uzun bir atış yaparak, elbette, karşılaştırmadan kazanır. Akışlarınız yaklaşımı, geleneksel bir döngüde her bir ziyareti ziyaret etmekten yaklaşık% 20 daha yavaştır. Ama benim TemporalAdjuster yaklaşım her zaman en yavaştı. Tüm koduma ve "Performans" bölümüne eklenen sonuçlara bakın. cevabım. - Basil Bourque
@BasilBourque Evet, paralel yürütme sonucu daha hızlı hesaplamak için daha fazla CPU döngüsü gerekir. Ancak bu büyük bir kriter oldu. - Ravindra Ranwala


startCal.add üzerine eklemeliyim Calendar.DATE alan değil Calendar.DAY_OF_MONTHDecemeber / Ocak dönemi boyunca garip sonuçlar alıyordum.


1
2017-08-28 06:47



Bunun doğru olduğunu düşünmüyorum, Calendar.DATE, Calendar.DAY_OF_MONTH ile eşanlamlıdır. - mbosecke