/**
* @author Ulrich Krause
*/
package de.eknori.ssjsplus.javascript;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class BusinessDayCalculator {
public static final SimpleDateFormat DEFAULT_FORMAT = new SimpleDateFormat(
"dd/mm/yyyy");
/**
* Use to find out what date is a certain number of days after a base date,
* not counting certain days of the week.
*
* @param offset
* Number of non-excluded days from the baseDate that the result
* date will be
* @param baseDate
* Date to start counting offset from. If specify "null" defaults
* to today
* @param excludedDaysOfWeek
* Array of constants from java.util.Calendar object e.g.
* Calendar.SUNDAY, which are excluded from counting towards
* offset for the result
* @return The earliest date which is offset days after the baseDate. In
* counting towards this result date, any excludedDaysOfWeek are
* excluded from counting towards the offset
*/
public static Date getNextBusinessDay(int offset, Date baseDate,
int[] excludedDaysOfWeek) {
return getNextBusinessDay(offset, baseDate, excludedDaysOfWeek,
(Hashtable) null);
}
/**
* Use to find out what date is a certain number of days after a base date,
* not counting certain days of the week and certain dates ( company holiday etc ).
*
* @param offset
* Number of non-excluded days from the baseDate that the result
* date will be
* @param baseDate
* Date to start counting offset from. If specify "null" defaults
* to today
* @param excludedDaysOfWeek
* Array of constants from java.util.Calendar object e.g.
* Calendar.SUNDAY, which are excluded from counting towards
* offset for the result
* @param excludedDateList
* Array of date strings (formatted as
* <CODE>SimpleDateFormat.getDateInstance().format(</CODE>))
* which represent specific dates which should be excluded from
* the counting of days, such as holidays
* @return The earliest date which is offset days after the baseDate. In
* counting towards this result date, any excludedDaysOfWeek and excludedDates are
* excluded from counting towards the offset
*/
public static Date getNextBusinessDay(int offset, Date baseDate,
int[] excludedDaysOfWeek, String[] excludedDateList) {
Hashtable excludedDates = new Hashtable();
if (excludedDateList != null)
for (int i = 0; i < excludedDateList.length; i++) {
try {
excludedDates.put(
DateFormat.getDateInstance().parse(
excludedDateList[i]), "");
} catch (ParseException err) {
}
}
return getNextBusinessDay(offset, baseDate, excludedDaysOfWeek,
excludedDates);
}
/**
* Use to find out what date is a certain number of days after a base date,
* not counting certain days.
*
* @param offset
* Number of non-excluded days from the baseDate that the result
* date will be
* @param baseDate
* Date to start counting offset from. If specify "null" defaults
* to today
* @param excludedDaysOfWeek
* Array of constants from java.util.Calendar object e.g.
* Calendar.SUNDAY, which are excluded from counting towards
* offset for the result
* @param excludedDateList
* Array of date objects which represent specific dates which
* should be excluded from the counting of days, such as holidays
* @return The earliest date which is offset days after the baseDate. In
* counting towards this result date, any excludedDaysOfWeek are
* excluded from counting towards the offset
*/
public static Date getNextBusinessDay(int offset, Date baseDate,
int[] excludedDaysOfWeek, Date[] excludedDateList) {
Hashtable excludedDates = new Hashtable();
if (excludedDateList != null)
for (int i = 0; i < excludedDateList.length; i++)
excludedDates.put(excludedDateList[i], "");
return getNextBusinessDay(offset, baseDate, excludedDaysOfWeek,
excludedDates);
}
/**
* <P>
* Use to find out what date is a certain number of days after a base date,
* not counting certain days.
*
* <P>
* <b>Primary example:</B> Give me the date which is at least 3 business
* days from today.
* <P>
* <b>Usage for example:</B>
* <UL>
* <LI><CODE>offset</CODE> = 3
* <LI><CODE>baseDate</CODE> = today (i.e. <CODE>new Date()</CODE> )
* <LI><CODE>excludedDaysOfWeek</CODE> = array with values for Sat and Sun
* (i.e. <CODE>{ Calendar.SATURDAY, Calendar.SUNDAY }</CODE> )
* <LI><CODE>excludedDates</CODE> = <CODE>Hashtable</CODE> with elements
* whose keys are <CODE>java.util.Date</CODE> objects equal to various
* business holidays not to be counted towards progress
* </UL>
*
* @param offset
* Number of non-excluded days from the baseDate that the result
* date will be
* @param baseDate
* Date to start counting offset from. If specify "null,"
* defaults to today
* @param excludedDaysOfWeek
* Array of constants from java.util.Calendar object e.g.
* Calendar.SUNDAY, which are excluded from counting towards
* offset for the result
* @param excludedDates
* Hashtable of java.util.Date objects, which represent specific
* dates (12:00am midnight) which should be excluded from
* counting towards the offset
* @return The earliest date which is offset days after the baseDate. In
* counting towards this result date, any excludedDaysOfWeek or
* specifically excludedDates are excluded from counting towards the
* offset
*/
public static Date getNextBusinessDay(int offset, Date baseDate,
int[] excludedDaysOfWeek, Hashtable excludedDates) {
int i;
if (offset < 0)
return null;
if (baseDate == null) {
baseDate = new Date();
}
Calendar baseCopy = Calendar.getInstance();
Calendar baseCal = Calendar.getInstance();
baseCopy.setTime(baseDate);
baseCal.clear();
baseCal.set(baseCopy.get(Calendar.YEAR), baseCopy.get(Calendar.MONTH),
baseCopy.get(Calendar.DATE));
if (excludedDaysOfWeek == null) {
excludedDaysOfWeek = new int[0];
}
if (excludedDates == null) {
excludedDates = new Hashtable();
}
Hashtable excludedDays = new Hashtable();
for (i = 0; i < excludedDaysOfWeek.length; i++)
excludedDays.put(new Integer(excludedDaysOfWeek[i]), "");
while (isExcluded(baseCal, excludedDays, excludedDates)) {
baseCal.add(Calendar.DATE, 1);
}
Calendar resultCal = Calendar.getInstance();
resultCal.setTime(baseCal.getTime());
int daysPerWeek = resultCal.getMaximum(Calendar.DAY_OF_WEEK);
int daysPerWeekNotExcluded = daysPerWeek - excludedDays.size();
int wholeWeeksAway = offset / daysPerWeekNotExcluded;
int remainingoffset = offset % daysPerWeekNotExcluded;
resultCal.add(Calendar.DATE, wholeWeeksAway * daysPerWeek);
int nExcludedDates = 0;
Enumeration exclDates = excludedDates.keys();
while (exclDates.hasMoreElements()) {
Date nextExclDate = (Date) exclDates.nextElement();
Calendar nextExclCal = Calendar.getInstance();
nextExclCal.setTime(nextExclDate);
if ((!excludedDays.containsKey(new Integer(nextExclCal
.get(Calendar.DAY_OF_WEEK))))
&& (baseCal.before(nextExclCal))
&& (!resultCal.before(nextExclCal))) {
nExcludedDates++;
}
}
remainingoffset = remainingoffset + nExcludedDates;
for (i = 0; i < remainingoffset; i++) {
do {
resultCal.add(Calendar.DATE, 1);
} while (isExcluded(resultCal, excludedDays, excludedDates));
}
return resultCal.getTime();
}
/**
* Tells you if the checkCal day/date is in one of the exclusion lists
*
* @param checkCal
* Calendar objects representing the date to check
* @param excludedDays
* Hashtable with Integer keys representing days of the week
* which count as excluded. Integer keys are constants from
* Calendar class, e.g. Calendar.SUNDAY
* @param excludedDates
* Hashtable with elements with java.util.Date objects as keys
* which represent specific dates which should be excluded. Dates
* should all be set on 12am midnight for correct comparison.
* @return True if the checkCal date is one of the days or dates that are to
* be excluded, as specified by the excludedDays and excludedDates
* parameters
*/
public static boolean isExcluded(Calendar checkCal, Hashtable excludedDays,
Hashtable excludedDates) {
Calendar testCal = Calendar.getInstance();
testCal.clear();
testCal.set(checkCal.get(Calendar.YEAR), checkCal.get(Calendar.MONTH),
checkCal.get(Calendar.DAY_OF_MONTH));
return ((excludedDays != null && excludedDays.containsKey(new Integer(
checkCal.get(Calendar.DAY_OF_WEEK)))) || (excludedDates != null && excludedDates
.containsKey(testCal.getTime())));
}
public static void main(String[] args) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy");
Date baseDate = dateFormat.parse("7.5.2012");
int[] excludedDays = new int[] { Calendar.SATURDAY, Calendar.SUNDAY };
String[] excludedDates = new String[] { "08.05.2012", "09.05.2012" };
Date NBD = getNextBusinessDay(1, baseDate, excludedDays, excludedDates);
System.out.println("getNextBusinessDay: " + NBD);
}
}