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

时间:2017-11-15 17:57:40

标签: java date

我要求计算两个给定日期之间的营业日数 我将假期列表作为用户提供的数组列表 所以我可以调查日期之间的每一天,检查它的工作日而不是联邦假期,就像我在下面提供的代码一样(工作正常)

但这是非常昂贵的,比如12个联邦假期,每天我都要检查它不是周末,
因此,如果我需要计算5年之间的数量,它需要365 * 5 * 12的21,000次迭代!它的疯狂(甚至不包括工作日的计算)
还有更好的方法吗?

package test;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.time.DateUtils;

public class TestDiff {

    public static void main(String[] args) throws ParseException {
        DateFormat formatter = new SimpleDateFormat("MM/dd/yy");
        // add 4 years as an example
        Date fromDate = formatter.parse("11/06/2017"),toDate = formatter.parse("11/29/2017");// DateUtils.addDays(fromDate,365 * 4);
        int numberOfDaysCount=0;
        int daysBetween  = daysBetween(fromDate,toDate);
        Date caurDate = fromDate;

        for(int i=0;i<=daysBetween ; i++ ) {
            if(isWeekDay(caurDate) && !isFederalHoliday(caurDate) )
                numberOfDaysCount++;
            caurDate = DateUtils.addDays(caurDate,1); // add one day
        }
        System.out.println("number of business days between "+fromDate+" and "+toDate+" is: "+numberOfDaysCount);
    }

   private static boolean isWeekDay(Date caurDate) {
             Calendar c = Calendar.getInstance();
             c.setTime(caurDate);
              int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
              return dayOfWeek!= Calendar.SATURDAY && dayOfWeek!= Calendar.SUNDAY ;
    }

        private static boolean isFederalHoliday(Date caurDate) throws ParseException {
            DateFormat formatter = new SimpleDateFormat("MM/dd/yy");    //list will come from dao.getFederalHoliday();
                List<Date> federalHolidays =  Arrays.asList(formatter.parse("01/02/2017"),formatter.parse("01/16/2017"),formatter.parse("02/20/2017"),formatter.parse("05/29/2017"),formatter.parse("07/04/2017"),formatter.parse("09/04/2017"),formatter.parse("10/09/2017"),formatter.parse("07/04/2017"),formatter.parse("11/10/2017"),formatter.parse("11/23/2017"),formatter.parse("12/25/2017"));
                for (Date holiday : federalHolidays) {
                    if(DateUtils.isSameDay(caurDate,holiday)) //using Apache commons-lang 
                        return true;
                }
                return false;
    }

