+import groovy.transform.Field
+import static java.time.DayOfWeek.*;
+import static java.time.temporal.ChronoUnit.*;
+import static java.time.LocalTime.MIDNIGHT
+import static java.time.temporal.TemporalAdjusters.previousOrSame
+import static java.time.temporal.TemporalAdjusters.next
+@Field def WEEKEND_DAYS = [FRIDAY, SATURDAY]
+@Field int WORK_DAY_START_HOUR = 8
+@Field int WORK_DAY_END_HOUR = 17
+@Field LocalTime WORK_DAY_START = LocalTime.of(WORK_DAY_START_HOUR, 0)
+@Field LocalTime WORK_DAY_END = LocalTime.of(WORK_DAY_END_HOUR, 0)
+@Field long WORK_DAY_SECONDS = SECONDS.between(WORK_DAY_START, WORK_DAY_END)
+// Set values of startDate and endDate here...
+def start = LocalDateTime.ofInstant(startDate.toInstant(), ZoneId.systemDefault())
+def end = LocalDateTime.ofInstant(endDate.toInstant(), ZoneId.systemDefault())
+/* We normalise the time range to (almost) a whole number of weeks, going from the
+immediately preceding Sunday midnight to the immediately following Friday midnight,
+while keeping track of how much extra time we add by doing so. The total time can
+then be calculated as a simple multiple of the number of weeks, minus the additional
+time introduced by normalisation. */
+def startCorrectionSeconds
+def sundayMidnightBeforeStart = start.with(previousOrSame(SUNDAY)).with(MIDNIGHT)
+if (start.dayOfWeek in WEEKEND_DAYS) {
+ /* If it's the weekend then we're always adding five full working days' worth
+ startCorrectionSeconds = 5 * WORK_DAY_SECONDS
+ /* Otherwise we calculate the number of full working days preceding, convert
+ to seconds, and add the number of extra seconds in the current, incomplete day. */
+ def secondsSinceWorkDayStart = clampSeconds(SECONDS.between(WORK_DAY_START, start.toLocalTime()))
+ def daysSinceSunday = DAYS.between(sundayMidnightBeforeStart, start)
+ startCorrectionSeconds = daysSinceSunday * WORK_DAY_SECONDS + secondsSinceWorkDayStart
+def endCorrectionSeconds
+def fridayMidnightAfterEnd = end.with(next(FRIDAY)).with(MIDNIGHT)
+//Have to special-case Sunday midnight to avoid double counting
+if (end.dayOfWeek in WEEKEND_DAYS || end.dayOfWeek == SUNDAY && end.toLocalTime() == MIDNIGHT) {
+ /* If it's the weekend then we're always adding five full working days' worth
+ endCorrectionSeconds = 5 * WORK_DAY_SECONDS
+ /* Otherwise we calculate the number of full working days following, convert
+ to seconds, and add the number of extra seconds in the current, incomplete day. */
+ def secondsUntilWorkDayEnd = clampSeconds(SECONDS.between(end.toLocalTime(), WORK_DAY_END))
+ def daysUntilFriday = DAYS.between(end, fridayMidnightAfterEnd)
+ endCorrectionSeconds = secondsUntilWorkDayEnd + daysUntilFriday * WORK_DAY_SECONDS
+def daysBetween = DAYS.between(sundayMidnightBeforeStart, fridayMidnightAfterEnd)
+/* We're measuring from Sunday midnight to Friday midnight, so we always expect
+to get a positive number of days which is two fewer than a whole number of weeks. */
+assert daysBetween > 0 && (daysBetween + 2) % 7 == 0
+/* Add 2 to get a multiple of 7, divide by 7 to get number of weeks, multiply by 5
+to get number of work days. */
+def workDaysBetween = ((daysBetween + 2) / 7) * 5
+log.info([workDaysBetween, startCorrectionSeconds, endCorrectionSeconds])
+def secondsBetween = workDaysBetween * WORK_DAY_SECONDS - (startCorrectionSeconds + endCorrectionSeconds)
+return Math.round(secondsBetween / 60.0 as Double)
+//***************************************************
+/* If a time falls before the start, or after the end, of a work day, then the
+difference between that time and the start/end of the day might be negative, or
+larger than the length of a work day.
+In either case we only want to count the seconds that are part of the work day,
+so we use this function to restrict the value into that range. */
+long clampSeconds(long seconds) {
+ Math.max(0, Math.min(WORK_DAY_SECONDS, seconds))