34

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!

Jonik
  • 80,077
  • 70
  • 264
  • 372
HeDinges
  • 4,367
  • 4
  • 30
  • 28
  • did you find a good solution for your needs? please provide feedback. – Bozho Feb 11 '10 at 13:02
  • Actually no, haven't found a separate project for this, that's why I haven't closed the question yet. The only thing that get close is the jBpm business calendar. – HeDinges Feb 11 '10 at 14:11
  • See [this Answer](http://stackoverflow.com/a/42147195/642706) about [Nager.Date](https://github.com/tinohager/Nager.Date) project. – Basil Bourque Feb 17 '17 at 03:35
  • This kind of Question may be posted on the sister site: [*Software Recommendations Stack Exchange*](https://softwarerecs.stackexchange.com/) – Basil Bourque Jan 26 '18 at 01:11
  • Duplicate of: [Best free library or database to determine if a date is a US or international holiday?](https://stackoverflow.com/q/736852/642706) – Basil Bourque Jan 26 '18 at 06:53
  • I've just released BusinessCalendar4J - https://github.com/yusuke/businessCalendar4J . Compatible with Java 8+. – YAMAMOTO Yusuke May 24 '21 at 05:52

7 Answers7

19

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

http://objectlabkit.sourceforge.net/

eli
  • 448
  • 4
  • 11
  • The objectlab date library has a JDK-only version as well, if you don't want to use joda-time. – Joshua Davis Dec 23 '11 at 20:14
  • 1
    It looks like the objectlabkit only supports Day-level computation. E.g. LocalDate newCurrent = cal.moveByDays(4).getCurrentBusinessDate(). It has no cal.moveByHours() or similar APIs related to "working hours". – kshen Sep 26 '13 at 09:48
  • 1
    built a sample maven project and used it's joda implementation with the example. Changed the move date on line 26 (in the example on the site) from 4 to 5, or 6. And the date remained the same at 04/09/2006. It clearly didn't work for me. Any one experience similar problems? – JackDev Aug 18 '14 at 05:11
5

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.getTime()) || 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();
    }
}
Mr Chow
  • 365
  • 6
  • 10
jnt30
  • 1,367
  • 2
  • 15
  • 21
3

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
  • 588,226
  • 146
  • 1,060
  • 1,140
2

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
  • 10,095
  • 15
  • 70
  • 96
  • 3
    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 Jun 25 '09 at 16:45
  • @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 Jun 27 '09 at 01:50
0

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
  • 10,244
  • 7
  • 35
  • 58
  • 3
    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 Jun 25 '09 at 16:52
  • 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 Jun 25 '09 at 17:19
0

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 for Ukraine
  • 12,283
  • 6
  • 56
  • 83
0

I recently developed this open source project http://lamma.io which is designed for date generation.

For example:

Date(2015, 10, 5) to Date(2015, 10, 15) by 2 except Weekends

will yield

List(2015-10-05, 2015-10-07, 2015-10-09, 2015-10-13, 2015-10-15)

The project is licensed under DO WHAT YOU WANT TO PUBLIC LICENSE, so feel free to use / redistribute :)

Max
  • 2,065
  • 24
  • 20