计算Java中两个日期之间的工作日数

Joh*_*n C 47 java

任何人都可以指向我的一些Java片段,其中我可以在两个日期之间获得业务(周六和周日除外).

卢声远*_* Lu 48

解决方案无循环:

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;
}
Run Code Online (Sandbox Code Playgroud)


Piy*_*too 43

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;
}
Run Code Online (Sandbox Code Playgroud)

开始日期和结束日期是独家的,只计算给定日期之间的天数.开始日期和结束日期不包括在内.

  • 它有效,但执行时间取决于天数: - / (7认同)
  • 要包含 startdate 将 startCal.add(Calendar.DAY_OF_MONTH,1) 放在 if 条件之后。 (2认同)

Rol*_*and 14

5行代码中没有循环的解决方案

两天之间的定义方式与周一至周五之间ChronoUnit.DAYS.between(start, end)4天数相同.由于我们只对工作日感兴趣,所以我们必须减去周末,因此从周五到周二将有2工作日(只计算endDay - startDay和减去2周末).1如果您想要包含结果,则添加到结果中,即不是两天之间.

我提出两个解决方案.

第一种解决方案(5线,短线和神秘):

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);
}
Run Code Online (Sandbox Code Playgroud)

二解决方案:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 如果开始日期和结束日期都在星期六,或者都在星期日,那么将给出错误的结果。 (2认同)
  • @shanti现在应该修好了.再次感谢您指出这一点. (2认同)

Lev*_*evi 11

我使用了盛源路的解决方案,但我需要修正一个方法被调用的情况,其中一个日期是星期六而另一个是星期日 - 否则答案是一天关闭:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 你为什么不编辑盛源路的解决方案? (6认同)

Bas*_*que 6

java.time

现代方法是使用java.time类.

LocalDate

LocalDate级表示没有时间一天和不同时区的日期,唯一的价值.

LocalDate start = LocalDate.of( 2016 , 1 , 23 );
LocalDate stop = start.plusMonths( 1 );
Run Code Online (Sandbox Code Playgroud)

DayOfWeek 枚举

枚举提供了一个为每个星期的服务器天实例.DayOfWeek

DayOfWeek dow = start.getDayOfWeek();
if( dow.equals( DayOfWeek.SATURDAY ) || dow.equals( DayOfWeek.SUNDAY ) ) …
Run Code Online (Sandbox Code Playgroud)

我们可以收集所需的日期是一个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 );
    …
Run Code Online (Sandbox Code Playgroud)

An EnumSet是一种非常高效,快速且低内存的实现方式Set.我们可以用一个EnumSet而不是if上面的陈述.

Set<DayOfWeek> weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) ;
…
if( weekend.contains( dayOfWeek ) ) …
Run Code Online (Sandbox Code Playgroud)

把它们放在一起.

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.
}
Run Code Online (Sandbox Code Playgroud)

nextWorkingDay TemporalAdjuster

另一种方法是使用ThreeTen-Extra项目添加适用于java.time的类.

Temporals类添加的另外的实施方式TemporalAdjuster用于处理日期时间值.我们希望nextWorkingDay调整器在周六和周日跳过时增加日期.

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 ) ;
    }
}
Run Code Online (Sandbox Code Playgroud)

性能

我对本页各种答案中各种方法的表现感到好奇.我只考虑现代java.time代码,而不是使用麻烦的遗留Date/ Calendar类的代码.

以下是四种方法,每种方法都返回经过的天数.

一个人使用了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;
}
Run Code Online (Sandbox Code Playgroud)

我在本答案中看到的两种使用方法:(a)访问每个日期,在传统循环中逐个递增.

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;
}
Run Code Online (Sandbox Code Playgroud)

......和,(b)使用TemporalAdjuster实施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;
}
Run Code Online (Sandbox Code Playgroud)

最后一个使用Java Streams方法在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;
}
Run Code Online (Sandbox Code Playgroud)

和测试工具.

注意事项:

  • 嗯,关于微观基准测试的常见警告是不值得信任的,容易产生不合理或不切实际的结论.
  • 我希望我学会使用JMH微基准测试框架.
  • 我没有打算尝试优化任何此代码.例如,在实际工作中,TemporalAdjuster可以在我们的方法之外缓存.

测试线束.

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 ) );
Run Code Online (Sandbox Code Playgroud)

当使用Oracle JDK 10.0.1和ThreeTen-Extra版本1.3.2 在我的MacBook Pro(Sierra)上运行时,我得到的结果始终接近以下内容.正如我们所期望的那样,数学解决方案只有几百纳米,而不是几千纳米的数学解决方案.在其他三个中,它TemporalAdjuster是最长的,每个超过10,000纳米.访问量和流量均低于10,000纳米,访问量明显快于流量.正如围绕互联网的其他示例所示,Java Streams通常会生成漂亮的短代码,同时运行时间要长得多,在这种情况下长约20%.

数学:18313309每人:183

访问量:708420626每个:7084

理算员:1002157240每人:10021

流:924724750每个:9247


关于java.time

java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.

现在处于维护模式Joda-Time项目建议迁移到java.time.

要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.

从哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.


Rav*_*ala 6

几乎所有的解决方案都已经过时和叙述。然而,这里有一个非常简洁易读的解决方案。

此方法使用Java 9 及更高版本中内置的方法提供的Java StreamLocalDate::datesUntil

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();
Run Code Online (Sandbox Code Playgroud)

.datesUntil返回一个顺序有序的日期流。返回的流从该日期(含)开始,以 1 天的增量步长进入 endExclusive(独占)。

然后过滤掉所有周六和周日。最后一步是获取剩余工作日的计数。

Java-9 已于一年前发布,因为现在使用它对我来说似乎是合理的。