Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
6dbb8d6
fix: Update code
larshelge Jan 19, 2026
5e78bcf
merge: Merge from master branch
larshelge Jan 19, 2026
eef36e9
fix: Update code
larshelge Jan 21, 2026
2aeb160
fix: Update code
larshelge Jan 21, 2026
09e16f5
fix: Update code
larshelge Jan 22, 2026
22baaf0
fix: Update code
larshelge Jan 22, 2026
65e4c6a
fix: Update code
larshelge Jan 22, 2026
6733363
fix: Update code
larshelge Jan 22, 2026
f0670b2
merge: Merge from master branch
larshelge Jan 22, 2026
bcb9517
merge: Merge from master branch
larshelge Jan 22, 2026
524576b
fix: Update code
larshelge Jan 26, 2026
76803c8
fix: Update code
larshelge Jan 27, 2026
6a2afb8
fix: Update code
larshelge Jan 27, 2026
996e4ac
fix: Update code
larshelge Jan 27, 2026
e73e9cc
merge: Merge from master branch
larshelge Jan 27, 2026
adcd707
fix: Update code
larshelge Jan 27, 2026
b895778
merge: Merge from master branch
larshelge Jan 27, 2026
baed0e5
fix: Update code
larshelge Jan 27, 2026
efe180f
fix: Update code
larshelge Jan 27, 2026
733abac
fix: Update code
larshelge Jan 27, 2026
dbbc958
merge: Merge from master branch
larshelge Jan 27, 2026
947da72
fix: Update code
larshelge Jan 27, 2026
13005f5
fix: Update code
larshelge Feb 2, 2026
6adf1d1
fix: Update code
larshelge Feb 18, 2026
a2124a4
fix: Update code
larshelge Feb 18, 2026
3860d11
merge: Merge from master branch
larshelge Feb 18, 2026
1b3b4ff
fix: Update code
larshelge Feb 23, 2026
7e988a8
merge: Merge from master branch
larshelge Feb 23, 2026
72e2b9c
fix: Update code
larshelge Feb 25, 2026
b296677
fix: Update code
larshelge Feb 25, 2026
151ed4f
chore: Bump version
larshelge Feb 25, 2026
6208073
merge: Merge from master branch
larshelge Feb 25, 2026
24a18c3
merge: Merge from master branch
larshelge Feb 25, 2026
cc0ac36
fix: Add test
larshelge Feb 26, 2026
7f2016f
fix: Update code
larshelge Feb 26, 2026
7c02d20
fix: Update code
larshelge Feb 26, 2026
d8a8d5f
merge: Merge from master branch
larshelge Feb 26, 2026
a7568f5
fix: Update code
larshelge Mar 4, 2026
8b4b947
merge: Merge from master branch
larshelge Mar 4, 2026
9fc6e7a
fix: Update code
larshelge Mar 6, 2026
a537088
fix: Update code
larshelge Mar 6, 2026
f18110f
merge: Merge from master branch
larshelge Mar 6, 2026
0249573
merge: Merge from master branch
larshelge Mar 6, 2026
c3b96d4
fix: Update code
larshelge Mar 6, 2026
136d976
merge: Merge from master branch
larshelge Mar 6, 2026
ab9a0ae
fix: Update code
larshelge Mar 7, 2026
247b74b
merge: Merge from master branch
larshelge Mar 7, 2026
6c1d0c8
merge: Merge from master branch
larshelge Mar 7, 2026
437749e
fix: Update code
larshelge Mar 8, 2026
9bd01ae
fix: Update code
larshelge Mar 8, 2026
6cb5cc4
merge: Merge from master branch
larshelge Mar 8, 2026
4cfcbcf
fix: Add util method
larshelge Mar 9, 2026
717f095
merge: Merge from master branch
larshelge Mar 9, 2026
bc14846
fix: Update code
larshelge Mar 9, 2026
cef6eb5
merge: Merge from master branch
larshelge Mar 9, 2026
1880d03
fix: Update code
larshelge Mar 10, 2026
880061e
merge: Merge from master branch
larshelge Mar 10, 2026
485c297
fix: Update code
larshelge Mar 12, 2026
7f4556d
merge: Merge from master branch
larshelge Mar 12, 2026
542a7ec
fix: Update code
larshelge Mar 13, 2026
7cc77e8
fix: Update code
larshelge Mar 13, 2026
2bea5cb
fix: Update code
larshelge Mar 13, 2026
92744b9
merge: Merge from master branch
larshelge Mar 13, 2026
5a3edef
fix: Update code
larshelge Mar 13, 2026
5712164
merge: Merge from master branch
larshelge Mar 13, 2026
b78faf2
fix: Update code
larshelge Apr 6, 2026
53b2015
fix: Update code
larshelge Apr 6, 2026
d9b2f5f
fix: Update code
larshelge Apr 6, 2026
93a4cb5
merge: Merge from master branch
larshelge Apr 6, 2026
e0bff48
merge: Merge from master branch
larshelge Apr 6, 2026
91c67dc
Merge branch 'master' into lars-dev
larshelge Apr 6, 2026
3c5c983
merge: Merge from master branch
larshelge Apr 6, 2026
0cd5cb7
fix: Update code
larshelge Apr 6, 2026
e9510fd
fix: Update code
larshelge Apr 15, 2026
8733638
fix: Update code
larshelge Apr 15, 2026
e2cd534
fix: Update code
larshelge Apr 15, 2026
0e99617
fix: Update code
larshelge Apr 15, 2026
a0266f4
merge: Merge from master branch
larshelge Apr 15, 2026
60794f6
fix: Update code
larshelge Apr 15, 2026
a1f9e43
merge: Merge from master branch
larshelge Apr 15, 2026
979a79c
fix: Update code
larshelge Apr 15, 2026
c6be59f
merge: Merge from master branch
larshelge Apr 15, 2026
e6b1270
fix: Update code
larshelge Apr 24, 2026
93d7c14
fix: Update code
larshelge Apr 30, 2026
14e8cb7
fix: Update code
larshelge Apr 30, 2026
06ca21c
fix: Update code
larshelge May 4, 2026
992beb7
merge: Merge from master branch
larshelge May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions src/main/java/org/hisp/dhis/util/StopWatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
* Copyright (c) 2004-2025, University of Oslo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the HISP project nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hisp.dhis.util;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.time.DurationFormatUtils;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StopWatch {
/** Enumeration of watch running states. */
private enum RunningState {
RUNNING,
STOPPED,
SUSPENDED,
UNSTARTED
}

/** Running state. */
private RunningState runningState = RunningState.UNSTARTED;

/** Start time. */
private Instant start;

/** Stop time. */
private Instant stop;

/** Duration of last split. */
private Duration lastSplitDuration;

// Factory

/**
* Creates and starts a stop watch.
*
* @return a {@link StopWatch}.
*/
public static StopWatch createStarted() {
final StopWatch sw = new StopWatch();
sw.start();
return sw;
}

// Operations

/**
* Starts stop watch.
*
* @return this {@link StopWatch}.
*/
public StopWatch start() {
start = Instant.now();
runningState = RunningState.RUNNING;
return this;
}

/**
* Stops stop watch.
*
* @return this {@link StopWatch}.
*/
public StopWatch stop() {
Instant now = Instant.now();
Instant previousSplit = ObjectUtils.firstNonNull(stop, start);

lastSplitDuration = Duration.between(previousSplit, now);
stop = now;
runningState = RunningState.STOPPED;

return this;
}

/**
* Splits stop watch.
*
* @return this {@link StopWatch}.
*/
public StopWatch split() {
return stop();
}

/**
* Suspends stop watch.
*
* @return this {@link StopWatch}.
*/
public StopWatch suspend() {
stop = Instant.now();
runningState = RunningState.SUSPENDED;
return this;
}

/**
* Resumes stop watch.
*
* @return this {@link StopWatch}.
*/
public StopWatch resume() {
Duration suspendDuration = Duration.between(Instant.now(), stop);
start = start.plus(suspendDuration);
runningState = RunningState.RUNNING;
return this;
}

// Running state

/**
* Indicates whether the stop watch is currently running.
*
* @return true if the stop watch is currently running, false otherwise.
*/
public boolean isRunning() {
return RunningState.RUNNING == runningState;
}

/**
* Indicates whether the stop watch is currently stopped.
*
* @return true if the stop watch is currently stopped, false otherwise.
*/
public boolean isStopped() {
return RunningState.STOPPED == runningState;
}

/**
* Indicates whether the stop watch is currently suspended.
*
* @return true if the stop watch is currently suspended, false otherwise.
*/
public boolean isSuspended() {
return RunningState.SUSPENDED == runningState;
}

// Reads

/**
* Returns the duration between start and stop in milliseconds.
*
* @return the duration between start and stop in milliseconds.
*/
public long getTime() {
return ChronoUnit.MILLIS.between(start, stop);
}

/**
* Returns the formatted time as a string.
*
* @return the formatted time as a string.
*/
public String getTimeFormatted() {
return DurationFormatUtils.formatDurationHMS(getTime());
}

/**
* Returns the duration of the last split in milliseconds.
*
* @return the duration of the last split in milliseconds, or -1 if watch is not split.
*/
public long getLastSplitDuration() {
return lastSplitDuration != null ? lastSplitDuration.toMillis() : -1;
}

/**
* Returns the formatted last split time as a string.
*
* @return the formatted last split time.
*/
public String getLastSplitDurationFormatted() {
return DurationFormatUtils.formatDurationHMS(getLastSplitDuration());
}

/**
* Splits the time and returns the formatted last split time.
*
* @return the formatted last split time.
*/
public String splitAndGetLastFormatted() {
return split().getLastSplitDurationFormatted();
}

/**
* Returns a string representation of the time.
*
* @return a string representation of the time.
*/
@Override
public String toString() {
return getTimeFormatted();
}
}
153 changes: 153 additions & 0 deletions src/test/java/org/hisp/dhis/util/StopWatchTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2004-2026, University of Oslo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the HISP project nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hisp.dhis.util;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Duration;
import org.apache.commons.lang3.ThreadUtils;
import org.hisp.dhis.support.TestTags;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag(TestTags.UNIT)
class StopWatchTest {
@Test
void testStopGetTime() {
StopWatch watch = StopWatch.createStarted();
sleep();
watch.stop();
assertPositive(watch.getTime());
}

@Test
void testSplitGetTime() {
StopWatch watch = StopWatch.createStarted();
sleep();
watch.split();
assertPositive(watch.getTime());
}

@Test
void testSplitStopGetTime() {
StopWatch watch = StopWatch.createStarted();
sleep();
watch.split();
assertPositive(watch.getTime());
sleep();
watch.stop();
assertPositive(watch.getTime());
}

@Test
void testSplitGetLastSplitDuration() {
StopWatch watch = StopWatch.createStarted();
sleep();
assertEquals(-1, watch.getLastSplitDuration());
watch.split();
assertPositive(watch.getLastSplitDuration());
}

@Test
void testStopGetLastSplitDuration() {
StopWatch watch = StopWatch.createStarted();
sleep();
assertEquals(-1, watch.getLastSplitDuration());
watch.stop();
assertPositive(watch.getLastSplitDuration());
}

@Test
void testSuspendResume() {
StopWatch watch = StopWatch.createStarted();
sleep();
watch.suspend();
assertTrue(watch.getTime() > 0);
long time = watch.getTime();
sleep();
assertEquals(time, watch.getTime());
watch.resume();
sleep();
assertTrue(watch.getTime() > time);
watch.stop();
}

@Test
void testGetRunningState() {
StopWatch watch = StopWatch.createStarted();
assertTrue(watch.isRunning());
assertFalse(watch.isSuspended());
assertFalse(watch.isStopped());

watch.stop();
assertFalse(watch.isRunning());
assertFalse(watch.isSuspended());
assertTrue(watch.isStopped());

watch.start();
assertTrue(watch.isRunning());
assertFalse(watch.isSuspended());
assertFalse(watch.isStopped());

watch.suspend();
assertFalse(watch.isRunning());
assertTrue(watch.isSuspended());
assertFalse(watch.isStopped());

watch.resume();
assertTrue(watch.isRunning());
assertFalse(watch.isSuspended());
assertFalse(watch.isStopped());

watch.stop();
assertFalse(watch.isRunning());
assertFalse(watch.isSuspended());
assertTrue(watch.isStopped());
}

@Test
void testToString() {
StopWatch watch = StopWatch.createStarted();
sleep();
watch.stop();
assertNotNull(watch.toString());
}

/** Sleeps for a short duration. */
private void sleep() {
ThreadUtils.sleepQuietly(Duration.ofMillis(20));
}

/** Asserts that the given long value is positive. */
void assertPositive(long value) {
assertTrue(value > 0, "Long value is not positive: " + value);
}
}
Loading