001 /**
002 * Copyright 2010-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 */
016 package org.kuali.common.util;
017
018 import java.text.NumberFormat;
019 import java.text.ParseException;
020 import java.text.SimpleDateFormat;
021 import java.util.ArrayList;
022 import java.util.Arrays;
023 import java.util.Date;
024 import java.util.List;
025
026 import org.apache.commons.lang3.StringUtils;
027
028 /**
029 * Format time, bytes, counts, dates, and transfer rates into human friendly form
030 *
031 * @author Jeff Caddel
032 * @since May 27, 2010 6:46:17 PM
033 */
034 public class FormatUtils {
035
036 public static final double SECOND = 1000;
037 public static final double MINUTE = 60 * SECOND;
038 public static final double HOUR = 60 * MINUTE;
039 public static final double DAY = 24 * HOUR;
040 public static final double YEAR = 365 * DAY;
041
042 private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
043
044 private static final List<String> TIME_TOKENS = Arrays.asList("ms", "s", "m", "h", "d", "y");
045 private static final List<Long> TIME_MULTIPLIERS = getTimeMultipliers();
046
047 private static final List<String> SIZE_TOKENS = Arrays.asList("b", "k", "m", "g", "t", "p", "e");
048 private static final int BASE = 1024;
049
050 private static NumberFormat largeSizeFormatter = NumberFormat.getInstance();
051 private static NumberFormat sizeFormatter = NumberFormat.getInstance();
052 private static NumberFormat timeFormatter = NumberFormat.getInstance();
053 private static NumberFormat rateFormatter = NumberFormat.getInstance();
054 private static NumberFormat countFormatter = NumberFormat.getInstance();
055
056 static {
057 sizeFormatter.setGroupingUsed(false);
058 sizeFormatter.setMaximumFractionDigits(1);
059 sizeFormatter.setMinimumFractionDigits(1);
060 largeSizeFormatter.setGroupingUsed(false);
061 largeSizeFormatter.setMaximumFractionDigits(3);
062 largeSizeFormatter.setMinimumFractionDigits(3);
063 timeFormatter.setGroupingUsed(false);
064 timeFormatter.setMaximumFractionDigits(3);
065 timeFormatter.setMinimumFractionDigits(3);
066 rateFormatter.setGroupingUsed(false);
067 rateFormatter.setMaximumFractionDigits(3);
068 rateFormatter.setMinimumFractionDigits(3);
069 countFormatter.setGroupingUsed(true);
070 countFormatter.setMaximumFractionDigits(0);
071 countFormatter.setMinimumFractionDigits(0);
072 }
073
074 /**
075 * Parse bytes from a size string that ends with a unit of measure. If no unit of measure is provided, bytes is assumed. Unit of measure is case insensitive.
076 *
077 * <pre>
078 * 1 == 1 byte
079 * 1b == 1 byte
080 * 1k == 1 kilobyte == 1024 bytes == 1,024 bytes
081 * 1m == 1 megabyte == 1024^2 bytes == 1,048,576 bytes
082 * 1g == 1 gigabyte == 1024^3 bytes == 1,073,741,824 bytes
083 * 1t == 1 terabyte == 1024^4 bytes == 1,099,511,627,776 bytes
084 * 1p == 1 petabyte == 1024^5 bytes == 1,125,899,906,842,624 bytes
085 * 1e == 1 exabyte == 1024^6 bytes == 1,152,921,504,606,846,976 bytes
086 * </pre>
087 */
088 public static long getBytes(String size) {
089 return getBytes(size, SIZE_TOKENS, BASE);
090 }
091
092 public static long getBytes(String size, List<String> tokens, int base) {
093 Assert.notBlank(size);
094 for (int i = 0; i < tokens.size(); i++) {
095 String token = tokens.get(i);
096 long multiplier = (long) Math.pow(base, i);
097 if (StringUtils.endsWithIgnoreCase(size, token)) {
098 return getByteValue(size, token, multiplier);
099 }
100 }
101 // Assume bytes
102 return getByteValue(size, "", 1);
103 }
104
105 protected static long getByteValue(String time, String suffix, long multiplier) {
106 int len = StringUtils.length(time);
107 String substring = StringUtils.substring(time, 0, len - suffix.length());
108 Double value = new Double(substring);
109 value = value * multiplier;
110 return value.longValue();
111 }
112
113 /**
114 * Parse milliseconds from a time string that ends with a unit of measure. If no unit of measure is provided, milliseconds is assumed. Unit of measure is case insensitive.
115 *
116 * <pre>
117 * 1 == 1 millisecond
118 * 1ms == 1 millisecond
119 * 1s == 1 second == 1000 milliseconds
120 * 1m == 1 minute == 60,000 milliseconds
121 * 1h == 1 hour == 3,600,000 milliseconds
122 * 1d == 1 day == 86,400,000 milliseconds
123 * 1y == 1 year == 31,536,000,000 milliseconds
124 * </pre>
125 */
126 public static long getMillis(String time) {
127 return getMillis(time, TIME_TOKENS, TIME_MULTIPLIERS);
128 }
129
130 public static long getMillis(String time, List<String> tokens, List<Long> multipliers) {
131 Assert.notBlank(time);
132 Assert.isTrue(tokens.size() == multipliers.size());
133 for (int i = 0; i < tokens.size(); i++) {
134 String token = tokens.get(i);
135 long multiplier = multipliers.get(i);
136 if (StringUtils.endsWithIgnoreCase(time, token)) {
137 return getTimeValue(time, token, multiplier);
138 }
139 }
140 // Assume milliseconds
141 return getTimeValue(time, "", 1);
142 }
143
144 protected static long getTimeValue(String time, String suffix, long multiplier) {
145 int len = StringUtils.length(time);
146 String substring = StringUtils.substring(time, 0, len - suffix.length());
147 Double value = new Double(substring);
148 value = value * multiplier;
149 return value.longValue();
150 }
151
152 /**
153 * Parse a date from the string. The string must be in the same format returned by the getDate() methods
154 */
155 public static Date parseDate(String date) {
156 try {
157 // New object every time because SimpleDateFormat isn't threadsafe
158 SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
159 return sdf.parse(date);
160 } catch (ParseException e) {
161 throw new IllegalArgumentException("Can't parse [" + date + "]", e);
162 }
163 }
164
165 /**
166 * Return a formatted date
167 */
168 public static String getDate(long millis) {
169 return getDate(new Date(millis));
170 }
171
172 /**
173 * Return a formatted date
174 */
175 public static String getDate(Date date) {
176 // New object every time because SimpleDateFormat isn't threadsafe
177 SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
178 return sdf.format(date);
179 }
180
181 /**
182 *
183 */
184 public static String getThroughputInSeconds(long millis, long count, String label) {
185 double seconds = millis / SECOND;
186 double countPerSecond = count / seconds;
187 return countFormatter.format(countPerSecond) + " " + label;
188 }
189
190 /**
191 * Given a number of bytes and the number of milliseconds it took to transfer that number of bytes, return bytes/s, KB/s, MB/s, GB/s, TB/s, PB/s, or EB/s as appropriate
192 */
193 public static String getRate(long millis, long bytes) {
194 double seconds = millis / SECOND;
195 double bytesPerSecond = bytes / seconds;
196 Size bandwidthLevel = getSizeEnum(bytesPerSecond);
197 double transferRate = bytesPerSecond / bandwidthLevel.getValue();
198 return rateFormatter.format(transferRate) + " " + bandwidthLevel.getRateLabel();
199 }
200
201 /**
202 * Return a formatted <code>count</code>
203 */
204 public static String getCount(long count) {
205 return countFormatter.format(count);
206 }
207
208 /**
209 * Given milliseconds, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
210 * exactly 365 days per year.
211 */
212 public static String getTime(long millis) {
213 long abs = Math.abs(millis);
214 if (abs < SECOND) {
215 return millis + "ms";
216 } else if (abs < MINUTE) {
217 return timeFormatter.format(millis / SECOND) + "s";
218 } else if (abs < HOUR) {
219 return timeFormatter.format(millis / MINUTE) + "m";
220 } else if (abs < DAY) {
221 return timeFormatter.format(millis / HOUR) + "h";
222 } else if (abs < YEAR) {
223 return timeFormatter.format(millis / DAY) + "d";
224 } else {
225 return timeFormatter.format(millis / YEAR) + "y";
226 }
227 }
228
229 /**
230 * Given a number of bytes return bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, or exabytes as appropriate.
231 */
232 public static String getSize(long bytes) {
233 return getSize(bytes, null);
234 }
235
236 /**
237 * Given a number of bytes return a string formatted into the unit of measure indicated
238 */
239 public static String getSize(long bytes, Size unitOfMeasure) {
240 unitOfMeasure = (unitOfMeasure == null) ? getSizeEnum(bytes) : unitOfMeasure;
241 StringBuilder sb = new StringBuilder();
242 sb.append(getFormattedSize(bytes, unitOfMeasure));
243 sb.append(unitOfMeasure.getSizeLabel());
244 return sb.toString();
245 }
246
247 public static String getFormattedSize(long bytes, Size size) {
248 switch (size) {
249 case BYTE:
250 return bytes + "";
251 case KB:
252 case MB:
253 case GB:
254 return sizeFormatter.format(bytes / (double) size.getValue());
255 default:
256 return largeSizeFormatter.format(bytes / (double) size.getValue());
257 }
258 }
259
260 public static Size getSizeEnum(double bytes) {
261 bytes = Math.abs(bytes);
262 if (bytes < Size.KB.getValue()) {
263 return Size.BYTE;
264 } else if (bytes < Size.MB.getValue()) {
265 return Size.KB;
266 } else if (bytes < Size.GB.getValue()) {
267 return Size.MB;
268 } else if (bytes < Size.TB.getValue()) {
269 return Size.GB;
270 } else if (bytes < Size.PB.getValue()) {
271 return Size.TB;
272 } else if (bytes < Size.EB.getValue()) {
273 return Size.PB;
274 } else {
275 return Size.EB;
276 }
277 }
278
279 protected static final List<Long> getTimeMultipliers() {
280 List<Long> m = new ArrayList<Long>();
281 m.add(1L);
282 m.add(new Double(SECOND).longValue());
283 m.add(new Double(MINUTE).longValue());
284 m.add(new Double(HOUR).longValue());
285 m.add(new Double(DAY).longValue());
286 m.add(new Double(YEAR).longValue());
287 return m;
288 }
289 }