Hi,
I have start Date and End date so how I can check holidays fall in between these two dates?
How many holidays fall ?
I have script runner plugin. How can I write script to find this?
Any help much appreciated.
You can use scriptrunner to generate a script field to hold the value or use as a script check in
Script Console, however you gonna need mantain an array with the holidays of the year, i know it's possible connect with some services to get the holidays of the year, but some of them are paid service, so you can also explore the connection with public API's to get the information and keep them in the array.
Here is the manual version of the script i mencioned:
import java.time.LocalDate
import java.time.format.DateTimeFormatter
// Input dates (you can replace these with your issue's field values or parameters)
def startDate = LocalDate.parse("2025-01-01", DateTimeFormatter.ISO_DATE)
def endDate = LocalDate.parse("2025-01-10", DateTimeFormatter.ISO_DATE)
// Define the list of holidays (in YYYY-MM-DD format)
def holidays = [
LocalDate.parse("2025-01-06"),
LocalDate.parse("2025-01-09")
]
// Filter holidays that fall within the range
def holidaysInRange = holidays.findAll { it.isAfter(startDate.minusDays(1)) && it.isBefore(endDate.plusDays(1)) }
// Output the result
def holidayCount = holidaysInRange.size()
log.info("Number of holidays between ${startDate} and ${endDate}: ${holidayCount}")
// Return the final value
return holidayCount
Let me know it this helps you.
Best Regards
Hi @Kawan Diogo
I tried getting the date field values from jira issue as its get as string so converting to Date format. Look like my script isn't working. Could you please see what is missing.
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.MutableIssue
import org.apache.log4j.Category
import com.atlassian.jira.issue.IssueManager;
import java.sql.Timestamp
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.util.IssueChangeHolder
import com.atlassian.jira.issue.*
import java.util.Date
import java.text.SimpleDateFormat
import java.text.DateFormat
import java.text.ParseException
import java.sql.Timestamp
import com.atlassian.mail.Email
import com.atlassian.mail.server.MailServerManager;
import com.atlassian.mail.server.SMTPMailServer;
import com.atlassian.mail.queue.MailQueue
import com.atlassian.mail.queue.SingleMailQueueItem
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.text.DateFormat
////////////////////////////////////////////////////////////////////////////////////////
//Used for testing on specific issue
def IssueKey = "GT-5429"
def issue = ComponentAccessor.getIssueManager().getIssueObject(IssueKey)
////////////////////////////////////////////////////////////////////////////////////////
Calendar cal = Calendar.getInstance();
// Logger
//def Category log = Category.getInstance("com.onresolve.jira.groovy.PostFunction");
def user = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()
def IssueManager issueManager = ComponentAccessor.issueManager
IssueChangeHolder changeHolder = new DefaultIssueChangeHolder();
//MutableIssue issue = event.issue as MutableIssue
// Set the logging level to DEBUG
log.setLevel(org.apache.log4j.Level.DEBUG);
def cf_pirResultDate = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("PIR Results/LL Due Date").first()
def cf_impdt = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Implementation Date").first()
def cf_datedt = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectsByName("Date").first()
// Define the list of holidays (in YYYY-MM-DD format)
def holidays = [
LocalDate.parse("2025-01-06"),
LocalDate.parse("2025-01-09")
]
String cf_impdtValue = issue.getCustomFieldValue(cf_impdt).toString()
String cf_datedtValue = issue.getCustomFieldValue(cf_datedt).toString()
def datePattern = "yyyy-MM-dd";
DateFormat formatter = new SimpleDateFormat(datePattern);
Date Startdate = formatter.parse(cf_impdtValue);
Date EndDate = formatter.parse(cf_datedtValue);
def holidaysInRange = holidays.findAll { it.isAfter(Startdate.minusDays(1)) && it.isBefore(EndDate.plusDays(1)) }
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You need to convert the Date to LocalDate to make the correct comparison with the holidays. Additionally, I recommend adding some logs to ensure every field is set with a value.
Here’s a new version with these adjustments. Please try it and let me know if it works.
import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Category
import java.text.SimpleDateFormat
import java.util.Date
import java.time.LocalDate
// Logger
def log = Category.getInstance("com.onresolve.jira.groovy.PostFunction");
log.setLevel(org.apache.log4j.Level.DEBUG);
// Issue Key for testing
def IssueKey = "GT-5429"
def issue = ComponentAccessor.getIssueManager().getIssueObject(IssueKey)
if (!issue) {
log.error("Issue not found.");
return;
}
// Custom fields
def cf_impdt = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Implementation Date")?.first()
def cf_datedt = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Date")?.first()
if (!cf_impdt || !cf_datedt) {
log.error("Required custom fields not found.");
return;
}
// Custom field values
String cf_impdtValue = issue.getCustomFieldValue(cf_impdt)?.toString()
String cf_datedtValue = issue.getCustomFieldValue(cf_datedt)?.toString()
if (!cf_impdtValue || !cf_datedtValue) {
log.error("Custom field values are null or empty.");
return;
}
// Parse dates
def datePattern = "yyyy-MM-dd";
Date Startdate, EndDate;
try {
def formatter = new SimpleDateFormat(datePattern);
Startdate = formatter.parse(cf_impdtValue);
EndDate = formatter.parse(cf_datedtValue);
} catch (Exception e) {
log.error("Error parsing dates: ${e.message}");
return;
}
// Convert Date to LocalDate
def startDate = Startdate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
def endDate = EndDate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
// Define holidays
def holidays = [
LocalDate.parse("2025-01-06"),
LocalDate.parse("2025-01-09")
]
// Find holidays in range
def holidaysInRange = holidays.findAll { it.isAfter(startDate.minusDays(1)) && it.isBefore(endDate.plusDays(1)) }
log.debug("Holidays in range: ${holidaysInRange}");
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Kawan Diogo Yes, This worked.
I tried converting string to LocalDate but couldn't not success but your code works fine.
Looks tome I am good with this now. Thank you so much.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Kawan Diogo
One last question. How I can convert localDate to Date ? I would like to set the date to custom field cf_datedt field in above script.
// Find holidays in range
def holidaysInRange = holidays.findAll { it.isAfter(startDate.minusDays(1)) && it.isBefore(endDate.plusDays(1)) }
Double holidayscount = holidaysInRange.size()
endDate = addBusinessDays(endDate, holidayscount)
// Function to add business days to a date
LocalDate addBusinessDays(LocalDate startDate, Double daysToAdd) {
int daysAdded = 0
LocalDate currentDate = startDate
currentDate = currentDate.plusDays(3)
if(currentDate.dayOfWeek == java.time.DayOfWeek.SUNDAY){
currentDate = currentDate.plusDays(1)
return currentDate
}else if(currentDate.dayOfWeek == java.time.DayOfWeek.SATURDAY){
currentDate = currentDate.plusDays(2)
return currentDate
}
return currentDate
}
Date addDt = endDate
// Set the updated target date back to the field
issue.setCustomFieldValue(cf_datedt, addDt)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You need make the inverse way now, like how we made with End Date to transform in LocalDate.
If I not wrong in your Date addDt you need to make the proper conversion.
something like this:
Date addDt = Date.from(endDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
Basically we convert the LocalDate to LocalDateTime and transform in Instant where the date.from() convert to the proper format you need.
Another way to make the conversion is with:
Date addDt = new Date(endDate.toEpochDay() * 24 * 60 * 60 * 1000)
But the first way is better in my opinion.
Have a try and let me know if works for you.
Best Regards
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Kawan Diogo
I think both converting to different format, which is not allowing to set the date after conversion to date
Example of output. Implementation date is in format of 2025-05-7 00:00:00.0
but the end date is in different format.
implementation date2025-05-07 00:00:00.0
PIRLLDueDateSun Aug 03 20:00:00 EDT 2025
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
import java.lang.Integer
import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Category
import java.text.SimpleDateFormat
import java.util.Date
import java.time.LocalDate
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.event.type.EventDispatchOption
import java.time.temporal.ChronoUnit
import java.time.ZoneId
import java.time.Instant
import java.text.SimpleDateFormat
import java.text.DateFormat
import java.sql.Timestamp
// Logger
def log = Category.getInstance("com.onresolve.jira.groovy.PostFunction");
log.setLevel(org.apache.log4j.Level.DEBUG);
// Issue Key for testing
//*****************************************************************************
def IssueKey = "GT-5429"
def issue = ComponentAccessor.getIssueManager().getIssueObject(IssueKey)
//*****************************************************************************
def IssueManager issueManager = ComponentAccessor.issueManager
def jirauser = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()
//MutableIssue issue = event.issue as MutableIssue
if (!issue) {
log.error("Issue not found.");
return;
}
// Custom fields
def cf_impdt = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Implementation Date")?.first()
def cf_datedt = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Date")?.first()
def cf_PIRLLDDate = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("PIR Results/LL Due Date")?.first()
def cf_numberofHolidays = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("NumOfHolidays")?.first()
def cf_HolidaysCount = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("HolidaysCount")?.first()
//def change = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Date"}
//if (change) {
if (!cf_impdt || !cf_datedt) {
log.error("Required custom fields not found.");
return;
}
// Custom field values
String cf_impdtValue = issue.getCustomFieldValue(cf_impdt)?.toString()
String cf_datedtValue = issue.getCustomFieldValue(cf_datedt)?.toString()
log.warn "implementation date" + cf_impdtValue
if (!cf_impdtValue || !cf_datedtValue) {
log.error("Custom field values are null or empty.");
return;
}
// Parse dates
def datePattern = "yyyy-MM-dd";
Date Startdate, EndDate;
try {
def formatter = new SimpleDateFormat(datePattern);
Startdate = formatter.parse(cf_impdtValue);
EndDate = formatter.parse(cf_datedtValue);
} catch (Exception e) {
log.error("Error parsing dates: ${e.message}");
return;
}
// Convert Date to LocalDate
def startDate = Startdate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
def endDate = EndDate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
// Define holidays
def holidays = [
LocalDate.parse("2025-01-01"),
LocalDate.parse("2025-01-20"),
LocalDate.parse("2025-04-18"),
LocalDate.parse("2025-05-26"),
LocalDate.parse("2025-07-04"),
LocalDate.parse("2025-09-01"),
LocalDate.parse("2025-10-13"),
LocalDate.parse("2025-11-11"),
LocalDate.parse("2025-11-27"),
LocalDate.parse("2025-11-28"),
LocalDate.parse("2025-12-25"),
LocalDate.parse("2025-12-26")
]
// Find holidays in range
def holidaysInRange = holidays.findAll { it.isAfter(startDate.minusDays(1)) && it.isBefore(endDate.plusDays(1)) }
Double holidayscount = holidaysInRange.size()
endDate = addBusinessDays(endDate, holidayscount)
// Function to add business days to a date
LocalDate addBusinessDays(LocalDate startDate, Double daysToAdd) {
int daysAdded = 0
LocalDate currentDate = startDate
currentDate = currentDate.plusDays(3)
if(currentDate.dayOfWeek == java.time.DayOfWeek.SUNDAY){
currentDate = currentDate.plusDays(1)
return currentDate
}else if(currentDate.dayOfWeek == java.time.DayOfWeek.SATURDAY){
currentDate = currentDate.plusDays(2)
return currentDate
}
return currentDate
}
Date PIRLLDueDate = Date.from(endDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
log.warn "PIRLLDueDate" + PIRLLDueDate
//log.warn "DefextDD" + DefextDD
// Set the updated target date back to the field
issue.setCustomFieldValue(cf_PIRLLDDate, PIRLLDueDate)
// Save the changes
//ComponentAccessor.getIssueManager().updateIssue(event.getUser(), issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_UPDATED, false)
issue.setCustomFieldValue(cf_HolidaysCount,holidayscount)
issueManager.updateIssue(jirauser, issue, EventDispatchOption.DO_NOT_DISPATCH ,false)
//}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Please, try make a formatting conversion after the conversion to Date in your field of reference, like this:
Date PIRLLDueDate = Date.from(endDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
// Defining the formatter if you need another format please change the SimpleDateFormat()
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
String PIRLLDueDateFormatted = sdf.format(PIRLLDueDate)
// check if the value are in the expected format
log.info(PIRLLDueDateFormatted)
issue.setCustomFieldValue(PIRLLDDate,PIRLLDueDateFormatted)
Tell me if know this works for you now.
Best Regards
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Kawan Diogo
Its converted but giving me below error when running in script console.
025-01-30T11:41:22,471 WARN [groovy.PostFunction]: implementation date2025-05-07 00:00:00.0 2025-01-30T11:41:22,473 INFO [groovy.PostFunction]: 2025-08-04 00:00:00 2025-01-30T11:41:22,477 ERROR [common.UserScriptEndpoint]: ************************************************************************************* 2025-01-30T11:41:22,477 ERROR [common.UserScriptEndpoint]: Script console script failed: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Date (java.lang.String and java.util.Date are in module java.base of loader 'bootstrap') at com.atlassian.jira.issue.customfields.impl.DateCFType.getDbValueFromObject(DateCFType.java:59) ~[classes/:?]
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Ok let’s try another method.
you can maintain the formatted to see in the log, but you don’t need the exactly format to set the field, we gonna try convert the value to timestamp.
import java.sql.Timestamp
// Convert LocalDate to Timestamp
Timestamp PIRLLDueDate = Timestamp.from(endDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
log.warn "PIRLLDueDate: " + PIRLLDueDate
issue.setCustomFieldValue(cf_PIRLLDDate, PIRLLDueDate)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Kawan Diogo Yes, It worked with timeStamp. I messed up with date formats. Thank you.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I’m glad it worked! If you need anything else, feel free to reach out 🙃.
Best regards.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Kawan Diogo Actually, It worked in script console but when I put in script listener then getting error at line 101
line at 101 "
2025-01-30T12:01:42,507 ERROR [events.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: null groovy.lang.MissingMethodException: No signature of method: org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.LocalDate() is applicable for argument types: (java.time.LocalDate, Class) values: [2025-05-07, class java.lang.Double] at Script182.run(Script182.groovy:101) ~[?:?]
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
It’s normal sometimes adapt the script when change the action (listener/job/behavior)
Probably more things can need adjustments, but let’s start concert the Double to Integer and see how the listener gonna interpret.
try this:
endDate = addBusinessDays(endDate, holidayscount.intValue())
If you have others Double, please make a test before change everything and after if there’s a error, convert to make an progression adjustment.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I made the same doube to INT and then working now. Thank you so much.
My main goal was to set 60 businessdays from Implementation date excluding holidays, which I made it with your help.
It may help to others. Here is the complete script.
import java.lang.Integer
import com.atlassian.jira.component.ComponentAccessor
import org.apache.log4j.Category
import java.text.SimpleDateFormat
import java.util.Date
import java.time.LocalDate
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.event.type.EventDispatchOption
import java.time.temporal.ChronoUnit
import java.time.ZoneId
import java.time.Instant
import java.text.SimpleDateFormat
import java.text.DateFormat
import java.sql.Timestamp
import com.atlassian.jira.issue.MutableIssue
// Logger
def log = Category.getInstance("com.onresolve.jira.groovy.PostFunction");
log.setLevel(org.apache.log4j.Level.DEBUG);
// Issue Key for testing
//*****************************************************************************
//def IssueKey = "GT-5429"
//def issue = ComponentAccessor.getIssueManager().getIssueObject(IssueKey)
//*****************************************************************************
def IssueManager issueManager = ComponentAccessor.issueManager
def jirauser = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()
MutableIssue issue = event.issue as MutableIssue
if (!issue) {
log.error("Issue not found.");
return;
}
// Custom fields
def cf_impdt = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Implementation Date")?.first()
def cf_datedt = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Date")?.first()
def cf_PIRLLDDate = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("PIR Results/LL Due Date")?.first()
def cf_numberofHolidays = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("NumOfHolidays")?.first()
def cf_HolidaysCount = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("HolidaysCount")?.first()
def change = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Date"}
if (change) {
if (!cf_impdt || !cf_datedt) {
log.error("Required custom fields not found.");
return;
}
// Custom field values
String cf_impdtValue = issue.getCustomFieldValue(cf_impdt)?.toString()
String cf_datedtValue = issue.getCustomFieldValue(cf_datedt)?.toString()
log.warn "implementation date" + cf_impdtValue
if (!cf_impdtValue || !cf_datedtValue) {
log.error("Custom field values are null or empty.");
return;
}
// Parse dates
def datePattern = "yyyy-MM-dd";
Date Startdate, EndDate;
try {
def formatter = new SimpleDateFormat(datePattern);
Startdate = formatter.parse(cf_impdtValue);
EndDate = formatter.parse(cf_datedtValue);
} catch (Exception e) {
log.error("Error parsing dates: ${e.message}");
return;
}
// Convert Date to LocalDate
def startDate = Startdate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
def endDate = EndDate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
// Define holidays
def holidays = [
LocalDate.parse("2025-01-01"),
LocalDate.parse("2025-01-20"),
LocalDate.parse("2025-04-18"),
LocalDate.parse("2025-05-26"),
LocalDate.parse("2025-07-04"),
LocalDate.parse("2025-09-01"),
LocalDate.parse("2025-10-13"),
LocalDate.parse("2025-11-11"),
LocalDate.parse("2025-11-27"),
LocalDate.parse("2025-11-28"),
LocalDate.parse("2025-12-25"),
LocalDate.parse("2025-12-26")
]
// Find holidays in range
def holidaysInRange = holidays.findAll { it.isAfter(startDate.minusDays(1)) && it.isBefore(endDate.plusDays(1)) }
int holidayscount = holidaysInRange.size()
Double hdcount = holidaysInRange.size()
//endDate = addBusinessDays(endDate, holidayscount)
int daysAdded =0
LocalDate currentDate = endDate
currentDate = currentDate.plusDays(holidayscount)
if(currentDate.dayOfWeek == java.time.DayOfWeek.SUNDAY){
currentDate = currentDate.plusDays(1)
return currentDate
}else if(currentDate.dayOfWeek == java.time.DayOfWeek.SATURDAY){
currentDate = currentDate.plusDays(2)
}
log.warn "CurrentDate" + currentDate
/*
// Function to add business days to a date
LocalDate addBusinessDays(LocalDate startDate, Double daysToAdd) {
int daysAdded = 0
LocalDate currentDate = startDate
currentDate = currentDate.plusDays(3)
if(currentDate.dayOfWeek == java.time.DayOfWeek.SUNDAY){
currentDate = currentDate.plusDays(1)
return currentDate
}else if(currentDate.dayOfWeek == java.time.DayOfWeek.SATURDAY){
currentDate = currentDate.plusDays(2)
return currentDate
}
return currentDate
}
*/
// Convert LocalDate to Timestamp
Timestamp PIRLLDueDate = Timestamp.from(currentDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
log.warn "PIRLLDueDate: " + PIRLLDueDate
issue.setCustomFieldValue(cf_PIRLLDDate, PIRLLDueDate)
// Save the changes
//ComponentAccessor.getIssueManager().updateIssue(event.getUser(), issue, com.atlassian.jira.event.type.EventDispatchOption.ISSUE_UPDATED, false)
issue.setCustomFieldValue(cf_HolidaysCount,hdcount)
issueManager.updateIssue(jirauser, issue, EventDispatchOption.DO_NOT_DISPATCH ,false)
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I've given a similar script using ScriptRunner's Behaviour in this Community Question.
Thank you and Kind regards,
Ram
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.