简体   繁体   English

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

[英]Calculate number of business days between two dates

I have requirement to calculate number of business days between two given dates我需要计算两个给定日期之间的工作日数
I have the list of holidays as an Array list provided by the user.我有假期列表作为用户提供的数组列表。
So I can investigate each and every day between the dates and check if its weekday and not federal holiday like the code I provided below (which is working fine)所以我可以调查日期之间的每一天,并检查它是否是工作日而不是联邦假日,就像我在下面提供的代码(工作正常)

But this is very expensive, lets say 12 federal holidays and each day I will have to check its not a weekend,但这非常昂贵,假设有 12 个联邦假期,每天我都必须检查它不是周末,
so if I need to count between 5 years it will take 365 * 5 * 12 its 21,000 iterations!因此,如果我需要在 5 年之间进行计数,则需要 365 * 5 * 12 的 21,000 次迭代! its crazy (not even including the calculation for business day)太疯狂了(甚至不包括工作日的计算)
Is there a better way?有没有更好的办法?

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

}

Here's an answer implemented in Java 8 using java.time.* .这是使用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;
  }
}

There are many example already given in the comments to calculate how many weekdays are between the two dates.评论中已经给出了许多示例来计算两个日期之间有多少个工作日。

As far as subtracting the the federal holidays goes.至于减去联邦假期。 Instead of looping over all the days in your fromdate-todate range, why don't you loop over all the entries in your federalHoliday array once per year in your fromDate-toDate range.与其循环遍历 fromdate-todate 范围内的所有天数,不如每年在 fromDate-toDate 范围内循环一次 FederalHoliday 数组中的所有条目。

Excuse the pseudo code:原谅伪代码:

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: gets you all of the weekdays in the range. getWeekdayCount:获取范围内的所有工作日。
  • getIndexFirstHoliday: loops through your federalHolidays array and returns the first index where the date is bigger than the fromDate getIndexFirstHoliday:循环遍历您的 FederalHolidays 数组并返回日期大于 fromDate 的第一个索引
  • getIndexLastHoliday: loops through your federalHolidays array (backwards) and returns the last index where the date is smaller than the toDate. getIndexLastHoliday:循环遍历您的 FederalHolidays 数组(向后)并返回日期小于 toDate 的最后一个索引。
  • isWeekday: determines if the date is a weekday in the year you're looping through (if it is, it's already been discarded in getWeekdayCount so we don't need to subtract!) isWeekday:确定日期是否是您循环的年份中的工作日(如果是,它已经在 getWeekdayCount 中被丢弃,所以我们不需要减去!)

This way, you're looping max 12 times per year, plus another 2 * 12 to get the first and the last index.这样,您每年最多循环 12 次,再加上另外 2 * 12 次以获得第一个和最后一个索引。

Instead of iterating over each day and checking whether it is a week day and not a holiday, you could instead do the following:您可以执行以下操作,而不是遍历每一天并检查它是否是工作日而不是假期:

  • get the number of days between your 2 dates获取两个日期之间的天数
  • calculate the number of full weeks and either calculate the number of potential business days (multiply with 5) or the number of weekends to subtract (multiply with 2)计算完整周数并计算潜在工作日数(乘以 5)或要减去的周末数(乘以 2)
  • calculate the number of week days to adjust for partial weeks计算调整部分周的工作日数
  • get the number of non-weekend holidays in the required range and subtract those (for n holidays this would just be up to n checks for each year - and that could be optimized even further)获取所需范围内的非周末假期数量并减去这些数量(对于n假期,每年最多只能进行n检查 - 并且可以进一步优化)

Putting everything together, it could look like this:把所有东西放在一起,它可能看起来像这样:

//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();
}
long numberOfDaysBetween = ChronoUnit.DAYS.between(fromDate, toDate);
                ArrayList<LocalDate> dates = new ArrayList<>();
                for(int i=0; i<=numberOfDaysBetween; i++) {
                    LocalDate l = fromDate.plus(i, ChronoUnit.DAYS);
                    if(l.getDayOfWeek().getValue() != 6 && l.getDayOfWeek().getValue() != 7) {
                        dates.add(l);
                    }
                }

The blow code is a complete project that I've written in java 8 and will solve your problem.打击代码是我用 java 8 编写的一个完整项目,它将解决您的问题。

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class App {

    public static void main(String[] args) {

        System.out.println("Hello World!");
        LocalDate today = LocalDate.of(2020, 5, 5);

        // Add one holiday for testing
        List<LocalDate> holidays = new ArrayList<>();
        holidays.add(LocalDate.of(2020, 5, 11));
        holidays.add(LocalDate.of(2020, 5, 1));
        for (LocalDate x : BusinessDaysBetween(today, today.plusDays(20), Optional.empty()))
        {
            System.out.println(x.toString());
        }
    }
    private static List<LocalDate> BusinessDaysBetween(LocalDate startDate, LocalDate endDate,
                                            Optional<List<LocalDate>> holidays)
    {
        if (startDate == null || endDate == null || !holidays.isPresent()) {
            throw new IllegalArgumentException("Invalid method argument(s) to countBusinessDaysBetween(" + startDate
                    + "," + endDate + "," + holidays + ")");
        }

        Predicate<LocalDate> isHoliday = date -> holidays.map(localDates -> localDates.contains(date)).orElse(false);

        Predicate<LocalDate> isWeekend = date -> date.getDayOfWeek() == DayOfWeek.SATURDAY
                || date.getDayOfWeek() == DayOfWeek.SUNDAY;

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

        return Stream.iterate(startDate, date -> date.plusDays(1)).limit(daysBetween)
                .filter(isHoliday.or(isWeekend).negate()).collect(Collectors.toList());
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM