views:

3367

answers:

6

Does anyone knows a good business calendar library in java?

It should handle easy :) date calculations, taking holidays into account.

Ideally, besides configuring holidays and company off days, we should also be able to configure 'working hours' on a day basis so we can calculate SLA's and KPI's on working hours.

I know something like this is part of jboss jBpm, but I was wondering if their was any other project doing this.

Off course, open source is a big plus point!

+2  A: 

for date calculations try joda-time.sourceforge.net

but i have no idea about what you mean by configuring holidays. because each country has different holidays. but try that one first, it is good for date and time calculation.

nightingale2k1
Joda time is great but it's a generic replacement of the crapy java Date hell.What I mean, is a configurable calendar, where e.g. a company can configure all the holidays in a file, and also what is considered as the working hours a a day basis.So the library will expose a api for doing date calculations taking into account the configured holidays, working hours etc..For example if you have an SLA of two business days, and you start open a ticket on friday, than your sla due date is Tuesday and not Sunday. Or even wednesday, if monday was a day off.
HeDinges
@HeDinges Check out what I posted below. It's not really an API per se, but it could be extended to have more of that feel. Instead of having a "get next business day" it could take in an additional parameter to find the Nth business day, essentially just looping over the "get next business day". As for the configuration of the days of the week, holidays configurable from an external file, ect. That too would be possible, I just didn't have the need for something as robust as that.
jnt30
A: 

I would suggest creating your own domestic holiday class that you can manage each of the holidays in. All of the holidays have rules on which day they will be. It is easy enough to program for these dates each year.

Martin Luther King day for example:

private static Date holidayHumanRights(int parmYear)
    {
     Date tempDate = new Date(parmYear, 0, 1); //January 1st...

     try
     {
      tempDate = getNextDayofWeek(tempDate, "Monday");

      //now point towards the 3rd Monday, which would be 2 weeks from
      //current Monday date...
      tempDate.advanceDays(2*7);
     }
     catch (Exception ex)
     {
            //throw or suppress the error, your choice
      System.err.println(ex.toString());
     }

     return tempDate;
    }
northpole
Yes doing it yourself is one option, but this 'business calendar' idea seems generic enough, that someone should already have written a library for this.
HeDinges
well, when I wrote the "business calendar" for my current project I could not find a free library so I created my own. So +1 for the question and I will be watching for an example I can also use.
northpole
+2  A: 

Below is a very longwinded answer. It's something that I put together for exactly this purpose. It's not super user friendly, but it should give you want you are looking for.

It relies on the Apache commons project which can be acquired here: http://commons.apache.org/lang/

package com.yourPackageName;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BusinessDayUtil {
    private static Log log = LogFactory.getLog(BusinessDayUtil.class);
    private static transient Map<Integer, List<Date>> computedDates = new HashMap<Integer, List<Date>>();

    /*
     * This method will calculate the next business day 
     * after the one input.  This means that if the next 
     * day falls on a weekend or one of the following 
     * holidays then it will try the next day. 
     * 
     * Holidays Accounted For: 
     * New Year's Day
     * Martin Luther King Jr. Day
     * President's Day 
     * Memorial Day 
     * Independence Day
     * Labor Day 
     * Columbus Day 
     * Veterans Day
     * Thanksgiving Day 
     * Christmas Day
     *  
     */
    public static boolean isBusinessDay(Date dateToCheck)
    {
     //Setup the calendar to have the start date truncated 
     Calendar baseCal = Calendar.getInstance();
     baseCal.setTime(DateUtils.truncate(dateToCheck, Calendar.DATE));

     List<Date> offlimitDates;

     //Grab the list of dates for the year.  These SHOULD NOT be modified. 
     synchronized (computedDates)
     {
      int year = baseCal.get(Calendar.YEAR);

      //If the map doesn't already have the dates computed, create them.
      if (!computedDates.containsKey(year))
       computedDates.put(year, getOfflimitDates(year));
      offlimitDates = computedDates.get(year);
     }

     //Determine if the date is on a weekend. 
     int dayOfWeek = baseCal.get(Calendar.DAY_OF_WEEK);
     boolean onWeekend =  dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY;

     //If it's on a holiday, increment and test again 
     //If it's on a weekend, increment necessary amount and test again
     if (offlimitDates.contains(baseCal) || onWeekend)
      return false;
     else 
      return true;
    }


    /**
     * 
     * This method will calculate the next business day 
     * after the one input.  This leverages the isBusinessDay
     * heavily, so look at that documentation for further information.
     * 
     * @param startDate the Date of which you need the next business day.
     * @return The next business day.  I.E. it doesn't fall on a weekend, 
     * a holiday or the official observance of that holiday if it fell 
     * on a weekend. 
     *  
     */
    public static Date getNextBusinessDay(Date startDate)
    {
     //Increment the Date object by a Day and clear out hour/min/sec information
     Date nextDay = DateUtils.truncate(addDays(startDate, 1), Calendar.DATE);
     //If tomorrow is a valid business day, return it
     if (isBusinessDay(nextDay))
      return nextDay;
     //Else we recursively call our function until we find one. 
     else
      return getNextBusinessDay(nextDay);
    }

    /*
     * Based on a year, this will compute the actual dates of 
     * 
     * Holidays Accounted For: 
     * New Year's Day
     * Martin Luther King Jr. Day
     * President's Day 
     * Memorial Day 
     * Independence Day
     * Labor Day 
     * Columbus Day 
     * Veterans Day
     * Thanksgiving Day 
     * Christmas Day
     * 
     */
    private static List<Date> getOfflimitDates(int year)
    {
     List<Date> offlimitDates = new ArrayList<Date>();

     Calendar baseCalendar = GregorianCalendar.getInstance();
     baseCalendar.clear();

     //Add in the static dates for the year.
     //New years day
     baseCalendar.set(year, Calendar.JANUARY, 1);
     offlimitDates.add(offsetForWeekend(baseCalendar));

     //Independence Day
     baseCalendar.set(year, Calendar.JULY, 4);
     offlimitDates.add(offsetForWeekend(baseCalendar));

     //Vetrans Day
     baseCalendar.set(year, Calendar.NOVEMBER, 11);
     offlimitDates.add(offsetForWeekend(baseCalendar));

     //Christmas
     baseCalendar.set(year, Calendar.DECEMBER, 25);
     offlimitDates.add(offsetForWeekend(baseCalendar));

     //Now deal with floating holidays.
     //Martin Luther King Day 
     offlimitDates.add(calculateFloatingHoliday(3, Calendar.MONDAY, year, Calendar.JANUARY));

     //Presidents Day
     offlimitDates.add(calculateFloatingHoliday(3, Calendar.MONDAY, year, Calendar.FEBRUARY));

     //Memorial Day
     offlimitDates.add(calculateFloatingHoliday(0, Calendar.MONDAY, year, Calendar.MAY));

     //Labor Day
     offlimitDates.add(calculateFloatingHoliday(1, Calendar.MONDAY, year, Calendar.SEPTEMBER));

     //Columbus Day
     offlimitDates.add(calculateFloatingHoliday(2, Calendar.MONDAY, year, Calendar.OCTOBER));

     //Thanksgiving Day and Thanksgiving Friday
     Date thanksgiving = calculateFloatingHoliday(4, Calendar.THURSDAY, year, Calendar.NOVEMBER);
     offlimitDates.add(thanksgiving);
     offlimitDates.add(addDays(thanksgiving, 1));


     return offlimitDates;
    }


    /**
     * This method will take in the various parameters and return a Date objet
     * that represents that value. 
     * 
     * Ex. To get Martin Luther Kings BDay, which is the 3rd Monday of January, 
     * the method call woudl be:
     * 
     * calculateFloatingHoliday(3, Calendar.MONDAY, year, Calendar.JANUARY);
     * 
     * Reference material can be found at: 
     * http://michaelthompson.org/technikos/holidays.php#MemorialDay
     * 
     * @param nth 0 for Last, 1 for 1st, 2 for 2nd, etc. 
     * @param dayOfWeek Use Calendar.MODAY, Calendar.TUESDAY, etc. 
     * @param year 
     * @param month Use Calendar.JANUARY, etc. 
     * @return
     */
    private static Date calculateFloatingHoliday(int nth, int dayOfWeek, int year, int month)
    {
     Calendar baseCal = Calendar.getInstance();
     baseCal.clear();

     //Determine what the very earliest day this could occur.
     //If the value was 0 for the nth parameter, incriment to the following
     //month so that it can be subtracted alter. 
     baseCal.set(year, month + ((nth <= 0) ? 1 : 0), 1);
     Date baseDate = baseCal.getTime();

     //Figure out which day of the week that this "earliest" could occur on 
     //and then determine what the offset is for our day that we actually need. 
     int baseDayOfWeek = baseCal.get(Calendar.DAY_OF_WEEK);
     int fwd = dayOfWeek - baseDayOfWeek;

     //Based on the offset and the nth parameter, we are able to determine the offset of days and then 
     //adjust our base date. 
     return addDays(baseDate, (fwd + (nth - (fwd >= 0 ? 1 : 0)) * 7));
    }

    /*
     * If the given date falls on a weekend, the
     * method will adjust to the closest weekday.
     * I.E. If the date is on a Saturday, then the Friday
     * will be returned, if it's a Sunday, then Monday 
     * is returned.  
     */
    private static Date offsetForWeekend(Calendar baseCal)
    {
     Date returnDate = baseCal.getTime();
     if (baseCal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY)
     {
      if (log.isDebugEnabled())
       log.debug("Offsetting the Saturday by -1: " + returnDate);
      return addDays(returnDate, -1);
     }
     else if (baseCal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
     {
      if (log.isDebugEnabled())
       log.debug("Offsetting the Sunday by +1: " + returnDate);
      return addDays(returnDate, 1);
     }
     else
      return returnDate;
    }

    /**
     * Private method simply adds 
     * @param dateToAdd
     * @param numberOfDay
     * @return
     */
    private static Date addDays(Date dateToAdd, int numberOfDay)
    {
     if (dateToAdd == null)
      throw new IllegalArgumentException("Date can't be null!");
     Calendar tempCal = Calendar.getInstance();
     tempCal.setTime(dateToAdd);
     tempCal.add(Calendar.DATE, numberOfDay);
     return tempCal.getTime();
    }
}
jnt30
In my opinion it is not generic enough. What if you need to take hours into account, I mean, you want to count the number of working hours between two dates...
Guido
A: 

While thinking of the same problem I found out a Quartz Calendar. It has several problems like:

  1. It is an implementation part of a scheduling library - using it apart from all quartz just as a holiday calendar is a bit hackish.
  2. It has getNextIncludeTime method but no getPrevIncludeTime.
  3. It has ugly and inconsistent API - AnnualCalendar has getter and setter that takes ArrayList, MonthlyCalendar has getter and setter that takes boolean[], both of them just expose class internals.
  4. It has some poorly documented issues - you can chain calendars, but order of chaining is important - DailyCalendar created on AnnualCalendar is OK, AnnualCalendar created on DailyCalendar will break (hang, I think).

Still it is the best thing I could find. So maybe just take the source code, fix what's wrong and add what's missing?

Tadeusz Kopec
A: 

jBPM (v3 at least) has a good business calendar implementation.

If you don't want the whole dependency on JBPM, I think you can take out just the calendar package

Bozho
+3  A: 

Check out this library, it has functionality for holidays and such, it's built around joda.

http://objectlabkit.sourceforge.net/

eli