        public static int daysBetween(Date d1, Date d2){
             return (int)( (d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
     }

}

4 个答案:

答案 0 :(得分:3)

以下是使用java.time.*在Java 8中实现的答案。

public class TestSo47314277 {

  /**
   * A set of federal holidays. Compared to iteration, using a
   * hash-based container provides a faster access for reading
   * element via hash code. Using {@link Set} avoids duplicates.
   * <p>
   * Add more dates if needed.
   */
  private static final Set<LocalDate> HOLIDAYS;

  static {
    List<LocalDate> dates = Arrays.asList(
        LocalDate.of(2017, 1, 2),
        LocalDate.of(2017, 1, 16),
        LocalDate.of(2017, 2, 20),
        LocalDate.of(2017, 5, 29),
        LocalDate.of(2017, 7, 4),
        LocalDate.of(2017, 9, 4),
        LocalDate.of(2017, 10, 9),
        LocalDate.of(2017, 11, 10),
        LocalDate.of(2017, 11, 23),
        LocalDate.of(2017, 12, 25)
    );
    HOLIDAYS = Collections.unmodifiableSet(new HashSet<>(dates));
  }

  public int getBusinessDays(LocalDate startInclusive, LocalDate endExclusive) {
    if (startInclusive.isAfter(endExclusive)) {
      String msg = "Start date " + startInclusive
          + " must be earlier than end date " + endExclusive;
      throw new IllegalArgumentException(msg);
    }
    int businessDays = 0;
    LocalDate d = startInclusive;
    while (d.isBefore(endExclusive)) {
      DayOfWeek dw = d.getDayOfWeek();
      if (!HOLIDAYS.contains(d)
          && dw != DayOfWeek.SATURDAY
          && dw != DayOfWeek.SUNDAY) {
        businessDays++;
      }
      d = d.plusDays(1);
    }
    return businessDays;
  }
}

答案 1 :(得分:1)

评论中已经提供了很多例子来计算这两个日期之间的工作日数。

就减去联邦假期而言。为什么不在你的fromDate-toDate范围内每年对你的fedeHoliday数组中的所有条目进行循环,而不是遍历fromdate-todate范围内的所有日期。

请原谅伪代码:

int workdays = getWeekdayCount();
for(int i = 0, count = getYearsBetween(); i < count; ++i)
{
    startIndex = (i==0?getIndexFirstHoliday():0);
    endIndex   = (i==(count-1)?getIndexLastHoliday():11);
    for(; startIndex <= endIndex; ++startIndex)
    {
        if(!federalHolidays[startIndex].IsWeekday(count))
            workdays--;
    }
}
  • getWeekdayCount:获取范围内的所有工作日。
  • getIndexFirstHoliday:遍历您的federalHolidays数组并返回日期大于fromDate的第一个索引
  • getIndexLastHoliday:遍历您的federalHolidays数组(向后)并返回日期小于toDate的最后一个索引。
  • isWeekday:确定日期是否是您正在循环的一年中的工作日(如果是,它已经在getWeekdayCount中被丢弃,因此我们不需要减去!)< / LI>

这样,您每年最多循环12次,再加上另外2 * 12来获取第一个和最后一个索引。

答案 2 :(得分:1)

您可以执行以下操作,而不是遍历每一天并检查它是否是工作日而不是假期:

  • 获取两个日期之间的天数
  • 计算完整周数并计算潜在工作日数(乘以 5)或要减去的周末数(乘以 2)
  • 计算要调整部分周的工作日数
  • 获取所需范围内的非周末假期数量并减去这些数量(对于 n 假期,每年最多只需要进行 n 检查 - 并且可以进一步优化)

把所有东西放在一起,它可能看起来像这样:

//start is inclusive, end is exclusive
public long getBusinessDays(LocalDate start, LocalDate end) {
  long days = ChronoUnit.DAYS.between(start, end);

  long fullWeeks = days/7;

  //day of week value ranges from 1 (Monday) to 7 (Sunday)
  //clamp Sunday to Saturday (so treat them as one day) - range is now 1 to 6
  //business days is the difference in days if dow(start) < dow(end)
  //if start and end are on a weekend we'll get 6-6 = 0
  long partialWeekAdjustment = Math.min(6, end.getDayOfWeek().getValue()) -  Math.min(6, start.getDayOfWeek().getValue() );

  //if the result is negative, we have dow(start) > dow(end) so add 5 business days
  //ex.: thu (4) to wed (3) will be 3-4 = -1, so adding 5 will result in 4 (thu, fri, mon, tue)
  if( partialWeekAdjustment < 0 ) {
    partialWeekAdjustment += 5;         
  }

  //get the number of non-weekend holidays between the 2 dates       
  long numNonWeekendHolidays = getNonWeekendHolidays(start, end);

  long businessDays = fullWeeks * 5 + partialWeekAdjustment - numNonWeekendHolidays;
  return businessDays;
}

private long getNonWeekendHolidays(LocalDate start, LocalDate end) {
   //get this from somewhere, could also be something else
   SortedSet<LocalDate> holidays = ...;

   //get holidays between the 2 dates, filter for non-weekend and count
   return holidays.subSet(start, end).stream()
                  .filter(d -> d.getDayOfWeek().compareTo(DayOfWeek.SATURDAY) < 0)
                  .count();

   //if weekend and non-weekend holidays are kept separate this could be:
   // SortedSet<LocalDate> nonWeekendHolidays = ...;
   // return nonWeekendHolidays.subSet(start, end).size();
}

答案 3 :(得分:0)

打击代码是我用Java 8编写的完整项目,可以解决您的问题。

std::unordered_set<IntSet>