Calculate number of business days between two dates

眉间皱痕 提交于 2020-12-25 04:49:09

问题


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,
so if I need to count between 5 years it will take 365 * 5 * 12 its 21,000 iterations! 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));
     }

}

回答1:


Here's an answer implemented in Java 8 using java.time.*.

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



回答2:


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.

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.
  • getIndexFirstHoliday: loops through your federalHolidays array and returns the first index where the date is bigger than the fromDate
  • getIndexLastHoliday: loops through your federalHolidays array (backwards) and returns the last index where the date is smaller than the 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!)

This way, you're looping max 12 times per year, plus another 2 * 12 to get the first and the last index.




回答3:


The blow code is a complete project that I've written in java 8 and will solve your problem.

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


来源:https://stackoverflow.com/questions/47314277/calculate-number-of-business-days-between-two-dates

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!