001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.common.math;
017
018import java.math.BigDecimal;
019import java.text.DecimalFormat;
020import java.util.concurrent.TimeUnit;
021import org.modeshape.common.annotation.Immutable;
022
023/**
024 * A number representing an immutable duration of time. This is intended to be used in the same manner as other {@link Number}
025 * subclasses.
026 */
027@Immutable
028public class Duration extends Number implements Comparable<Duration> {
029
030    private static final long serialVersionUID = 1L;
031
032    private final long durationInNanos;
033    private Components components;
034
035    /**
036     * Create a duration given the number of nanoseconds.
037     * 
038     * @param nanos the number of nanoseconds in the duration
039     */
040    public Duration( long nanos ) {
041        this(nanos, TimeUnit.NANOSECONDS);
042    }
043
044    /**
045     * Create a duration and the time unit.
046     * 
047     * @param duration the duration in the supplied time units
048     * @param unit the time unit
049     */
050    public Duration( long duration,
051                     TimeUnit unit ) {
052        this.durationInNanos = TimeUnit.NANOSECONDS.convert(duration, unit);
053    }
054
055    @Override
056    public double doubleValue() {
057        return this.durationInNanos;
058    }
059
060    @Override
061    public float floatValue() {
062        return this.durationInNanos;
063    }
064
065    @Override
066    public int intValue() {
067        return (int)this.durationInNanos;
068    }
069
070    @Override
071    public long longValue() {
072        return this.durationInNanos;
073    }
074
075    public BigDecimal toBigDecimal() {
076        return new BigDecimal(this.durationInNanos);
077    }
078
079    /**
080     * Add the supplied duration to this duration, and return the result.
081     * 
082     * @param duration the duration to add to this object
083     * @param unit the unit of the duration being added; may not be null
084     * @return the total duration
085     */
086    public Duration add( long duration,
087                         TimeUnit unit ) {
088        long durationInNanos = TimeUnit.NANOSECONDS.convert(duration, unit);
089        return new Duration(this.durationInNanos + durationInNanos);
090    }
091
092    /**
093     * Subtract the supplied duration from this duration, and return the result.
094     * 
095     * @param duration the duration to subtract from this object
096     * @param unit the unit of the duration being subtracted; may not be null
097     * @return the total duration
098     */
099    public Duration subtract( long duration,
100                              TimeUnit unit ) {
101        long durationInNanos = TimeUnit.NANOSECONDS.convert(duration, unit);
102        return new Duration(this.durationInNanos - durationInNanos);
103    }
104
105    /**
106     * Add the supplied duration to this duration, and return the result. A null value is treated as a duration of 0 nanoseconds.
107     * 
108     * @param duration the duration to add to this object
109     * @return the total duration
110     */
111    public Duration add( Duration duration ) {
112        return new Duration(this.durationInNanos + (duration == null ? 0l : duration.longValue()));
113    }
114
115    /**
116     * Subtract the supplied duration from this duration, and return the result. A null value is treated as a duration of 0
117     * nanoseconds.
118     * 
119     * @param duration the duration to subtract from this object
120     * @return the resulting duration
121     */
122    public Duration subtract( Duration duration ) {
123        return new Duration(this.durationInNanos - (duration == null ? 0l : duration.longValue()));
124    }
125
126    /**
127     * Multiply the duration by the supplied scale factor, and return the result.
128     * 
129     * @param scale the factor by which the duration is to be scaled.
130     * @return the scaled duration
131     */
132    public Duration multiply( long scale ) {
133        return new Duration(this.durationInNanos * scale);
134    }
135
136    /**
137     * Divide the duration by the supplied number, and return the result.
138     * 
139     * @param denominator the factor by which the duration is to be divided.
140     * @return the resulting duration
141     */
142    public Duration divide( long denominator ) {
143        return new Duration(this.durationInNanos / denominator);
144    }
145
146    /**
147     * Divide the duration by another duration to calculate the ratio.
148     * 
149     * @param duration the duration that this duration is to be divided by; may not be null
150     * @return the resulting duration
151     */
152    public double divide( Duration duration ) {
153        return this.toBigDecimal().divide(duration.toBigDecimal()).doubleValue();
154    }
155
156    @Override
157    public int compareTo( Duration that ) {
158        if (that == null) return 1;
159        return this.durationInNanos < that.durationInNanos ? -1 : this.durationInNanos > that.durationInNanos ? 1 : 0;
160    }
161
162    /**
163     * Return the total duration in nanoseconds.
164     * 
165     * @return the total duration in nanoseconds
166     */
167    public long getDuratinInNanoseconds() {
168        return this.durationInNanos;
169    }
170
171    /**
172     * Return the total duration in microseconds, which may contain a fraction part for the sub-microsecond component.
173     * 
174     * @return the total duration in microseconds
175     */
176    public BigDecimal getDurationInMicroseconds() {
177        return this.toBigDecimal().divide(new BigDecimal(1000));
178    }
179
180    /**
181     * Return the total duration in microseconds, which may contain a fraction part for the sub-microsecond component.
182     * 
183     * @return the total duration in microseconds
184     */
185    public BigDecimal getDurationInMilliseconds() {
186        return this.toBigDecimal().divide(new BigDecimal(1000000));
187    }
188
189    /**
190     * Return the total duration in microseconds, which may contain a fraction part for the sub-microsecond component.
191     * 
192     * @return the total duration in microseconds
193     */
194    public BigDecimal getDurationInSeconds() {
195        return this.toBigDecimal().divide(new BigDecimal(1000000000));
196    }
197
198    /**
199     * Return the duration components.
200     * 
201     * @return the individual time components of this duration
202     */
203    public Components getComponents() {
204        if (this.components == null) {
205            // This is idempotent, so no need to synchronize ...
206
207            // Calculate how many seconds, and don't lose any information ...
208            BigDecimal bigSeconds = new BigDecimal(this.durationInNanos).divide(new BigDecimal(1000000000));
209            // Calculate the minutes, and round to lose the seconds
210            int minutes = bigSeconds.intValue() / 60;
211            // Remove the minutes from the seconds, to just have the remainder of seconds
212            double dMinutes = minutes;
213            double seconds = bigSeconds.doubleValue() - dMinutes * 60;
214            // Now compute the number of full hours, and change 'minutes' to hold the remainding minutes
215            int hours = minutes / 60;
216            minutes = minutes - (hours * 60);
217            this.components = new Components(hours, minutes, seconds);
218        }
219        return this.components;
220    }
221
222    /**
223     * Get the duration value in the supplied unit of time.
224     * 
225     * @param unit the unit of time for the returned value; may not be null
226     * @return the value of this duration in the supplied unit of time
227     */
228    public long getDuration( TimeUnit unit ) {
229        if (unit == null) throw new IllegalArgumentException();
230        return unit.convert(durationInNanos, TimeUnit.NANOSECONDS);
231    }
232
233    /**
234     * Writes the duration in a form containing hours, minutes, and seconds, including the fractional part of the seconds. The
235     * format is essentially <code>HHH:MM:SS.mmm,mmm</code>, where
236     * <dl>
237     * <dt>HHH</dt>
238     * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
239     * <dt>MM</dt>
240     * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
241     * <dt>SS</dt>
242     * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
243     * <dt>mmm,mmm</dt>
244     * <dd>is the fractional part of seconds, written in at least millisecond precision and up to microsecond precision. The comma
245     * appears if more than 3 digits are used.</dd>
246     * </dl>
247     * 
248     * @return a string representation of the duration
249     */
250    @Override
251    public String toString() {
252        // Insert a comma after the milliseconds, if there are enough digits ..
253        return this.getComponents().toString().replaceAll("(\\d{2}).(\\d{3})(\\d{1,3})", "$1.$2,$3");
254    }
255
256    /**
257     * Writes the duration in a form containing hours, minutes, and seconds, excluding the fractional part of the seconds. The
258     * format is essentially <code>HHH:MM:SS</code>, where
259     * <dl>
260     * <dt>HHH</dt>
261     * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
262     * <dt>MM</dt>
263     * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
264     * <dt>SS</dt>
265     * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
266     * </dl>
267     * 
268     * @return a string representation of the duration
269     */
270    public String toSimpleString() {
271        // Insert a comma after the milliseconds, if there are enough digits ..
272        return this.getComponents().toSimpleString();
273    }
274
275    /**
276     * The atomic components of this duration, broken down into whole hours, minutes and (fractional) seconds.
277     */
278    public class Components {
279
280        private final int hours;
281        private final int minutes;
282        private final double seconds;
283
284        protected Components( int hours,
285                              int minutes,
286                              double seconds ) {
287            this.hours = hours;
288            this.minutes = minutes;
289            this.seconds = seconds;
290        }
291
292        /**
293         * Get the whole hours in this duration.
294         * 
295         * @return the hours
296         */
297        public int getHours() {
298            return hours;
299        }
300
301        /**
302         * Get the whole minutes in this duration.
303         * 
304         * @return the minutes, from 0 to 59.
305         */
306        public int getMinutes() {
307            return minutes;
308        }
309
310        /**
311         * Get the duration's seconds component.
312         * 
313         * @return the number of seconds, including fractional part.
314         */
315        public double getSeconds() {
316            return seconds;
317        }
318
319        /**
320         * Return the duration as a string in a form containing hours, minutes, and seconds, including the fractional part of the
321         * seconds. The format is essentially <code>HHH:MM:SS.mmm</code>, where
322         * <dl>
323         * <dt>HHH</dt>
324         * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
325         * <dt>MM</dt>
326         * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
327         * <dt>SS</dt>
328         * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
329         * <dt>mmm</dt>
330         * <dd>is the fractional part of seconds, written with 3-6 digits (any trailing zeros are dropped)</dd>
331         * </dl>
332         * 
333         * @return a string representation of the duration components
334         */
335        @Override
336        public String toString() {
337            // Format the string, and have at least 2 digits for the hours, minutes and whole seconds,
338            // and between 3 and 6 digits for the fractional part of the seconds...
339            String result = new DecimalFormat("######00").format(hours) + ':' + new DecimalFormat("00").format(minutes) + ':'
340                            + new DecimalFormat("00.000###").format(seconds);
341            return result;
342        }
343
344        /**
345         * Return the duration as a string in a form containing hours, minutes, and seconds, excluding the fractional part of the
346         * seconds. The format is essentially <code>HHH:MM:SS.mmm</code>, where
347         * <dl>
348         * <dt>HHH</dt>
349         * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
350         * <dt>MM</dt>
351         * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
352         * <dt>SS</dt>
353         * <dd>is the number of hours written in at least 2 digits (e.g., "03")</dd>
354         * </dl>
355         * 
356         * @return a simple string representation of the duration components
357         */
358        public String toSimpleString() {
359            // Format the string, and have at least 2 digits for the hours, minutes and whole seconds ...
360            String result = new DecimalFormat("######00").format(hours) + ':' + new DecimalFormat("00").format(minutes) + ':'
361                            + new DecimalFormat("00").format(seconds);
362            return result;
363        }
364    }
365
366}