Business Calendar Business Hours/Days Logic - Timers, Gateways, generic logic


#1

Another scripting example to share:

A few forum members have asked about how to calculate dates based on Business Calendars / Business Hours/Days:

Example: M-F 8am-5pm are working hours. If something is due “Next Day”, and it’s currently a Friday, it would be due by Monday at 5pm. BUT if Monday is a holiday, then it would be due on Tuesday by 5pm. So how do we calculate this, and how do we do it without having to write a lot of code.

How do we determine if the current DateTime is within Business Hours, where Business Hours are based on a complex schedule + Holidays.

How do we add and subtract business days. etc, etc.


There are some Business Calendar libraries out there, but many of them are very poorly documented (I am talking to you jBPM!!!).

There are two that I found that looked promising:

  1. https://github.com/dhatim/business-hours-java
  2. https://github.com/olejnikk/bizdays

https://github.com/dhatim/business-hours-java looks especially interesting because it will generate Cron Expressions, and so this can be really interesting usage for BPMN Timer events for Cycles.


For these libraries to work, you need to use Java 8. So we deploy Camunda 7.8 :+1:

We have a dockerfile for this:

FROM camunda/camunda-bpm-platform:tomcat-7.8.0

# Copy third-party Java libraries
COPY docker/camunda/lib/* /camunda/lib/

Where we are coping in the following .jar file: https://github.com/DigitalState/camunda-variations/blob/master/business-calendar/docker/camunda/lib/business-hours.jar
This is the https://github.com/dhatim/business-hours-java library.

Then it is as simple as running a Javascript function:

with (new JavaImporter(org.dhatim.businesshours, java.time))
{
    var businessHours = new BusinessHours("wday{Mon-Fri} hour{9am-6pm}, wday{Sat} hour{9am-12pm}");
    var open = businessHours.isOpen(LocalDateTime.now());
}
execution.setVariable('isOpen', open)

This example is a modification from the examples in: https://github.com/dhatim/business-hours-java readme.

Check out the Github Repos for all of the different usage examples.


You can full source code examples in the Business-Calendar folder:


Further Use Cases

  1. Loading Business Hours/Days schedule from a file deployed along with the BPMN
  2. Using DMN to provide business calendar schedules
  3. Conditional Business Schedules based on DMN
  4. Gateway Logic scripts that change flow based on if its currently working hours or if its a holiday
  5. Loading Business Hours/Days schedules from a third-party URL
  6. Calculating future dates based on the planned during of a task (if something takes 6 business days (8 hours per day)(48 hours) to complete, and it is currently Wed, then the due date is the following Wed (even though its 8 days away).
  7. Calculation of number of business days between two dates.
  8. Working Schedules based on User’s personal working schedule
  9. Tie in with Work Assignment logic to choose the current working Staffer.
  10. Run some process(es) at the start / opening of each business day and end of each business day. and then runs some different processes on holidays

How to exclude weekend days from Timed Event's duration?
Pause Timer - Timer only running while worktime hours
Calculating SLA
#2

Update to this with little more detail

some updated script that loads a specific “Business Hours Schedule”. Note that in this example, holidays are not supported.

So we have a script that looks like this:

function loadBusinessCalendarFile(fileName, type)
{
  var processDefinitionId = execution.getProcessDefinitionId()
  var deploymentId = execution.getProcessEngineServices().getRepositoryService().getProcessDefinition(processDefinitionId).getDeploymentId()
  var resource = execution.getProcessEngineServices().getRepositoryService().getResourceAsStream(deploymentId, fileName)

  function getResourceAsString()
  {
    var Scanner = Java.type('java.util.Scanner')
    var scannerResource = new Scanner(resource, 'UTF-8')
    var resourceAsString = scannerResource.useDelimiter('\\Z').next()
    scannerResource.close()
    return resourceAsString
  }

  switch (type.toLowerCase()) {
    case 'json':
      return JSON.parse(getResourceAsString())
    case 'stream':
      return resource
    case 'string':
      return getResourceAsString()
  }
}

function createBusinessHoursCalendar(bhString)
{
  with (new JavaImporter(org.dhatim.businesshours, java.time))
  {
    var businessHours = new BusinessHours(bhString)
  }
  return businessHours
}

function businessCalendar(businessCalendarFileName, scheduleName)
{
  var calendarJson = loadBusinessCalendarFile(businessCalendarFileName, 'json')
  var specificSchedule = calendarJson[scheduleName]
  return createBusinessHoursCalendar(specificSchedule.join(','))
}

var now = Java.type('java.time.LocalDateTime').now()
var openEval = businessCalendar('business-calendar.json', 'schedule1').isOpen(now)

execution.setVariable('isOpen', openEval)

and we have a json file (business-calendar.json) with our schedules (we used json because of the default support it, but would be interesting to add YAML support instead):

{
  "schedule1": [
    "wday{Mon-Fri} hour{9am-6pm}",
    "wday{Sat} hour{9am-12pm}",
    "wday{Mon} hour{5am}",
    "wday{Wed} hour{9pm}",
    "hour{1am}"
  ],
  "schedule2": [
    "wday{Mon-Fri} hour{9am-6pm}",
    "wday{Sat} hour{9am-12pm}"
  ]
}

So all the config is done in the line:

var openEval = businessCalendar('business-calendar.json', 'schedule1').isOpen(now)

where we generate the businessCalendar (as BusinessHours Object) and we provide the name of the json file we are loading (which was uploaded to camunda during the BPMN deployment), and the specific Property that we are loading as our schedule; in this case schedule1. Then we call the .isOpen() method which is part of the BusinessHours object (http://www.javadoc.io/doc/org.dhatim/business-hours/1.0.0).

Enjoy


#3

Reference update on this. The longer term solution for this wast to implement a Vertx webservice that exposes one of the Ruby based Business hour libraries. This was chosen because it drastically simplified business hours usage (as ruby seems to have the most advanced libraries), and using Vertx.io’s polyglot features, we could write the the app in JS but still use ruby library.

See:

and https://github.com/DigitalState/camunda-worker-vertx/tree/feature-ruby-rubygems-stackmanager/worker/verticles/ruby-biz