Scripts Groove showing how long the task is in a specific status

smorozov January 18, 2022

Hello! I am new to Groove.
Please help me write the script Groove showing how long the task is in a specific status.
Only to display not the actual time - 24/7 A how working hours 5/8.

The code below will display the time 24/7. What should be the code to display the time in 5/8 format?
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.history.ChangeItemBean

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

def inProgressName = "X"

List<Long> rt = [0L]
def changeItems = changeHistoryManager.getChangeItemsForField(issue, "status")
changeItems.reverse().each { ChangeItemBean item ->
def timeDiff = System.currentTimeMillis() - item.created.getTime()
if (item.fromString == inProgressName) {
rt << -timeDiff
}
if (item.toString == inProgressName) {
rt << timeDiff
}
}

def total = rt.sum() as Long
return (total / 1000 ) as long ?: 0L  

 

PS. I am trying to configure the display of this functionality through (JMCF) Calculated (scripted) Duration Field

2 answers

0 votes
David Fischer
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 18, 2022

Hi @smorozov ,

you can use the following code, where you'll need to update the Status name (statusName), work hours (WORK_HOUR_START, WORK_HOUR_END), and time zone in which the work hours are defined (timeZone):

import com.atlassian.jira.issue.history.ChangeItemBean
import org.joda.time.DateTime
import org.joda.time.DateTimeZone

import java.sql.Timestamp
import java.time.*
import java.time.temporal.ChronoUnit
import java.util.stream.IntStream

final String statusName = "To Do" //adjust for the status you want
final List<ChangeItemBean> changeItems = issue.getFieldHistory("status")
if (changeItems.size() == 0) //issue status has never changed
if (issue.get("status").name == statusName)
return workingSecondsBetween(new Date().getTime(), issue.getCreated().getTime())
else
return null;
long totalTime = 0L;
Long startTime = null;
//iterate over changes
for (ChangeItemBean changeItemBean : changeItems) {
if (changeItemBean.getFromString() == statusName) {
if (startTime == null) //this must have been the starting status
startTime = issue.getCreated().getTime();
totalTime += workingSecondsBetween(changeItemBean.getCreated().getTime(), startTime);
startTime = null;
}
if (changeItemBean.getToString() == statusName) {
startTime = changeItemBean.getCreated().getTime();
}
}
if (startTime != null)
totalTime += workingSecondsBetween(new Date().getTime(), startTime); //we are currently in the requested status
return totalTime


public class WorkingMinutesCalculator {
private static final int WORK_HOUR_START = 8; //first working hour
private static final int WORK_HOUR_END = 16; //last working hour (work ends at the end of this hour)
private static final long SECONDS_PER_HOUR = 3600;

private static final long WORKING_HOURS_PER_DAY = WORK_HOUR_END - WORK_HOUR_START;
private static final long WORKING_SECONDS_PER_DAY = WORKING_HOURS_PER_DAY * SECONDS_PER_HOUR;

public int getWorkingSecondsSince(final Timestamp startTime) {
Timestamp now = Timestamp.from(Instant.now());
return getWorkingSeconds(startTime, now);
}

public int getWorkingSeconds(final Timestamp startTime, final Timestamp endTime) {
if (null == startTime || null == endTime) {
throw new IllegalStateException();
}
if (endTime.before(startTime)) {
return 0;
}

LocalDateTime from = startTime.toLocalDateTime();
LocalDateTime to = endTime.toLocalDateTime();

LocalDate fromDay = from.toLocalDate();
LocalDate toDay = to.toLocalDate();

int allDaysBetween = (int) (ChronoUnit.DAYS.between(fromDay, toDay) + 1);
long allWorkingSeconds = IntStream.range(0, allDaysBetween)
.filter{i -> isWorkingDay(from.plusDays(i))}
.count() * WORKING_SECONDS_PER_DAY ;

// from - working_day_from_start
long tailRedundantSeconds = 0;
if (isWorkingDay(from)) {
if (isWorkingHours(from)) {
tailRedundantSeconds = Duration.between(fromDay.atTime(WORK_HOUR_START, 0), from).toSeconds();
} else if (from.getHour() > WORK_HOUR_START) {
tailRedundantSeconds = WORKING_SECONDS_PER_DAY;
}
}

// working_day_end - to
long headRedundanSeconds = 0;
if (isWorkingDay(to)) {
if (isWorkingHours(to)) {
headRedundanSeconds = Duration.between(to, toDay.atTime(WORK_HOUR_END, 0)).toSeconds();
} else if (from.getHour() < WORK_HOUR_START) {
headRedundanSeconds = WORKING_SECONDS_PER_DAY;
}
}
return (int) (allWorkingSeconds - tailRedundantSeconds - headRedundanSeconds);
}

private boolean isWorkingDay(final LocalDateTime time) {
return time.getDayOfWeek().getValue() < DayOfWeek.SATURDAY.getValue();
}

private boolean isWorkingHours(final LocalDateTime time) {
int hour = time.getHour();
return WORK_HOUR_START <= hour && hour <= WORK_HOUR_END;
}
}

long workingSecondsBetween(long to, long from) {
//to and from are Unix time in the system time zone (not UTC)
final DateTimeZone timeZone = DateTimeZone.forID("America/Chicago") //adjust for the time zone you want
DateTime fromDt = new DateTime(from + (new Date(from)).getTimezoneOffset()*60000, timeZone)
DateTime toDt = new DateTime(to + (new Date(to)).getTimezoneOffset()*60000, timeZone)
return (new WorkingMinutesCalculator()).getWorkingSeconds(new Timestamp(fromDt.toLocalDateTime().getLocalMillis()), new Timestamp(toDt.toLocalDateTime().getLocalMillis()))
}
0 votes
Vishwas
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 18, 2022

Hey @smorozov 

You can check out this link, JMCF field rite ? there is a code in the answer for your case. You may have to check and modify if requried.

https://community.atlassian.com/t5/Marketplace-Apps-Integrations/Ageing-Calculated-Field-excluding-weekends-using-JMCF/qaq-p/594228

 

Regards,

Vishwas

Suggest an answer

Log in or Sign up to answer