/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.common.math;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.concurrent.TimeUnit;
import org.modeshape.common.annotation.Immutable;
/**
* A number representing an immutable duration of time. This is intended to be used in the same manner as other {@link Number}
* subclasses.
*/
@Immutable
public class Duration extends Number implements Comparable<Duration> {
private static final long serialVersionUID = 1L;
private final long durationInNanos;
private Components components;
/**
* Create a duration given the number of nanoseconds.
*
* @param nanos the number of nanoseconds in the duration
*/
public Duration( long nanos ) {
this(nanos, TimeUnit.NANOSECONDS);
}
/**
* Create a duration and the time unit.
*
* @param duration the duration in the supplied time units
* @param unit the time unit
*/
public Duration( long duration,
TimeUnit unit ) {
this.durationInNanos = TimeUnit.NANOSECONDS.convert(duration, unit);
}
@Override
public double doubleValue() {
return this.durationInNanos;
}
@Override
public float floatValue() {
return this.durationInNanos;
}
@Override
public int intValue() {
return (int)this.durationInNanos;
}
@Override
public long longValue() {
return this.durationInNanos;
}
public BigDecimal toBigDecimal() {
return new BigDecimal(this.durationInNanos);
}
/**
* Add the supplied duration to this duration, and return the result.
*
* @param duration the duration to add to this object
* @param unit the unit of the duration being added; may not be null
* @return the total duration
*/
public Duration add( long duration,
TimeUnit unit ) {
long durationInNanos = TimeUnit.NANOSECONDS.convert(duration, unit);
return new Duration(this.durationInNanos + durationInNanos);
}
/**
* Subtract the supplied duration from this duration, and return the result.
*
* @param duration the duration to subtract from this object
* @param unit the unit of the duration being subtracted; may not be null
* @return the total duration
*/
public Duration subtract( long duration,
TimeUnit unit ) {
long durationInNanos = TimeUnit.NANOSECONDS.convert(duration, unit);
return new Duration(this.durationInNanos - durationInNanos);
}
/**
* Add the supplied duration to this duration, and return the result. A null value is treated as a duration of 0 nanoseconds.
*
* @param duration the duration to add to this object
* @return the total duration
*/
public Duration add( Duration duration ) {
return new Duration(this.durationInNanos + (duration == null ? 0l : duration.longValue()));
}
/**
* Subtract the supplied duration from this duration, and return the result. A null value is treated as a duration of 0
* nanoseconds.
*
* @param duration the duration to subtract from this object
* @return the resulting duration
*/
public Duration subtract( Duration duration ) {
return new Duration(this.durationInNanos - (duration == null ? 0l : duration.longValue()));
}
/**
* Multiply the duration by the supplied scale factor, and return the result.
*
* @param scale the factor by which the duration is to be scaled.
* @return the scaled duration
*/
public Duration multiply( long scale ) {
return new Duration(this.durationInNanos * scale);
}
/**
* Divide the duration by the supplied number, and return the result.
*
* @param denominator the factor by which the duration is to be divided.
* @return the resulting duration
*/
public Duration divide( long denominator ) {
return new Duration(this.durationInNanos / denominator);
}
/**
* Divide the duration by another duration to calculate the ratio.
*
* @param duration the duration that this duration is to be divided by; may not be null
* @return the resulting duration
*/
public double divide( Duration duration ) {
return this.toBigDecimal().divide(duration.toBigDecimal()).doubleValue();
}
@Override
public int compareTo( Duration that ) {
if (that == null) return 1;
return this.durationInNanos < that.durationInNanos ? -1 : this.durationInNanos > that.durationInNanos ? 1 : 0;
}
/**
* Return the total duration in nanoseconds.
*
* @return the total duration in nanoseconds
*/
public long getDuratinInNanoseconds() {
return this.durationInNanos;
}
/**
* Return the total duration in microseconds, which may contain a fraction part for the sub-microsecond component.
*
* @return the total duration in microseconds
*/
public BigDecimal getDurationInMicroseconds() {
return this.toBigDecimal().divide(new BigDecimal(1000));
}
/**
* Return the total duration in microseconds, which may contain a fraction part for the sub-microsecond component.
*
* @return the total duration in microseconds
*/
public BigDecimal getDurationInMilliseconds() {
return this.toBigDecimal().divide(new BigDecimal(1000000));
}
/**
* Return the total duration in microseconds, which may contain a fraction part for the sub-microsecond component.
*
* @return the total duration in microseconds
*/
public BigDecimal getDurationInSeconds() {
return this.toBigDecimal().divide(new BigDecimal(1000000000));
}
/**
* Return the duration components.
*
* @return the individual time components of this duration
*/
public Components getComponents() {
if (this.components == null) {
// This is idempotent, so no need to synchronize ...
// Calculate how many seconds, and don't lose any information ...
BigDecimal bigSeconds = new BigDecimal(this.durationInNanos).divide(new BigDecimal(1000000000));
// Calculate the minutes, and round to lose the seconds
int minutes = bigSeconds.intValue() / 60;
// Remove the minutes from the seconds, to just have the remainder of seconds
double dMinutes = minutes;
double seconds = bigSeconds.doubleValue() - dMinutes * 60;
// Now compute the number of full hours, and change 'minutes' to hold the remainding minutes
int hours = minutes / 60;
minutes = minutes - (hours * 60);
this.components = new Components(hours, minutes, seconds);
}
return this.components;
}
/**
* Get the duration value in the supplied unit of time.
*
* @param unit the unit of time for the returned value; may not be null
* @return the value of this duration in the supplied unit of time
*/
public long getDuration( TimeUnit unit ) {
if (unit == null) throw new IllegalArgumentException();
return unit.convert(durationInNanos, TimeUnit.NANOSECONDS);
}
/**
* Writes the duration in a form containing hours, minutes, and seconds, including the fractional part of the seconds. The
* format is essentially <code>HHH:MM:SS.mmm,mmm</code>, where
* <dl>
* <dt>HHH</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>MM</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>SS</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>mmm,mmm</dt>
* <dd>is the fractional part of seconds, written in at least millisecond precision and up to microsecond precision. The comma
* appears if more than 3 digits are used.</dd>
* </dl>
*
* @return a string representation of the duration
*/
@Override
public String toString() {
// Insert a comma after the milliseconds, if there are enough digits ..
return this.getComponents().toString().replaceAll("(\\d{2}).(\\d{3})(\\d{1,3})", "$1.$2,$3");
}
/**
* Writes the duration in a form containing hours, minutes, and seconds, excluding the fractional part of the seconds. The
* format is essentially <code>HHH:MM:SS</code>, where
* <dl>
* <dt>HHH</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>MM</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>SS</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* </dl>
*
* @return a string representation of the duration
*/
public String toSimpleString() {
// Insert a comma after the milliseconds, if there are enough digits ..
return this.getComponents().toSimpleString();
}
/**
* The atomic components of this duration, broken down into whole hours, minutes and (fractional) seconds.
*/
public class Components {
private final int hours;
private final int minutes;
private final double seconds;
protected Components( int hours,
int minutes,
double seconds ) {
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
}
/**
* Get the whole hours in this duration.
*
* @return the hours
*/
public int getHours() {
return hours;
}
/**
* Get the whole minutes in this duration.
*
* @return the minutes, from 0 to 59.
*/
public int getMinutes() {
return minutes;
}
/**
* Get the duration's seconds component.
*
* @return the number of seconds, including fractional part.
*/
public double getSeconds() {
return seconds;
}
/**
* Return the duration as a string in a form containing hours, minutes, and seconds, including the fractional part of the
* seconds. The format is essentially <code>HHH:MM:SS.mmm</code>, where
* <dl>
* <dt>HHH</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>MM</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>SS</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>mmm</dt>
* <dd>is the fractional part of seconds, written with 3-6 digits (any trailing zeros are dropped)</dd>
* </dl>
*
* @return a string representation of the duration components
*/
@Override
public String toString() {
// Format the string, and have at least 2 digits for the hours, minutes and whole seconds,
// and between 3 and 6 digits for the fractional part of the seconds...
String result = new DecimalFormat("######00").format(hours) + ':' + new DecimalFormat("00").format(minutes) + ':'
+ new DecimalFormat("00.000###").format(seconds);
return result;
}
/**
* Return the duration as a string in a form containing hours, minutes, and seconds, excluding the fractional part of the
* seconds. The format is essentially <code>HHH:MM:SS.mmm</code>, where
* <dl>
* <dt>HHH</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>MM</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* <dt>SS</dt>
* <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
* </dl>
*
* @return a simple string representation of the duration components
*/
public String toSimpleString() {
// Format the string, and have at least 2 digits for the hours, minutes and whole seconds ...
String result = new DecimalFormat("######00").format(hours) + ':' + new DecimalFormat("00").format(minutes) + ':'
+ new DecimalFormat("00").format(seconds);
return result;
}
}
}