All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.unkrig.commons.util.time.Duration Maven / Gradle / Ivy

Go to download

A versatile Java(TM) library that implements many useful container and utility classes.

There is a newer version: 1.1.12
Show newest version

/*
 * de.unkrig.commons - A general-purpose Java class library
 *
 * Copyright (c) 2013, Arno Unkrig
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. 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.
 *    3. The name of the author may not be used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 de.unkrig.commons.util.time;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.unkrig.commons.nullanalysis.Nullable;

/**
 * Representation of the length of time between two points of time, with a resolution of 1 millisecond.
 */
public final
class Duration {

    /**
     * Patterns for valid duraction specs.
     * 

* Notice that the pattern "{@code 12:34}" is intentionally not valid, because it is ambiguous * ("{@code hh:mm}" vs. "{@code mm:ss}"). Same for "{@code 99[.9]}". *

*/ // CHECKSTYLE WrapAndIndent:OFF // CHECKSTYLE ConstantName:OFF // CHECKSTYLE LineLength:OFF private static final Pattern TI_X = Pattern.compile("\\d++(?:\\.\\d*)?"), // 9[.9] - disallowed! TI_MS = Pattern.compile("(\\d+)ms(?:ecs?)?"), // 999ms[ec[s]] TI_S = Pattern.compile("(\\d+)(\\.\\d*)?s(?:ecs?)?"), // 59[.9]s[ec[s]] TI_M = Pattern.compile("(\\d+)mins?"), // 59min[s] TI_X_X = Pattern.compile("(?:\\d+):(?:\\d+)"), // 12:34 - disallowed! TI_M_S__1 = Pattern.compile("(\\d+)mins? (\\d+)(\\.\\d*)?s(?:ecs?)?"), // 59min 59[.9]s[ec[s]] TI_M_S__2 = Pattern.compile("(\\d+):(\\d+)(\\.\\d*)?(?:mins?)?"), // 59:59[.9][min[s]] TI_H = Pattern.compile("(\\d++)h(?:ours?)?"), // 23h[our[s]] TI_H_M__1 = Pattern.compile("(\\d+):(\\d+)h(?:ours?)?"), // 23:59h[our[s]] TI_H_M__2 = Pattern.compile("(\\d+)h(?:ours?)? (\\d+)mins?"), // 23h[our[s]] 59min[s] TI_H_M_S__1 = Pattern.compile("(\\d+):(\\d+):(\\d+)(\\.\\d*)?"), // 23:59:59[.9] TI_H_M_S__2 = Pattern.compile("(\\d+)h(?:ours?)? (\\d+)mins? (\\d+)(\\.\\d*)?s(?:ecs?)?"), // 23h[our[s]] 59min[s] 59[.9]s[ec[s]] TI_D = Pattern.compile("(\\d++)d(?:ays?)?"), // 7d[ay[s]] TI_D_H = Pattern.compile("(\\d+)d(?:ays?)? (\\d+)h(?:ours?)?"), // 7d[ay[s]] 23h[our[s]] TI_D_H_M__1 = Pattern.compile("(\\d+)d(?:ays?)? (\\d+):(\\d+)"), // 7d[ay[s]] 23:59 TI_D_H_M__2 = Pattern.compile("(\\d+)d(?:ays?)? (\\d+)h(?:ours?)? (\\d+)mins?"), // 7d[ay[s]] 23h[our[s]] 59min[s] TI_D_H_M_S__1 = Pattern.compile("(\\d+)d(?:ays?)? (\\d+):(\\d+):(\\d+)(\\.\\d*)?"), // 7d[ay[s]] 23:59:59[.9] TI_D_H_M_S__2 = Pattern.compile("(\\d+)d(?:ays?)? (\\d+)h(?:ours?)? (\\d+)mins? (\\d+)(\\.\\d*)?s(?:ecs?)?"), // 7d[ay[s]] 23h[our[s]] 59min[s] 59[.9]s[ec[s]] TI_W = Pattern.compile("(\\d++)w(?:eeks?)?"), // 1w TI_W_D = Pattern.compile("(\\d+)w(?:eeks?)? (\\d+)d(?:ays?)?"), // 1w[eek[s]] 1d[ay[s]] TI_W_D_H = Pattern.compile("(\\d+)w(?:eeks?)? (\\d+)d(?:ays?)? (\\d+)h(?:ours?)?"), // 1w[eek[s]] 1d[ay[s]] 23h[our[s]] TI_W_D_H_M__1 = Pattern.compile("(\\d+)w(?:eeks?)? (\\d+)d(?:ays?)? (\\d+):(\\d+)"), // 1w[eek[s]] 1d[ay[s]] 23:59 TI_W_D_H_M__2 = Pattern.compile("(\\d+)w(?:eeks?)? (\\d+)d(?:ays?)? (\\d+)h(?:ours?)? (\\d+)mins?"), // 1w[eek[s]] 1d[ay[s]] 23h[our[s]] 59min[s] TI_W_D_H_M_S__1 = Pattern.compile("(\\d+)w(?:eeks?)? (\\d+)d(?:ays?)? (\\d+):(\\d+):(\\d+)(\\.\\d*)?"), // 1w[eek[s]] 1d[ay[s]] 23:59:59[.9] TI_W_D_H_M_S__2 = Pattern.compile("(\\d+)w(?:eeks?)? (\\d+)d(?:ays?)? (\\d+)h(?:ours?)? (\\d+)mins? (\\d+)(\\.\\d*)?s(?:ecs?)?"); // 1w[eek[s]] 1d[ay[s]] 23h[our[s]] 59min[s] 59[.9]s[ec[s]] // CHECKSTYLE WrapAndIndent:ON // CHECKSTYLE ConstantName:ON // CHECKSTYLE LineLength:ON private final long ms; public Duration(long ms) { this.ms = ms; } public Duration(double seconds) { this.ms = (long) (1000.0 * seconds); } /** * Creates a {@link Duration} from a string representation. *

* Accepts duration specifications in any of the following formats: *

*
    *
  • {@code 999ms} (milliseconds)
  • *
  • {@code 59[.9]s} (seconds)
  • *
  • {@code 59min} (minutes)
  • *
  • {@code 59min 59[.9]s}
  • *
  • {@code 59:59[.9][min]}
  • *
  • {@code 23h} (hours)
  • *
  • {@code 23:59h}
  • *
  • {@code 23h 59min}
  • *
  • {@code 23:59:59[.9]} (hours, minutes and seconds)
  • *
  • {@code 23h 59min 59[.9]sec}
  • *
  • {@code 7d} (days)
  • *
  • {@code 7d 23h}
  • *
  • {@code 7d 23:59}
  • *
  • {@code 7d 23h 59min}
  • *
  • {@code 7d 23:59:59[.9]}
  • *
  • {@code 7d 23h 59min 59[.9]s}
  • *
  • {@code 1w} (weeks, i.e. intervals of seven days)
  • *
  • {@code 1w 1d}
  • *
  • {@code 1w 1d 23h}
  • *
  • {@code 1w 1d 23:59}
  • *
  • {@code 1w 1d 23h 59min}
  • *
  • {@code 1w 1d 23:59:59[.9]}
  • *
  • {@code 1w 1d 23h 59min 59[.9]s}
  • *
*

* Notice that the pattern "{@code 12:34}" is intentionally not valid, because it is ambiguous * ("{@code hh:mm}" vs. "{@code mm:ss}"). Same for "{@code 99[.9]}". *

*

* Some alternative unit notations are allowed: *

*
*
ms
*
msec msecs
*
s
*
sec secs
*
min
*
mins
*
h
*
hour hours
*
d
*
day days
*
w
*
week weeks
*
*/ public Duration(String s) { Matcher m; if ((m = Duration.TI_X.matcher(s)).matches()) { throw new IllegalArgumentException( "'" + s + "' could mean weeks, days, hours, minutes, seconds or milliseconds; please write '" + s + "w, " + s + "d, " + s + "h, " + s + "min, " + s + "s, " + s + "ms, " + s + ":00:00', '0:" + s + ":00' or '0:0:" + s + "'" ); } if ((m = Duration.TI_MS.matcher(s)).matches()) { this.ms = Duration.milliseconds(m.group(1)); return; } if ((m = Duration.TI_S.matcher(s)).matches()) { this.ms = Duration.seconds(m.group(1)) + Duration.sfrac(m.group(2)); return; } if ((m = Duration.TI_M.matcher(s)).matches()) { this.ms = Duration.minutes(m.group(1)); return; } if ((m = Duration.TI_X_X.matcher(s)).matches()) { throw new IllegalArgumentException( "'" + s + "' could mean 'hh:mm' or 'mm:ss'; please use either '" + s + ":00' or '0:" + s + "'" ); } if ((m = Duration.TI_M_S__1.matcher(s)).matches() || (m = Duration.TI_M_S__2.matcher(s)).matches()) { this.ms = Duration.minutes(m.group(1)) + Duration.seconds(m.group(2)) + Duration.sfrac(m.group(3)); return; } if ((m = Duration.TI_H.matcher(s)).matches()) { this.ms = Duration.hours(m.group(1)); return; } if ((m = Duration.TI_H_M__1.matcher(s)).matches() || (m = Duration.TI_H_M__2.matcher(s)).matches()) { this.ms = Duration.hours(m.group(1)) + Duration.minutes(m.group(2)); return; } if ((m = Duration.TI_H_M_S__1.matcher(s)).matches() || (m = Duration.TI_H_M_S__2.matcher(s)).matches()) { this.ms = ( 0 + Duration.hours(m.group(1)) + Duration.minutes(m.group(2)) + Duration.seconds(m.group(3)) + Duration.sfrac(m.group(4)) ); return; } if ((m = Duration.TI_D.matcher(s)).matches()) { this.ms = Duration.days(m.group(1)); return; } if ((m = Duration.TI_D_H.matcher(s)).matches()) { this.ms = ( 0 + Duration.days(m.group(1)) + Duration.hours(m.group(2)) ); return; } if ((m = Duration.TI_D_H_M__1.matcher(s)).matches() || (m = Duration.TI_D_H_M__2.matcher(s)).matches()) { this.ms = ( 0 + Duration.days(m.group(1)) + Duration.hours(m.group(2)) + Duration.minutes(m.group(3)) ); return; } if ((m = Duration.TI_D_H_M_S__1.matcher(s)).matches() || (m = Duration.TI_D_H_M_S__2.matcher(s)).matches()) { this.ms = ( 0 + Duration.days(m.group(1)) + Duration.hours(m.group(2)) + Duration.minutes(m.group(3)) + Duration.seconds(m.group(4)) + Duration.sfrac(m.group(5)) ); return; } if ((m = Duration.TI_W.matcher(s)).matches()) { this.ms = Duration.weeks(m.group(1)); return; } if ((m = Duration.TI_W_D.matcher(s)).matches()) { this.ms = ( 0 + Duration.weeks(m.group(1)) + Duration.days(m.group(2)) ); return; } if ((m = Duration.TI_W_D_H.matcher(s)).matches()) { this.ms = ( 0 + Duration.weeks(m.group(1)) + Duration.days(m.group(2)) + Duration.hours(m.group(3)) ); return; } if ((m = Duration.TI_W_D_H_M__1.matcher(s)).matches() || (m = Duration.TI_W_D_H_M__2.matcher(s)).matches()) { this.ms = ( 0 + Duration.weeks(m.group(1)) + Duration.days(m.group(2)) + Duration.hours(m.group(3)) + Duration.minutes(m.group(4)) ); return; } if ( (m = Duration.TI_W_D_H_M_S__1.matcher(s)).matches() || (m = Duration.TI_W_D_H_M_S__2.matcher(s)).matches() ) { this.ms = ( 0 + Duration.weeks(m.group(1)) + Duration.days(m.group(2)) + Duration.hours(m.group(3)) + Duration.minutes(m.group(4)) + Duration.seconds(m.group(5)) + Duration.sfrac(m.group(6)) ); return; } throw new IllegalArgumentException("Time interval '" + s + "' cannot be parsed"); } private static long weeks(String s) { return Long.parseLong(s) * (7 * 24 * 3600 * 1000); } private static long days(String s) { return Long.parseLong(s) * (24 * 3600 * 1000); } private static long hours(String s) { return Long.parseLong(s) * (3600 * 1000); } private static long minutes(String s) { return Long.parseLong(s) * (60 * 1000); } private static long seconds(String s) { return Long.parseLong(s) * 1000; } private static long milliseconds(String s) { return Long.parseLong(s); } /** * Parses strings like {@code . .9 .99 .999}. *

* Iff the string is longer than four characters, then the excess characters are ignored. *

*

* Iff the first character is not the period, or iff the the second, third and fourth character is not a * digit, then the result is undefined. *

* * @return The parsed value in milliseconds, e.g. {@code 900 990 999}, or {@code 0} iff {@code s == null} */ private static long sfrac(@Nullable String s) { if (s == null) return 0; int l = s.length(); int result = 0; if (l >= 2) { result += Character.digit(s.charAt(1), 10) * 100; if (l >= 3) { result += Character.digit(s.charAt(2), 10) * 10; if (l >= 4) result += Character.digit(s.charAt(3), 10); } } return result; } /** @return The number of milliseconds represented by this object */ public long milliseconds() { return this.ms; } /** @return The length of time in seconds represented by this object */ public double toSeconds() { return this.ms / 1000.0; } @Override public String toString() { return ( this.ms < 1000 ? this.ms + "ms" : this.ms < 60 * 1000 ? Duration.msToString(this.ms, false) + 's' : this.ms < 24 * 60 * 60 * 1000 ? String.format( Locale.US, "%d:%02d:%s", (this.ms / (60 * 60 * 1000)), (this.ms / (60 * 1000)) % 60, Duration.msToString(this.ms % (60 * 1000), true) ) : this.ms < 7 * 24 * 60 * 60 * 1000 ? String.format( Locale.US, "%dd %d:%02d:%s", (this.ms / (24 * 60 * 60 * 1000)), (this.ms / (60 * 60 * 1000)) % 24, (this.ms / (60 * 1000)) % 60, Duration.msToString(this.ms % (60 * 1000), true) ) : String.format( Locale.US, "%dw %dd %d:%02d:%s", (this.ms / (7 * 24 * 60 * 60 * 1000)), (this.ms / (24 * 60 * 60 * 1000)) % 7, (this.ms / (60 * 60 * 1000)) % 24, (this.ms / (60 * 1000)) % 60, Duration.msToString(this.ms % (60 * 1000), true) ) ); } /** * @param twoDigits Whether to left-pad the integral part with '0' */ private static String msToString(long t, boolean twoDigits) { StringBuilder sb = new StringBuilder(); long s = (int) (t / 1000); if (twoDigits && s <= 9) sb.append('0'); sb.append(s); int ms = (int) (t % 1000); if (ms != 0) { sb.append('.').append(Character.forDigit(ms / 100, 10)); if (ms % 100 != 0) { sb.append(Character.forDigit(ms / 10 % 10, 10)); if (ms % 10 != 0) { sb.append(Character.forDigit(ms % 10, 10)); } } } return sb.toString(); } /** @return A duration that represents the sum of {@code this} and {@code other} */ public Duration add(Duration other) { return new Duration(this.ms + other.ms); } /** @return A duration that is {@code factor} as long as this duration */ public Duration multiply(double factor) { return new Duration((long) (this.ms * factor)); } /** @return A duration which is one {@code divisor}th of this duration long */ public Duration divide(double divisor) { return new Duration((long) (this.ms / divisor)); } /** @return Whether this object represents the zero-length interval */ public boolean isZero() { return this.ms == 0; } @Override public int hashCode() { return (int) (this.ms ^ (this.ms >>> 32)); } @Override public boolean equals(@Nullable Object obj) { return obj == this || (obj instanceof Duration && ((Duration) obj).ms == this.ms); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy