How can I add business days to the current date in Java?
public Calendar addBusinessDate(Calendar cal, int days) {
//
// code goes over here
//
}
Note:
It should consider weekends too.
You may want to consider using ObjectLab Kit to do the heavy lifting for you.
Assuming the requirement is simply to return the next business day when the computed date falls on a non-business day:
package bizdays.example;
import java.time.LocalDate;
import java.util.HashSet;
import net.objectlab.kit.datecalc.common.DateCalculator;
import net.objectlab.kit.datecalc.common.DefaultHolidayCalendar;
import net.objectlab.kit.datecalc.common.HolidayHandlerType;
import net.objectlab.kit.datecalc.jdk8.LocalDateKitCalculatorsFactory;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
public class BizDayTest {
private DateCalculator<LocalDate> dateCalculator;
private final LocalDate startDate = LocalDate.of(2009, 12, 23);
#Before
public void setUp() {
HashSet<LocalDate> holidays = new HashSet<LocalDate>();
holidays.add(LocalDate.of(2009, 12, 25)); // Friday
DefaultHolidayCalendar<LocalDate> holidayCalendar =
new DefaultHolidayCalendar<LocalDate>(holidays);
LocalDateKitCalculatorsFactory.getDefaultInstance()
.registerHolidays("example", holidayCalendar);
dateCalculator = LocalDateKitCalculatorsFactory.getDefaultInstance()
.getDateCalculator("example", HolidayHandlerType.FORWARD);
dateCalculator.setStartDate(startDate);
}
#Test
public void should_not_change_calendar_start_date_even_after_moving() {
assertThat(
dateCalculator.moveByBusinessDays(6).getStartDate(),
equalTo(startDate));
}
#Test
public void moveByBusinessDays_will_return_24_dec_2009_as_next_business_day() {
assertThat(
dateCalculator.moveByBusinessDays(1).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 24)));
}
#Test
public void moveByBusinessDays_will_return_28_dec_2009_as_two_business_days_later() {
assertThat(
dateCalculator.moveByBusinessDays(2).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 28)));
}
#Test
public void moveByDays_will_also_return_28_dec_2009_as_two_business_days_later() {
assertThat(
dateCalculator.moveByDays(2).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 28)));
}
#Test
public void moveByBusinessDays_will_exclude_25_26_and_27_dec_when_computing_business_days() {
assertThat(
dateCalculator.moveByBusinessDays(5).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 31)));
}
#Test
public void moveByDays_will_include_25_26_and_27_dec_when_computing_business_days() {
assertThat(
dateCalculator.moveByDays(5).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 28)));
}
}
The library defaults the working week to be from Monday to Friday, but you can change the defaults by supplying a custom WorkingWeek to DateCalculator's setWorkingWeek().
As shown in the last two examples, moveByDays() includes the weekends when moving the days, whereas moveByBusinessDays() excludes weekends.
The library also allows you to use java.util.Calendar or Joda Time's LocalDate. The examples use JDK8's java.time.LocalDate because it is the preferred way since JDK8.
Edit: Updated examples to use java.time.LocalDate
Use:
public Calendar addBusinessDate(Calendar cal, int numBusinessDays) {
int numNonBusinessDays = 0;
for(int i = 0; i < numBusinessDays; i++) {
cal.add(Calendar.DATE, 1);
/*
It's a Canadian/American custom to get the Monday (sometimes Friday) off
when a holiday falls on a weekend.
*/
for(int j = 0; j < holidays; j++) { //holidays is list of dates
if(cal.getTime() == (Date)holidays.get(j)) {
numNonBusinessDays++;
}
}
if(cal.get(Calendar.DAY_OF_WEEK) == 1 ||
cal.get(Calendar.DAY_OF_WEEK) == 7) {
numNonBusinessDays++;
}
}
if(numNonBusinessDays > 0) {
cal.add(Calendar.DATE, numNonBusinessDays);
}
return cal;
}
You'd have to populate a list of dates in order to handle holidays. There's common ones like New Years, but Thanksgiving is different between Canada & the US for instance. Also mind that holidays can fall on a weekend, so the weekend becomes a 3 day weekend.
Reference:
Calendar
Calendar Constant Values
PS: There isn't really a need to return the Calendar instance if you are updating the value as in the example. But it is valid if you want to create a separate Calendar instance, use:
public Calendar addBusinessDate(Calendar cal, int numBusinessDays) {
Calendar cal2 = Calendar.getInstance();
cal2.setTime(cal.getTime());
int numNonBusinessDays = 0;
for(int i = 0; i < numBusinessDays; i++) {
cal2.add(Calendar.DATE, 1);
/*
It's a Canadian/American custom to get the Monday (sometimes Friday) off
when a holiday falls on a weekend.
*/
for(int j = 0; j < holidays; j++) { //holidays is list of dates
if(cal2.getTime() == (Date)holidays.get(j)) {
numNonBusinessDays++;
}
}
if(cal2.get(Calendar.DAY_OF_WEEK) == 1 ||
cal2.get(Calendar.DAY_OF_WEEK) == 7) {
numNonBusinessDays++;
}
}
if(numNonBusinessDays > 0) {
cal2.add(Calendar.DATE, numNonBusinessDays);
}
return cal2;
}
Here is the modified version to find date calculation.
public Calendar algorithm2(int businessDays){
Calendar cal2 = Calendar.getInstance();
Calendar cal = Calendar.getInstance();
int totalDays= businessDays/5*7;
int remainder = businessDays % 5;
cal2.add(cal2.DATE, totalDays);
switch(cal.get(Calendar.DAY_OF_WEEK)){
case 1:
break;
case 2:
break;
case 3:
if(remainder >3)
cal2.add(cal2.DATE,2);
break;
case 4:
if(remainder >2)
cal2.add(cal2.DATE,2);
break;
case 5:
if(remainder >1)
cal2.add(cal2.DATE,2);
break;
case 6:
if(remainder >1)
cal2.add(cal2.DATE,2);
break;
case 7:
if(remainder >1)
cal2.add(cal2.DATE,1);
break;
}
cal2.add(cal2.DATE, remainder);
return cal2;
}
//supports negative numbers too.
private Calendar addBusinessDay(final Calendar cal, final Integer numBusinessDays)
{
if (cal == null || numBusinessDays == null || numBusinessDays.intValue() == 0)
{
return cal;
}
final int numDays = Math.abs(numBusinessDays.intValue());
final int dateAddition = numBusinessDays.intValue() < 0 ? -1 : 1;//if numBusinessDays is negative
int businessDayCount = 0;
while (businessDayCount < numDays)
{
cal.add(Calendar.DATE, dateAddition);
//check weekend
if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
{
continue;//adds another day
}
//check holiday
if (isHoliday(cal))//implement isHoliday yourself
{
continue;//adds another day
}
businessDayCount++;
}
return cal;
}
public static Date addBusinessDays(Date date, int days) {
DateTime result = new DateTime(date);
result = isWeekEnd(result)
? getPreviousBusinessDate(result)
: result;
for (int i = 0; i < days; i++) {
if (isWeekEnd(result)) {
i--;
}
result = result.plusDays(1);
}
return result.toDate();
}
private static boolean isWeekEnd(DateTime dateTime) {
int dayOfWeek = dateTime.getDayOfWeek();
return dayOfWeek == DateTimeConstants.SATURDAY || dayOfWeek == DateTimeConstants.SUNDAY;
}
private static DateTime getPreviousBusinessDate(DateTime result) {
while (isWeekEnd(result)) {
result = result.minusDays(1);
}
return result;
}
Will this work? Of course, this is not handling holidays.
public static Date
addBusinessDays(Date baseDate, int
numberOfDays){
if(baseDate == null){
baseDate = new Date();
}
Calendar baseDateCal = Calendar.getInstance();
baseDateCal.setTime(baseDate);
for(int i = 0; i < numberOfDays; i++){
baseDateCal.add(Calendar.DATE,1);
if(baseDateCal.get(Calendar.DAY_OF_WEEK)
== Calendar.SATURDAY){
baseDateCal.add(Calendar.DATE,2);
}
}
return baseDateCal.getTime();
}
tl;dr
Going forward.
myLocalDate.with(
org.threeten.extra.Temporals.nextWorkingDay()
)
Going backward.
myLocalDate.with(
org.threeten.extra.Temporals.previousWorkingDay()
)
Using java.time
The Question and other Answers use the troublesome old date-time classes, now legacy, supplanted by the java.time classes.
Also, see my Answer to a similar Question.
TemporalAdjuster
In java.time, the TemporalAdjuster interface provides for classes to manipulate date-time values. Using immutable objects, a new instance is created with values based on the original.
nextWorkingDay
The ThreeTen-Extra project extend java.time with additional functionality. That includes a nextWorkingDay adjuster that skips over Saturday and Sunday days. So we can loop, incrementing a date one day at a time, and skip over any weekend days.
The LocalDate class represents a date-only value without time-of-day and without time zone.
LocalDate start = LocalDate.now( ZoneId.of( "America/Montreal" ) ) ;
int businessDaysToAdd = 13 ;
// … ensure that: ( businessDaysToAdd >= 0 )
int daysLeft = businessDaysToAdd ;
LocalDate localDate = start ;
while ( daysLeft > 0 ) {
localDate = localDate.with( Temporals.nextWorkingDay() );
daysLeft = ( daysLeft - 1 ) ; // Decrement as we go.
}
return localDate ;
Holidays
Holidays are an entirely different matter. Obviously there is no simple solution. You must either supply a list of your honored holidays, or obtain a list with which you agree.
Once you have such a list, I suggest writing your own implementation of TemporalAdjuster similar to nextWorkingDay.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8 and SE 9 and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
This algorithm calculates the next business date for a given date
(business days are from monday to friday in my country), you can adapt it to iterate the number of days you need to add.
public Calendar nextBusinessDate(Calendar cal) {
List<Calendar> holidays = ********
// Here get list of holidays from DB or some other service...
GregorianCalendar calCp = new GregorianCalendar();
calCp.setTime(cal.getTime());
calCp.add(Calendar.DAY_OF_MONTH, 1);
boolean isSaturday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
boolean isSunday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
boolean isHoliday = holidays.contains(calCp);
while (isSaturday || isSunday || isHoliday) {
if (isSaturday) {
calCp.add(Calendar.DAY_OF_MONTH, +2); // is saturday, make it monday
} else {
if (isSunday) {
calCp.add(Calendar.DAY_OF_MONTH, +1); // is sunday, make it monday
} else {
if (isHoliday) {
calCp.add(Calendar.DAY_OF_MONTH, +1); // is holiday, make it next day
}
}
}
calCp = new GregorianCalendar();
calCp.setTime(cal.getTime());
isSaturday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
isSunday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
isHoliday = holidays.contains(calCp);
} // end while
return calCp;
}
O(1) version that works and supports different weekend patterns and negative days:
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class DateUtil {
//Weekend patterns
public static final int WEEKEND_SAT_SUN = 0;
public static final int WEEKEND_FRI_SAT = 1;
public static final int WEEKEND_THU_FRI = 2;
public static final int WEEKEND_FRI_SUN = 3;
public static final int WEEKEND_FRI = 4;
public static final int WEEKEND_SAT = 5;
public static final int WEEKEND_SUN = 6;
//Weekend pattern by country
//#see https://en.wikipedia.org/wiki/Workweek_and_weekend
public static Map<String,Integer> weekendPatternByCountry = new HashMap<>();
static {
weekendPatternByCountry.put("CO",WEEKEND_SUN); //Colombia
weekendPatternByCountry.put("GQ",WEEKEND_SUN); //Equatorial Guinea
weekendPatternByCountry.put("IN",WEEKEND_SUN); //India
weekendPatternByCountry.put("MX",WEEKEND_SUN); //Mexico
weekendPatternByCountry.put("KP",WEEKEND_SUN); //North Korea
weekendPatternByCountry.put("UG",WEEKEND_SUN); //Uganda
weekendPatternByCountry.put("BN",WEEKEND_FRI_SUN); //Brunei Darussalam
weekendPatternByCountry.put("DJ",WEEKEND_FRI); //Djibouti
weekendPatternByCountry.put("IR",WEEKEND_FRI); //Iran
weekendPatternByCountry.put("AF",WEEKEND_THU_FRI); //Afghanistan
weekendPatternByCountry.put("NP",WEEKEND_SAT); //Nepal
weekendPatternByCountry.put("DZ",WEEKEND_FRI_SAT); //Algeria
weekendPatternByCountry.put("BH",WEEKEND_FRI_SAT); //Bahrain
weekendPatternByCountry.put("BD",WEEKEND_FRI_SAT); //Bangladesh
weekendPatternByCountry.put("EG",WEEKEND_FRI_SAT); //Egypt
weekendPatternByCountry.put("IQ",WEEKEND_FRI_SAT); //Iraq
weekendPatternByCountry.put("IL",WEEKEND_FRI_SAT); //Israel
weekendPatternByCountry.put("JO",WEEKEND_FRI_SAT); //Jordan
weekendPatternByCountry.put("KW",WEEKEND_FRI_SAT); //Kuwait
weekendPatternByCountry.put("LY",WEEKEND_FRI_SAT); //Libya
weekendPatternByCountry.put("MV",WEEKEND_FRI_SAT); //Maldives
weekendPatternByCountry.put("MR",WEEKEND_FRI_SAT); //Mauritania
weekendPatternByCountry.put("MY",WEEKEND_FRI_SAT); //Malaysia
weekendPatternByCountry.put("OM",WEEKEND_FRI_SAT); //Oman
weekendPatternByCountry.put("PS",WEEKEND_FRI_SAT); //Palestine
weekendPatternByCountry.put("QA",WEEKEND_FRI_SAT); //Qatar
weekendPatternByCountry.put("SA",WEEKEND_FRI_SAT); //Saudi Arabia
weekendPatternByCountry.put("SD",WEEKEND_FRI_SAT); //Sudan
weekendPatternByCountry.put("SY",WEEKEND_FRI_SAT); //Syria
weekendPatternByCountry.put("AE",WEEKEND_FRI_SAT); //United Arab Emirates
weekendPatternByCountry.put("YE",WEEKEND_FRI_SAT); //Yemen
}
//Adjustment vectors - precomputed adjustment
static int[][][] adjVector = new int[][][]{
{//WEEKEND_SAT_SUN
//Positive number of days
{1,0,-1,-2,-3,1,1},
{0,0},
{0,0,0,0,0,2,1},
//Negative number of days
{-1,3,2,1,0,-1,-1},
{0,0},
{-1,1,1,1,1,1,0}
},
{//WEEKEND_FRI_SAT
//Positive number of days
{0,-1,-2,-3,1,1,1},
{0,0},
{0,0,0,0,2,1,0},
//Negative number of days
{3,2,1,0,-1,-1,-1},
{0,0},
{1,1,1,1,1,0,-1}
},
{//WEEKEND_THU_FRI
//Positive number of days
{-1,-2,-3,1,1,1,0},
{0,0},
{0,0,0,2,1,0,0},
//Negative number of days
{2,1,0,-1,-1,-1,3},
{0,0},
{1,1,1,1,0,-1,1}
},
{//WEEKEND_FRI_SUN
//Positive number of days
{0,-1,-2,-3,-4,-4,0},
{1,0},
{0,0,0,0,0,-1,1},
//Negative number of days
{4,3,2,1,0,0,4},
{0,-1},
{1,1,1,1,1,0,2}
},
{//WEEKEND_FRI
//Positive number of days
{-1,-2,-3,-4,1,1,0},
{0},
{0,0,0,0,1,0,0},
//Negative number of days
{3,2,1,0,-1,-1,4},
{0},
{1,1,1,1,1,0,1}
},
{//WEEKEND_SAT
//Positive number of days
{0,-1,-2,-3,-4,1,1},
{0},
{0,0,0,0,0,1,0},
//Negative number of days
{4,3,2,1,0,-1,-1},
{0},
{1,1,1,1,1,1,0}
},
{//WEEKEND_SUN
//Positive number of days
{1,0,-1,-2,-3,-4,1},
{0},
{0,0,0,0,0,0,1},
//Negative number of days
{-1,4,3,2,1,0,-1},
{0},
{0,1,1,1,1,1,1}
}
};
//O(1) algorithm to add business days.
public static Date addBusinessDays(Date day, int days,int weekendPattern){
Calendar ret = Calendar.getInstance();
if(day != null) {
ret.setTime(day);
}
if(days != 0) {
int startDayofWeek = ret.get(Calendar.DAY_OF_WEEK)-1; //Zero based to use the vectors bellow.
int idx = days > 0 ? 0 : 3;
int howManyWeekendDays = 0;
int[][] adjV = adjVector[weekendPattern];
int numWeekendDaysInOneWeek = adjV[idx+1].length;
for(int i = 0; i < numWeekendDaysInOneWeek;i++){
int adjustmentA = adjV[idx][startDayofWeek]; //pattern shift
int adjustmentB = adjV[idx+1][i]; //day shift
howManyWeekendDays += (days-adjustmentA-adjustmentB)/(7-numWeekendDaysInOneWeek);
}
int adjustmentC = adjV[idx+2][startDayofWeek]; //f(0) adjustment
howManyWeekendDays += adjustmentC;
ret.add(Calendar.DATE,days + howManyWeekendDays);
//TODO: Extend to support holidays using recursion
// int numHolidays = getNumHolidaysInInterval(day,ret.getTime());
// if(numHolidays > 0) return addBusinessDays(ret.getTime,numHolidays);
}
return ret.getTime();
}
public static Date addBusinessDays(Date day, int days,String country){
Integer weekpat = weekendPatternByCountry.get(country);
return weekpat != null ? addBusinessDays(day,days,weekpat) : addBusinessDays(day,days,WEEKEND_SAT_SUN);
}
}
This is the method I came up with:
private Date addLaborDays(Integer days, Date date){
Collection<Date> holidaysList = getHolidays();
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DATE, 1);
Date dateTemp = cal.getTime();
if(days == 1) return dateTemp;
if(holidaysList.contains(dateTemp) || DateUtil.isWeekend(dateTemp)){
return addLaborDays(days, dateTemp);
} else {
return addLaborDays(days-1, dateTemp);
}
}
Method getHolidays() queries a custom holidays database table, and method DateUtil.isWeekend(dateTemp) returns true if dateTemp is Saturday or Sunday.
/* To Calculate 10 business days ahead of today's date
*/
public class DueDate {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
DueDate d = new DueDate();
String dueDate = d.getDueDate(10);
System.out.println("due Date " + dueDate);
}
public String getDueDate(int bday){
Calendar cal = new GregorianCalendar();
SimpleDateFormat fdate = new SimpleDateFormat("MM/dd/yyyy");
while(bday > 0){
cal.add(Calendar.DAY_OF_MONTH, 1);
if(noWeekendsorHolidays(cal)){
bday--;
}
}
return fdate.format(cal.getTime());
}
public boolean noWeekendsorHolidays(Calendar cal){
int day = cal.get(Calendar.DAY_OF_WEEK);
if(day == 1 || day == 7){
return false;
}
return true;
}
}
This one works for me, short and simple:
public static Date getBusinessDay(final Date date, final int businessDaysFromDate) {
final int max = 60;
if (date == null) {
return getBusinessDay(new Date(), businessDaysFromDate);
} else if (date != null && (businessDaysFromDate < 0 || businessDaysFromDate > max)) {
return getBusinessDay(date, 0);
} else {
final Calendar baseDateCal = Calendar.getInstance();
baseDateCal.setTime(date);
for (int i = 1; i <= businessDaysFromDate; i++) {
baseDateCal.add(Calendar.DATE, 1);
while (baseDateCal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || baseDateCal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
baseDateCal.add(Calendar.DATE, 1);
}
}
return baseDateCal.getTime();
}
}
Adding two business days to current date:
Date today = new Date();
Calendar cal1 = Calendar.getInstance();
cal1.setTime(today);
switch(cal1.get(Calendar.DAY_OF_WEEK)){
case 1:
cal1.add(Calendar.DATE, 2);
break;
case 2:
cal1.add(Calendar.DATE, 2);
break;
case 3:
cal1.add(Calendar.DATE, 2);
break;
case 4:
cal1.add(Calendar.DATE, 2);
break;
case 5:
cal1.add(Calendar.DATE, 4);
break;
case 6:
cal1.add(Calendar.DATE, 4);
break;
case 7:
cal1.add(Calendar.DATE, 3);
break;
}
// You may also set the time to meet your purpose:
cal1.set(Calendar.HOUR_OF_DAY, 23);
cal1.set(Calendar.MINUTE, 59);
cal1.set(Calendar.SECOND, 59);
cal1.set(Calendar.MILLISECOND, 00);
Date twoWeekdaysAhead = cal1.getTime();
Most of the answer I've found online didn't work as expected, so I tweaked an example on this thread, How to get current date and add five working days in Java. The code below appears to work better.
public static Date addWorkingDays(Date date, int days) {
if (days > 0) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int daysAdded = 0;
do {
cal.add(Calendar.DATE, 1);
if (isWorkingDay(cal)) {
daysAdded++;
}
} while (daysAdded < days);
return cal.getTime();;
} else {
return date;
}
}
private static boolean isWorkingDay(Calendar cal) {
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
if (dayOfWeek == Calendar.SUNDAY || dayOfWeek == Calendar.SATURDAY)
return false;
// tests for other holidays here
return true;
}