Snippets

Adaptavist Working minutes between two dates

Created by Joanna Choules
import groovy.transform.Field
import java.time.*;
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)

Date startDate
Date endDate

// Set values of startDate and endDate here...
// code code code

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
    of seconds. */
    startCorrectionSeconds = 5 * WORK_DAY_SECONDS
} else {
    /* 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
    of seconds. */
    endCorrectionSeconds = 5 * WORK_DAY_SECONDS
} else {
    /* 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))
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.