Skip to content

Instantly share code, notes, and snippets.

@SocraticPhoenix
Last active April 23, 2016 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SocraticPhoenix/f3d74641db94c3fc531ab2a97ae7010e to your computer and use it in GitHub Desktop.
Save SocraticPhoenix/f3d74641db94c3fc531ab2a97ae7010e to your computer and use it in GitHub Desktop.
package your.namespace.here;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* The Stopwatch class represents a mapping of versatile timing entries, each of which can be independently stopped, started, or examined.
*
* @param <T> The type of key to use for the timing entries
*/
public class Stopwatch<T> {
private Map<T, Entry> counts;
private boolean initiallyStarted;
/**
* Creates a new Stopwatch that's timing entries are automatically started whenever they are created.
*/
public Stopwatch() {
this(true);
}
/**
* Creates a new Stopwatch that's timing entries are either started or stopped whenever they are created.
*
* @param initiallyStarted Whether or not to start a timing entry when it is created.
*/
public Stopwatch(boolean initiallyStarted) {
this.counts = new ConcurrentHashMap<>();
this.initiallyStarted = initiallyStarted;
}
/**
* Converts milliseconds to a human readable string
*
* @param millis The milliseconds to convert
* @param roundMillis Whether or not to round milliseconds to seconds
* @return A human readable string
*/
public static String toTimeString(long millis, boolean roundMillis) {
if (millis == 0|| (millis < 500 && millis >= 0 && roundMillis)) { //Handle the 0 case
return roundMillis ? "0 Seconds" : "0 Milliseconds";
}
long seconds = millis / 1000; //TOTAL number of seconds
long minutes = seconds / 60; //TOTAL number of minutes
long hours = minutes / 60; //TOTAL number of hours
millis = millis - (seconds * 1000); //Remove the milliseconds taken care of by the number of seconds
seconds = seconds - (minutes * 60); //Remove the seconds taken care of by the number of minutes
minutes = minutes - (60 * hours); //Remove the minutes taken care of by the number of hours
//If we're rounding milliseconds, add stuff to seconds.
if (roundMillis) {
if (millis >= 500) {
seconds++;
} else if (millis <= -500) {
seconds--;
}
millis = 0;
}
StringBuilder builder = new StringBuilder();
if (hours != 0) { //Are their hours present?
builder.append(hours).append(hours == 1 ? " Hour" : " Hours"); //Append hours and "Hours"
builder.append(containsNonZero(1, minutes, seconds, millis) ? " and" : ""); //If there are remaining things to append, append "and"
}
if (minutes != 0) { //Are their minutes present?
builder.append(hours != 0 && !containsNonZero(1, minutes, seconds, millis) ? ", " : (hours != 0 ? " " : "")).append(minutes).append(minutes == 1 ? " Minute" : " Minutes"); //Append minutes and "Minutes"
builder.append(containsNonZero(1, seconds, millis) ? " and" : ""); //If there are remaining things to append, append "and"
}
if (seconds != 0) { //Are their seconds present?
builder.append((minutes != 0 || hours != 0) && !containsNonZero(1, seconds, millis) ? ", " : (minutes != 0 || hours != 0 ? " " : "")).append(seconds).append(seconds == 1 ? " Second" : " Seconds"); //Append seconds and "Seconds"
builder.append(containsNonZero(1, millis) ? " and" : ""); //If there are remaining things to append, append "and"
}
if (millis != 0) { //Are their milliseconds present?
builder.append(minutes != 0 || hours != 0 || seconds != 0 ? " " : "").append(millis).append(millis == 1 ? " Millisecond" : " Milliseconds"); //Append milliseconds and "Milliseconds"
}
return builder.toString();
}
/**
* Returns true if <i>toCheck</i> contains exactly <i>amount</i> non-zero numbers.
*
* @param amount The desired amount of non-zero numbers.
* @param toCheck The numbers to check
* @return True if the desired amount of non-zeroes were found, false otherwise.
*/
private static boolean containsNonZero(int amount, long... toCheck) {
int count = 0;
for (long l : toCheck) {
if (l != 0) {
count++;
}
if (count > amount) {
break;
}
}
return count == amount;
}
/**
* Creates a new timing entry under the specified id.
*
* @param id The id of the timing entry.
*/
public void create(T id) {
this.counts.put(id, new Entry(!this.initiallyStarted));
}
/**
* Removes a timing entry under the specified id, if it exists.
*
* @param id The id of the timing entry.
*/
public void remove(T id) {
this.counts.remove(id);
}
/**
* Determines whether or not there is a timing entry registered under the specified id.
*
* @param id The id of the timing entry.
* @return True if the timing entry exists, false otherwise.
*/
public boolean contains(T id) {
return this.counts.containsKey(id);
}
/**
* Gets an existing timing entry, or creates one if it does not exist.
*
* @param id The id of the timing entry.
* @return The new or pre-existing timing entry.
*/
public Stopwatch.Entry getOrCreate(T id) {
if (!this.counts.containsKey(id)) {
this.create(id);
}
return this.counts.get(id);
}
/**
* Fetches the elapsed time of the timing entry under the specified id.
*
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
* @return The elapsed time of the timing entry.
*/
public long time(T id) {
return this.getOrCreate(id).time();
}
/**
* Stops the timing entry under the specified id.
*
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
*/
public void stop(T id) {
this.getOrCreate(id).stop();
}
/**
* Starts the timing entry under the specified id.
*
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
*/
public void start(T id) {
this.getOrCreate(id).start();
}
/**
* Fetches the time until the timing entry the specified id will reach the specified time elapsed.
* <p>If the specified time entry has more elapsed time than specified, negative or zero time will be returned</p>
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
* @param elapsed The time elapsed to consider.
* @return The time until the entry will reach the specified elapsed time.
*/
public long until(T id, long elapsed) {
return this.getOrCreate(id).until(elapsed);
}
/**
* Fetches the elapsed time of the timing entry under the specified id and returns a human readable representation of it.
*
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
* @return The human readable elapsed time of the timing entry.
*/
public String humanReadableTime(T id) {
return this.getOrCreate(id).humanReadableTime();
}
/**
* Fetches the time until the timing entry the specified id will reach the specified time elapsed and returns a human readable representation if it.
* <p>If the specified time entry has more elapsed time than specified, negative or zero time will be returned</p>
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
* @param elapsed The time elapsed to consider.
* @return The human readable time until the entry will reach the specified elapsed time.
*/
public String humanReadableUntil(T id, long elapsed) {
return this.getOrCreate(id).humanReadableUntil(elapsed);
}
/**
* Fetches the elapsed time of the timing entry under the specified id and returns a human readable representation of it.
*
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
* @param roundMillis Whether or not to round milliseconds when creating the human readable string.
* @return The human readable elapsed time of the timing entry.
*/
public String humanReadableTime(T id, boolean roundMillis) {
return this.getOrCreate(id).humanReadableTime(roundMillis);
}
/**
* Fetches the time until the timing entry the specified id will reach the specified time elapsed and returns a human readable representation if it.
* <p>If the specified time entry has more elapsed time than specified, negative or zero time will be returned</p>
* <p>The entry is created if it does not exist.</p>
* @param id The id of the timing entry.
* @param elapsed The time elapsed to consider.
* @param roundMillis Whether or not to round milliseconds when creating the human readable string.
* @return The human readable time until the entry will reach the specified elapsed time.
*/
public String humanReadableUntil(T id, long elapsed, boolean roundMillis) {
return this.getOrCreate(id).humanReadableUntil(elapsed, roundMillis);
}
/**
* The Stopwatch.Entry class represents a single Stopwatch timing entry. This class takes care of the actual counting and mathematical calculations.
*/
public static class Entry {
private long startTime;
private long stoppedTimeElapsed;
private long stopTime;
private boolean stopped;
/**
* Creates a new Stopwatch.Entry, with the specified initial stopped value.
*
* @param stopped Whether or not the Stopwatch.Entry starts when it is created.
*/
public Entry(boolean stopped) {
this.startTime = current();
this.stoppedTimeElapsed = 0;
this.stopTime = 0;
this.stopped = stopped;
if (stopped) {
this.stopTime = current();
}
}
/**
* Creates a new Stopwatch.Entry that starts immediately.
*/
public Entry() {
this(false);
}
/**
* Pauses this Stopwatch.Entry. This method only has an effect if the Stopwatch.Entry is not currently paused.
*/
public void stop() {
if (!stopped) {
this.stopped = true;
this.stopTime = current();
}
}
/**
* Starts this Stopwatch.Entry. This method only has an effect if the Stopwatch.Entry is not currently started.
*/
public void start() {
if (stopped) {
this.stopped = false;
this.stoppedTimeElapsed = this.stoppedTimeElapsed + (current() - stopTime);
}
}
/**
* Fetches the time elapsed since this Stopwatch.Entry was created, <b>without taking into account pauses.</b>
*
* @return The time elapsed since this Stopwatch.Entry was created.
*/
public long rawTime() {
return current() - this.startTime;
}
/**
* Fetches the time elapsed since this Stopwatch.Entry was created, taking into account pauses.
*
* @return The time elapsed since this Stopwatch.Entry was created.
*/
public long time() {
if (stopped) {
long current = current();
this.stoppedTimeElapsed = this.stoppedTimeElapsed + (current - stopTime);
this.stopTime = current;
}
return (current() - this.startTime) - stoppedTimeElapsed;
}
/**
* Fetches the amount of time until this.time() will return <i>elapsed</i>
*
* @param elapsed The target elapsed time
* @return The amount of time until this.time() reaches <i>elapsed</i>, or a time < 0 if this.time() is greater than elapsed.
*/
public long until(long elapsed) {
return elapsed - this.time();
}
/**
* Fetches the time elapsed since this Stopwatch.Entry was created, taking into account pauses, and returns it as a human readable string.
*
* @return The human readable time elapsed since this Stopwatch.Entry was created.
*/
public String humanReadableTime() {
return Stopwatch.toTimeString(this.time(), false);
}
/**
* Fetches the amount of time until this.time() will return <i>elapsed</i>, and returns it as a human readable string.
*
* @param elapsed The target elapsed time
* @return The amount of time until this.time() reaches <i>elapsed</i>, or a time < 0 if this.time() is greater than elapsed.
*/
public String humanReadableUntil(long elapsed) {
return Stopwatch.toTimeString(this.until(elapsed), false);
}
/**
* Fetches the time elapsed since this Stopwatch.Entry was created, taking into account pauses, and returns it as a human readable string.
*
* @param roundMillis Whether or not to round milliseconds when creating the human readable string.
* @return The human readable time elapsed since this Stopwatch.Entry was created.
*/
public String humanReadableTime(boolean roundMillis) {
return Stopwatch.toTimeString(this.time(), roundMillis);
}
/**
* Fetches the amount of time until this.time() will return <i>elapsed</i>, and returns it as a human readable string.
*
* @param roundMillis Whether or not to round milliseconds when creating the human readable string.
* @param elapsed The target elapsed time
* @return The amount of time until this.time() reaches <i>elapsed</i>, or a time < 0 if this.time() is greater than elapsed.
*/
public String humanReadableUntil(long elapsed, boolean roundMillis) {
return Stopwatch.toTimeString(this.until(elapsed), roundMillis);
}
/**
* Convenience method for quickly accessing System.currentTimeMillis()
*
* @return System.currentTimeMillis()
*/
private long current() {
return System.currentTimeMillis();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment