[英]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--;
}
}
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:您可以执行以下操作,而不是遍历每一天并检查它是否是工作日而不是假期:
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.