Estimated time to read: 12 minutes
TL; DR: When incrementing a date by business days, we may want to skip any holidays / non-working days. Let's build a rule help do that!
What problem are we solving?
Observation: This design choice has bothered me for years. One can understand a need for different spaces (formerly called projects) and boards supporting a different list of working / non-working days. Teams could be in different geographic locations, have different employment relationships, etc. However, why not have a Jira site definition of working day / non-working date lists and add association tables for which space / board uses which values? Certainly, the current design seems to sprawl the data unnecessarily.
Ways to solve it…and some limitations
Many may consider holidays to also be called "non-working days"; let's use the term "holidays" for the remainder of this article for consistency.
I can think of at least two ways to solve this: math and lists. I have implemented both of these techniques with rules, and found both have limitations.
- Math – For a starting date, increment it using the built-in business days functions. Then count any holidays within that date range (i.e., starting date -> ending date), and use that count to increment again. Repeat these steps until no further increments are found.
- Lists – For a starting date, generate an ordered list of possible business dates into the future (e.g. out to a few hundred days). Remove any known holidays from the list to shorten it. The last step is now easy: we index into the remaining date list with the desired increment to find the date.
The first method is difficult as automation rules do not have sequential branching or recursion within a single rule's scope. There are workarounds for these difficulties, yet the resulting rule is quite complicated. (As an aside, the newer branching features for Premium / Enterprise license levels may help, although I have not tried those.) The second approach with lists is simpler, and it relies upon some assumptions about list behavior and how large the increment may be.
Let's build the second approach using lists as it is simpler to try.
Approach walkthrough
We briefly described the list approach, and let's add a visual to help describe it further:
In this example, we have a Start Date of 5 January 2026, or 2026-01-05 in jiraDate format. We want to increment that by 35 business days, skipping over the holidays in our list. For this example, and rule, I am using some common federal holidays in the United States during 2026.
- We count out a bunch of business days. Let's go out a few hundred to make a list. Note this list still contains the holiday dates.
- We use the values in the holidays list to strip out any matching dates, compressing the resulting list to update the indices, from 0 to N. That "strip out" will happen later using a regular expression.
- The last step is now easy as we just find the 35-th entry in the list, and that is the answer date: 2026-02-25.
How to build the rule
As with many of my sample rules, we will use a Scheduled Trigger and write results using the Log action. This is a helpful tip to build many rules, experiment a lot, and not consume your valuable monthly usage counts / limits. You may adjust later for your specific rule needs: trigger, actions, etc.
Here is a picture of the complete rule, with the audit log showing a sample execution. This rule uses techniques from a couple of earlier articles, and I will add links to those where they apply.
And for the specifics of the rule components...
- trigger: Scheduled
- action: Log, to add a reminder for the holidays table content
- action: Create Lookup Table
- Lookup table variable name: varHolidays
- Table entries
- Row
- Key: some holiday name
- Value: holiday date, formatted as yyyy-MM-dd; e.g. 2026-01-01
- ...
The order of holidays in the table does not matter, and new ones may be added later. And while there is a reminder to only include weekdays in the table, values for weekend holidays will not break the rule. The log reminder helps consider observed versus actual holiday dates, such as for Independence Day 2026 in the US on 2026-07-03.
- action: Create Variable
- Variable name: varNull
- Smart value: {{null}}
- action: Create Variable
- Variable name: varStartDate
- Smart value: {{#debug}}2026-01-05{{/}}
- action: Create Variable
- Variable name: varIncrement
- Smart value: {{#debug}}35{{/}}
- action: Create Variable
- Variable name: varBusinessDaysList
- Smart value:
{{#debug}}{{#varNull.rightPad(1000, "~,").substringBeforeLast(",").replace("~", varStartDate).split(",")}}{{#toDate("yyyy-MM-dd")}}func=plusBusinessDays({{index}}),format="jiraDate"{{/}}{{^last}},{{/}}{{/}}{{/}}
How that works is:
- Using our varNull technique to start with an empty string
- We create a boilerplate string using rightPad(). Note the count is 1000, which helps us add 500 days (where there are 2 characters of tilde and comma for each item)
- Dropping off the last comma with the substringBeforeLast() function
- We replace() the tildes with the varStartDate value
- Split() into a list of unnamed attributes, where each is the starting date
- Now comes the tricky part: we switch syntaxes to the long format of date incrementing by converting the string with toDate, add business days using its index position in the list, and format the result as a jirDate
- We add comma delimiters between the items
- And finally wrap that in a {{#debug}} … {{/}} expression so it writes to the audit log
- action: Create Variable
- Variable name: varRegEx
- Smart value:
{{#debug}}^((?!({{varHolidays.entries.value.trim.replace("-","\-").join("|")}})).*){{/}}
How that works is:
- We want a regular expression which excludes values, and regex can do that with a negative lookahead
- Using the varHolidays table, we get all the entries
- Pulling out only the value with the dates; the key value (holiday name) is not needed
- To reduce errors, we trim any spacing, and add escapes for the dash characters with replace()
- We join() the list items with pipe characters to create a string
- And finish the syntax to complete the regular expression
- action: Create Variable
- Variable name: varFinalDate
- Smart value:
{{#debug}}{{varBusinessDaysList.split(",").match(varRegEx).get(varNull.length().plus(varIncrement.asNumber))}}{{/}}
How that works to get the final date is:
- Using our full list of business days, we split() that back into a list
- With the match() function and our regular expression, we remove the holidays
- And now we can get() the correct date…
- Our varIncrement is stored in a variable, and thus we once again use our varNull technique to use that as a number; if instead, you have the increment stored in a number field, it may be used directly
- action: Log, to display the results in the audit log
start on {{varStartDate}} incremented by {{varIncrement}} business day(s) to {{varFinalDate}}
Testing the rule
To test this rule, I used a modified version with a list of starting dates, increments, and expected results stored in a created variable. Then with an Advanced Branch, I iterated the tests and to process each one. You may test it in the same manner or with single date values and repeated rule executions. Please remember to first update to your holiday list and watch the audit log for problems!
Other options, additional exercises, etc.
Remember my observation about the "sprawl" of having the holidays in different spaces / boards? This rule approach makes the situation worse as we have added the dates in a lookup table…in as many rules as we use it! What can we do instead?
A few other options to reduce replication of the holiday list and store it in one place are:
- Store the holidays in an entity property as JSON for one space (i.e., project). Then read the property with the Send Web Request action and the REST API from any project. Parse the JSON from the web response to use the holidays in the rule.
- Use an Incoming Webhook Trigger rule for all date increments, and "call" that rule from your own rules with the Send Web Request action
- Create a specific space to hold holidays, with a work item (e.g., Story) to represent each holiday date. Limit access to the space, as needed. Now use either the Lookup Work Items action or the REST API work item search endpoints to get the data, parsing it for use as the holidays.
One more thing: what if instead one wants business date difference counts, excluding holidays, rather than incrementing a date? Please consider you would know the holidays, the start date, and the final date. With those, and the techniques described in this article, one could build a list from the start to end date, remove the holidays, and get the diff count from the size of the resulting list.
Wrapping it up...
So, would anyone ever use this rule? Maybe…
I hypothesize no changes to the storage of non-working dates in a single location from Atlassian any time soon. Or, the creation of new endpoints to access them. When your team wants this type of date increment, maybe try this rule approach to observe what happens and how it helps.
Even if you do not use the described rule, I hope you learned something from the techniques shown. Please let me know your feedback, and your interest in the messy "math" version of the solution too. And as always…
Happy rule writing!
1 comment