Snippets

Adaptavist Working minutes between two dates

Created by Joanna Choules

File snippet.groovy Added

  • Ignore whitespace
  • Hide word diff
+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))
+}
HTTPS SSH

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