001/* 002 * Copyright (c) 2013 Nu Echo Inc. All rights reserved. 003 */ 004 005package com.nuecho.rivr.core.util; 006 007import java.io.*; 008import java.util.regex.*; 009 010/** 011 * Represents a delta of time. 012 * 013 * @author Nu Echo Inc. 014 */ 015public final class Duration implements Comparable<Duration>, Serializable { 016 017 /* Even though they fit inside an integer, those constant should be kept as long to avoid 018 * potential mistakes (overflow) when doing arithmetics with them. By having them in a long, 019 * we avoid forgetful people who might do WEEK_IN_MILLIS * 1000 instead of WEEK_IN_MILLIS * 1000L 020 */ 021 public static final long SECOND_IN_MILLIS = 1000; 022 public static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; 023 public static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; 024 public static final long DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS; 025 public static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS; 026 public static final long YEAR_IN_MILLIS = DAY_IN_MILLIS * 365L + HOUR_IN_MILLIS * 6; 027 028 private static final long serialVersionUID = 1L; 029 030 public static final Duration ZERO = new Duration(0); 031 public static final Pattern DURATION_EXPRESSION_REGEXP = Pattern.compile("((\\d+)\\s*y)?\\s*" 032 + "((\\d+)\\s*d)?\\s*" 033 + "((\\d+)\\s*h)?\\s*" 034 + "((\\d+)\\s*m)?\\s*" 035 + "((\\d+)\\s*s)?\\s*" 036 + "((\\d+)\\s*ms)?"); 037 038 public static Duration seconds(long seconds) { 039 Assert.between(0, seconds, Long.MAX_VALUE / SECOND_IN_MILLIS); 040 return new Duration(seconds * SECOND_IN_MILLIS); 041 } 042 043 public static Duration minutes(long minutes) { 044 Assert.between(0, minutes, Long.MAX_VALUE / MINUTE_IN_MILLIS); 045 return new Duration(minutes * MINUTE_IN_MILLIS); 046 } 047 048 public static Duration hours(long hours) { 049 Assert.between(0, hours, Long.MAX_VALUE / HOUR_IN_MILLIS); 050 return new Duration(hours * HOUR_IN_MILLIS); 051 } 052 053 public static Duration days(long days) { 054 Assert.between(0, days, Long.MAX_VALUE / DAY_IN_MILLIS); 055 return new Duration(days * DAY_IN_MILLIS); 056 } 057 058 public static Duration year(long year) { 059 Assert.between(0, year, Long.MAX_VALUE / YEAR_IN_MILLIS); 060 return new Duration(year * YEAR_IN_MILLIS); 061 } 062 063 public static Duration milliseconds(long milliseconds) { 064 Assert.notNegative(milliseconds, "milliseconds"); 065 return new Duration(milliseconds); 066 } 067 068 public static Duration parse(String text) { 069 Duration duration = Duration.ZERO; 070 071 text = text.trim(); 072 Matcher matcher = DURATION_EXPRESSION_REGEXP.matcher(text); 073 074 if (!matcher.matches()) throw new IllegalArgumentException("Invalid time value syntax: [" + text + "]"); 075 076 String yearsMatch = matcher.group(2); 077 if (yearsMatch != null) { 078 duration = Duration.sum(duration, Duration.year(Long.parseLong(yearsMatch))); 079 } 080 081 String daysMatch = matcher.group(4); 082 if (daysMatch != null) { 083 duration = Duration.sum(duration, Duration.days(Long.parseLong(daysMatch))); 084 } 085 086 String hoursMatch = matcher.group(6); 087 if (hoursMatch != null) { 088 duration = Duration.sum(duration, Duration.hours(Long.parseLong(hoursMatch))); 089 } 090 091 String minutesMatch = matcher.group(8); 092 if (minutesMatch != null) { 093 duration = Duration.sum(duration, Duration.minutes(Long.parseLong(minutesMatch))); 094 } 095 096 String secondsMatch = matcher.group(10); 097 if (secondsMatch != null) { 098 duration = Duration.sum(duration, Duration.seconds(Long.parseLong(secondsMatch))); 099 } 100 101 String millisecondsMatch = matcher.group(12); 102 if (millisecondsMatch != null) { 103 duration = Duration.sum(duration, Duration.milliseconds(Long.parseLong(millisecondsMatch))); 104 } 105 return duration; 106 } 107 108 public static Duration sum(Duration a, Duration b) { 109 long millisecondsA = a.getMilliseconds(); 110 long millisecondsB = b.getMilliseconds(); 111 112 long allowedMax = Long.MAX_VALUE - millisecondsA; 113 if (millisecondsB > allowedMax) throw new AssertionError("Time sum would overflow Long capacity."); 114 115 return new Duration(millisecondsA + millisecondsB); 116 } 117 118 private final long mMilliseconds; 119 120 private Duration(long milliseconds) { 121 mMilliseconds = milliseconds; 122 } 123 124 public long getMilliseconds() { 125 return mMilliseconds; 126 } 127 128 @Override 129 public int compareTo(Duration other) { 130 // Don't simply return (int) mMilliseconds - other.mMilliseconds because of potential int overflow. 131 long diff = mMilliseconds - other.mMilliseconds; 132 if (diff < 0) return -1; 133 else if (diff > 0) return 1; 134 return 0; 135 } 136 137 @Override 138 public String toString() { 139 if (mMilliseconds == 0) return "0 ms"; 140 141 StringBuffer buffer = new StringBuffer(); 142 143 long value = append(buffer, YEAR_IN_MILLIS, "year", mMilliseconds, true, true); 144 value = append(buffer, DAY_IN_MILLIS, "day", value, true, true); 145 value = append(buffer, HOUR_IN_MILLIS, "hour", value, true, true); 146 value = append(buffer, MINUTE_IN_MILLIS, "minute", value, true, true); 147 value = append(buffer, SECOND_IN_MILLIS, "second", value, true, true); 148 append(buffer, 1, "millisecond", value, true, true); 149 150 if (mMilliseconds > 1000) { 151 buffer.append("("); 152 buffer.append(mMilliseconds); 153 buffer.append(" ms)"); 154 } 155 return buffer.toString(); 156 } 157 158 private long append(StringBuffer buffer, 159 long constant, 160 String label, 161 long baseValue, 162 boolean appendPlural, 163 boolean appendWhitespace) { 164 long value = baseValue / constant; 165 if (value == 0) return baseValue; 166 buffer.append(value); 167 168 if (appendWhitespace) { 169 buffer.append(" "); 170 } 171 buffer.append(label); 172 if (value > 1 && appendPlural) { 173 buffer.append("s"); 174 } 175 if (appendWhitespace) { 176 buffer.append(" "); 177 } 178 return baseValue % constant; 179 } 180 181 @Override 182 public int hashCode() { 183 final int prime = 31; 184 int result = 1; 185 result = prime * result + (int) (mMilliseconds ^ mMilliseconds >>> 32); 186 return result; 187 } 188 189 @Override 190 public boolean equals(Object obj) { 191 if (this == obj) return true; 192 if (obj == null) return false; 193 if (getClass() != obj.getClass()) return false; 194 Duration other = (Duration) obj; 195 if (mMilliseconds != other.mMilliseconds) return false; 196 return true; 197 } 198 199}