/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.wicket.util.time; import java.util.Locale; import java.util.Locale.Category; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.wicket.util.string.StringValue; import org.apache.wicket.util.string.StringValueConversionException; import org.apache.wicket.util.thread.ICode; import org.slf4j.Logger; /** * A <code>Duration</code> is an immutable length of time stored as a number of milliseconds. * Various factory and conversion methods are available for convenience. * <p> * These static factory methods allow easy construction of value objects using either long values * like <code>seconds(2034)</code> or <code>hours(3)</code>: * <p> * <ul> * <li><code>Duration.milliseconds(long)</code> * <li><code>Duration.seconds(int)</code> * <li><code>Duration.minutes(int)</code> * <li><code>Duration.hours(int)</code> * <li><code>Duration.days(int)</code> * </ul> * <p> * ...or double-precision floating point values like <code>days(3.2)</code>: * <p> * <ul> * <li><code>Duration.milliseconds(double)</code> * <li><code>Duration.seconds(double)</code> * <li><code>Duration.minutes(double)</code> * <li><code>Duration.hours(double)</code> * <li><code>Duration.days(double)</code> * </ul> * <p> * In the case of <code>milliseconds(double)</code>, the value will be rounded off to the nearest * integral millisecond using <code>Math.round()</code>. * <p> * The precise number of milliseconds represented by a <code>Duration</code> object can be retrieved * by calling the <code>getMilliseconds</code> method. The value of a <code>Duration</code> object * in a given unit like days or hours can be retrieved by calling one of the following unit methods, * each of which returns a double-precision floating point number: * <p> * <ul> * <li><code>seconds()</code> * <li><code>minutes()</code> * <li><code>hours()</code> * <li><code>days()</code> * </ul> * <p> * Values can be added and subtracted using the <code>add(Duration)</code> and * <code>subtract(Duration)</code> methods, each of which returns a new immutable * <code>Duration</code> object. * <p> * <code>String</code> values can be converted to <code>Duration</code> objects using the static * <code>valueOf</code> factory methods. The <code>String</code> format is the opposite of the one * created by <code>toString()</code>, which converts a <code>Duration</code> object to a readable * form, such as "3.2 hours" or "32.5 minutes". Valid units are: milliseconds, seconds, minutes * hours and days. Correct English plural forms are used in creating <code>String</code> values and * are parsed as well. The <code>Locale</code> is respected and "," will be used instead of "." in * the Eurozone. * <p> * The benchmark method will "benchmark" a <code>Runnable</code> or an {@link ICode} implementing * object, returning a <code>Duration</code> object that represents the amount of time elapsed in * running the code. * <p> * Finally, the <code>sleep</code> method will sleep for the value of a <code>Duration</code>. * * @author Jonathan Locke * @since 1.2.6 */ public class Duration extends AbstractTimeValue { private static final long serialVersionUID = 1L; /** Constant for maximum duration. */ public static final Duration MAXIMUM = milliseconds(Long.MAX_VALUE); /** Constant for no duration. */ public static final Duration NONE = milliseconds(0); /** Constant for one day. */ public static final Duration ONE_DAY = days(1); /** Constant for one hour. */ public static final Duration ONE_HOUR = hours(1); /** Constant for on minute. */ public static final Duration ONE_MINUTE = minutes(1); /** Constant for one second. */ public static final Duration ONE_SECOND = seconds(1); /** Constant for one week. */ public static final Duration ONE_WEEK = days(7); /** pattern to match strings */ private static final Pattern pattern = Pattern.compile( "([0-9]+([.,][0-9]+)?)\\s+(millisecond|second|minute|hour|day)s?", Pattern.CASE_INSENSITIVE); /** * Benchmark the given command. * * @param code * an <code>ICode</code> * @param log * optional logger to use with errors and exceptions * @return the <code>Time</code> value it took to run the code */ public static Duration benchmark(final ICode code, final Logger log) { // Get time before running code final Time start = Time.now(); // Run the code code.run(log); // Return the difference return Time.now().subtract(start); } /** * Benchmark the given command. * * @param code * a <code>Runnable</code> * @return the <code>Time</code> value it took to run the code */ public static Duration benchmark(final Runnable code) { // Get time before running code final Time start = Time.now(); // Run code code.run(); // Return the difference return Time.now().subtract(start); } /** * Retrieves the <code>Duration</code> based on days. * * @param days * days <code>double</code> value * @return the <code>Duration</code> based on days */ public static Duration days(final double days) { return hours(24.0 * days); } /** * Retrieves the <code>Duration</code> based on days. * * @param days * days <code>int</code> value * @return the <code>Duration</code> based on days */ public static Duration days(final int days) { return hours(24 * days); } /** * Calculates the amount of time elapsed since start time. * * @param start * the start <code>Time</code> * @return the elapsed period as a <code>Duration</code> * @throws IllegalStateException * if start <code>Time</code> is in the future */ public static Duration elapsed(final Time start) { return start.elapsedSince(); } /** * Retrieves the <code>Duration</code> based on hours. * * @param hours * hours <code>double</code> value * @return the <code>Duration</code> based on hours */ public static Duration hours(final double hours) { return minutes(60.0 * hours); } /** * Retrieves the <code>Duration</code> based on hours. * * @param hours * hours <code>int</code> value * @return the <code>Duration</code> based on hours */ public static Duration hours(final int hours) { return minutes(60 * hours); } /** * Retrieves the <code>Duration</code> based on milliseconds. * * @param milliseconds * milliseconds <code>double</code> value * @return the <code>Duration</code> based on milliseconds */ public static Duration milliseconds(final double milliseconds) { return milliseconds(Math.round(milliseconds)); } /** * Retrieves the <code>Duration</code> based on milliseconds. * * @param milliseconds * milliseconds <code>long</code> value * @return the <code>Duration</code> based on milliseconds */ public static Duration milliseconds(final long milliseconds) { return new Duration(milliseconds); } /** * Retrieves the <code>Duration</code> based on minutes. * * @param minutes * minutes <code>double</code> value * @return the <code>Duration</code> based on minutes */ public static Duration minutes(final double minutes) { return seconds(60.0 * minutes); } /** * Retrieves the <code>Duration</code> based on minutes. * * @param minutes * minutes <code>int</code> value * @return the <code>Duration</code> based on minutes */ public static Duration minutes(final int minutes) { return seconds(60 * minutes); } /** * Retrieves the <code>Duration</code> based on seconds. * * @param seconds * seconds <code>double</code> value * @return the <code>Duration</code> based on seconds */ public static Duration seconds(final double seconds) { return milliseconds(seconds * 1000.0); } /** * Retrieves the <code>Duration</code> based on seconds. * * @param seconds * seconds <code>int</code> value * @return the <code>Duration</code> based on seconds */ public static Duration seconds(final int seconds) { return milliseconds(seconds * 1000L); } /** * Retrieves the given <code>long</code> as a <code>Duration</code>. * * @param time * the duration <code>long</code> value in milliseconds * @return the <code>Duration</code> value */ public static Duration valueOf(final long time) { return new Duration(time); } /** * Converts the given <code>String</code> to a new <code>Duration</code> object. The string can * take the form of a floating point number followed by a number of milliseconds, seconds, * minutes, hours or days. For example "6 hours" or "3.4 days". Parsing is case-insensitive. * * @param string * a <code>String</code> to parse * @return the <code>Duration</code> value of the given <code>String</code> * @throws StringValueConversionException */ public static Duration valueOf(final String string) throws StringValueConversionException { return valueOf(string, Locale.getDefault(Locale.Category.FORMAT)); } /** * Converts the given <code>String</code> to a new <code>Duration</code> object. The string can * take the form of a floating point number followed by a number of milliseconds, seconds, * minutes, hours or days. For example "6 hours" or "3.4 days". Parsing is case-insensitive. * * @param string * a <code>String</code> to parse * @param locale * the <code>Locale</code> used for parsing * @return the <code>Duration</code> value of the given <code>String</code> * @throws StringValueConversionException */ public static Duration valueOf(final String string, final Locale locale) throws StringValueConversionException { final Matcher matcher = pattern.matcher(string); if (matcher.matches()) { final double value = StringValue.valueOf(matcher.group(1), locale).toDouble(); final String units = matcher.group(3); if (units.equalsIgnoreCase("millisecond")) { return milliseconds(value); } else if (units.equalsIgnoreCase("second")) { return seconds(value); } else if (units.equalsIgnoreCase("minute")) { return minutes(value); } else if (units.equalsIgnoreCase("hour")) { return hours(value); } else if (units.equalsIgnoreCase("day")) { return days(value); } else { throw new StringValueConversionException("Unrecognized units: " + string); } } else { throw new StringValueConversionException("Unable to parse duration: " + string); } } /** * Private constructor forces use of static factory methods. * * @param milliseconds * number of milliseconds in this <code>Duration</code> */ protected Duration(final long milliseconds) { super(milliseconds); } /** * Adds a given <code>Duration</code> to this <code>Duration</code>. * * @param duration * the <code>Duration</code> to add * @return the sum of the <code>Duration</code>s */ public Duration add(final Duration duration) { return valueOf(getMilliseconds() + duration.getMilliseconds()); } /** * Retrieves the number of days of the current <code>Duration</code>. * * @return number of days of the current <code>Duration</code> */ public final double days() { return hours() / 24.0; } /** * Retrieves the number of hours of the current <code>Duration</code>. * * @return number of hours of the current <code>Duration</code> */ public final double hours() { return minutes() / 60.0; } /** * Retrieves the number of minutes of the current <code>Duration</code>. * * @return number of minutes of the current <code>Duration</code> */ public final double minutes() { return seconds() / 60.0; } /** * Retrieves the number of seconds of the current <code>Duration</code>. * * @return number of seconds of the current <code>Duration</code> */ public final double seconds() { return getMilliseconds() / 1000.0; } /** * Sleeps for the current <code>Duration</code>. */ public final void sleep() { if (getMilliseconds() > 0) { try { Thread.sleep(getMilliseconds()); } catch (InterruptedException e) { // Ignored } } } /** * Subtracts a given <code>Duration</code> from this <code>Duration</code>. * * @param that * the <code>Duration</code> to subtract * @return this <code>Duration</code> minus that <code>Duration</code> */ public Duration subtract(final Duration that) { return valueOf(getMilliseconds() - that.getMilliseconds()); } /** * Wait for this duration on the given monitor * * @param object * The monitor to wait on */ public void wait(final Object object) { try { object.wait(getMilliseconds()); } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * Retrieves the <code>String</code> representation of this <code>Duration</code> in days, * hours, minutes, seconds or milliseconds, as appropriate. Uses the default <code>Locale</code> * . * * @return a <code>String</code> representation */ @Override public String toString() { return toString(Locale.getDefault(Category.FORMAT)); } /** * Retrieves the <code>String</code> representation of this <code>Duration</code> in days, * hours, minutes, seconds or milliseconds, as appropriate. * * @param locale * a <code>Locale</code> * @return a <code>String</code> representation */ public String toString(final Locale locale) { if (getMilliseconds() >= 0) { if (days() >= 1.0) { return unitString(days(), "day", locale); } if (hours() >= 1.0) { return unitString(hours(), "hour", locale); } if (minutes() >= 1.0) { return unitString(minutes(), "minute", locale); } if (seconds() >= 1.0) { return unitString(seconds(), "second", locale); } return unitString(getMilliseconds(), "millisecond", locale); } else { return "N/A"; } } /** * Converts a value to a unit-suffixed value, taking care of English singular/plural suffix. * * @param value * a <code>double</code> value to format * @param units * the units to apply singular or plural suffix to * @param locale * the <code>Locale</code> * @return a <code>String</code> representation */ private String unitString(final double value, final String units, final Locale locale) { return StringValue.valueOf(value, locale) + " " + units + ((value > 1.0) ? "s" : ""); } }