Business Calendar Plugin

Hi all

I have a business calendar plugin that we built for Timers: Cycle, DueDate and Duration.

We have custom calendars such as:

@Singleton
@Named("StdWorkWeek")
class StdWorkWeekCalendar : CustomBusinessCalendar {
    override val name: String = "StdWorkWeek"

    override val rules: List<Predicate<ZonedDateTime>> = listOf(
            isWeekday()
                    .and(betweenHours("09:00", "17:00"))
                    .and(betweenHours("12:00", "12:59").negate())
                    .and(isDate("06-01").negate())
    )
    override val incrementer: ((date: Instant) -> Instant)? = null
}

I wanted to get some feedback on this related to the predicates: What sort of predicates do people want/need when building business calendars.

There is currently:

  1. isDate(dates…)
  2. isSpecificDate(dates…)
  3. betweenHours(start, stop)
  4. isDayOfWeek(dayOfWeek…)
  5. isMonth(month…)
  6. isWeekday()
  7. isWeekend()

and then in a timer you can configure it such as (StdWorkWeek)0 0 9 ? * MON-FRI *

incrementer is the custom logic for incrementing next possible time for DueDates and Durations. Such as “plus 1 hour”.

Thoughts?

3 Likes

cross ref for other threads:

  1. Business Calendar Business Hours/Days Logic - Timers, Gateways, generic logic
  2. How to exclude weekend days from Timed Event's duration?
  3. Calculating SLA
  4. Configuring timers for working hours

Hi Stephen,

The most common requirement I come across is find the next nth business day. The challenge with this is finding the next nth weekday is easy, but often a database recording local or regional public holidays is required to be consulted…

regards

Rob

@Webcyberrob like a Due Date for SLA for like 13 business days in future?

Spot on - the challenge I have is local, regional and national public holidays which change from year to year. Hence impossible to derive via algorithm. Thus the inputs are now, number of business days in the future and location…

Having said that, the ability to find the next nth business day is very useful as this is a necessary capability to find the next business day including consideration of holidays…

regards

Rob

@Webcyberrob okay logic has been added for this with some syntax updates for expanded usage.

Can you give me a few more scenarios that you deal with?

val bc = StdBusinessCalendar(
                listOf(
                        Holiday(listOf("2020-06-24", "2020-06-25", "2020-06-26")), // Cannot be any of these dates
                        Is(weekday()), // Must be a weekday (no weekends)
                        Is(weekend().negate()), // example of saying "not a weekend"
                        IfIs(weekday(), betweenHours("09:00", "17:30")) {
                            // Standard workweek, but if something comes in at or after 16:30 (one hour less than regular business hours) then we will just go to next day. (can see how you can add extra special logic where needed).
                            if (it.toLocalTime().isBefore(LocalTime.of(16, 30))) {
                                Duration.ofMinutes(1)
                            } else {
                                Duration.ofDays(1)
                            }
                        },
                        IfIs(date("06-29"), betweenHours("10:00", "12:30")) {
                            // modification to a standard work week with a partial day
                            if (it.toLocalTime().isBefore(LocalTime.of(12, 30))) {
                                Duration.ofMinutes(1)
                            } else {
                                // If it is already past 12:30 then go to next day
                                Duration.ofDays(1)
                            }
                        },
                        Holiday(listOf("2020-06-23"))
                )
        )
bc.nextAvailableDateDayIncrement(now, 10, true).get() // the boolean indicates if the calculation should start +1 days from the provided date.
bc.nextAvailableDate(now).get()
bc.isAvailableDate(now)

The first two return optionals for more complex configurations at the calendar level (such as max attempts to find a date, date must be in this year, within 6 months, etc)

Can add other increment options but more common is increment by days: So if you had a now of June 1 2020 6am, and said increment 10 business days it would give you either June 12 9am or June 15 9am (depends on your usage of the boolean in .nextAvailableDateDayIncrement(...) . The boolean deals with a common calculation scenario where you get a submission right now, but your “2 days” starts on the next business day (such as a submission at 5pm on Friday is likely to say Monday and Tuesday are your SLA with delivery on Wed. Again its optional based on needs.

IfIs(...) means “If the first condition is true, then the second condition must be true”. But if the first is not true, then it still returns as a successful rule. It is another way of saying “here is a sub condition that must be met if the first condition is true, otherwise i dont care.”

Holiday(..) is a wrapper for saying "Must not be this/these dates`. You can also use MonthDay if you know it is the same dates every year. And of course you can write completely custom logic if needed…

I have been moving through a few iterations of naming conversions ConditionalIf, IfIs, Must, etc. Would love feedback!

1 Like

Hi Stephen,

Have you thought/do you think it would be possible to implement these functions as a collection of DMN tables? I could potentially see public holiday dates stored in a DMN table, hence each year an updated table is deployed and thus this DMN table is the ‘database’ of public holidays…

Another thought I had a while back, was to consider implementing these kinds of utility functions as custom FEEL functions such that business date business rules could be constructed using DMN. The Scala FEEL implementation was very easy to extend this way…

regards

Rob

Yes to both: under the hood these are all Java predicate Api with extra sauce for the incrementer logic. So yes you could execute a rule as a DMN: create a class similar to Holiday and implements dmn execution. The only problem with this is speed: dmn will be very slow. But it’s a trade off: you get some cool configuration options with the dmn.

As long as your calendars are made available to the expression resolver (feel or otherwise) then you can call up the calendar and run logic on them within a expression: such as having a Camunda timer (due date) with a expression of ${myCalendar.nextAvailableDateDaysInc(myCalendar.now(), 10)} (10 business days. And a helper for setting a starting point)

Hi Stephen,

In your below post you mentioned about Conditional Business Schedules based on DMN

I have a BPMN that needs to be triggered at various schedules but with different parameters. I can use a DMN to resolve the parameters but how can I specify multiple start times on the same BPMN that do not have a pattern. For example

everyday at 9am → job1 → jobParameters1
weekday at 9am → job2 → jobParameters2
weekend at 9am → job3 → jobParameters3

Effectively you have 2 instances of the same BPMN executing with different parameters at 9am at a given day. How can I achieve this, my understanding is that with timer expressions you can launch only one instance for a resolved due date.

Thanks in advance.

@Ujwal i would probably do something like:

If there is a problem is multiple timer start events, then i would create a BPMN for each timer start event where i configure the execution, and then use a Call Activity to run the common Job BPMN that was configured through the execution of the call activity.

Thanks for the prompt response, I have few hundreds of jobs to be defined that way, neither multiple start events nor individual would be scalable for me as they’d spam the cockpit or tasklist.

I will have to probably implement a custom trigger that starts the processInstance with the variables myself. Although its an easy thing to do, it feels like I am stepping into the territory of the bpm-engine who should ideally be responsible for starting a process instance.

I would likely use the Model API then to build your workflows as code. So you can create create workflows with your timer configs. This will generate your code at compile/runtime and you can deploy from there.

https://docs.camunda.org/manual/7.9/user-guide/model-api/bpmn-model-api/fluent-builder-api/

Sure, thats how I intend to do if I go that route. But I have to ensure that they follow a certain naming convention and use that to hide these resources from user’s tasklist/cockpit otherwise it’d be a mess. That said by hiding these resources I lose the ability to manage/monitor them via Cockpit.

May be I will try this once I get any hints on the other topic I just created.

you can block the startable setting in the modeler config to make it hidden from the tasklist.

I would suggest further questions be moved into a new thread, as this thread is specifically for the Camunda Business Calendar plugin