001/**
002 * Copyright 2010-2014 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 */
016package org.kuali.common.util;
017
018import static java.util.concurrent.TimeUnit.MILLISECONDS;
019
020import java.text.NumberFormat;
021import java.text.ParseException;
022import java.text.SimpleDateFormat;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Date;
026import java.util.List;
027import java.util.Map;
028import java.util.TimeZone;
029
030import org.apache.commons.lang3.StringUtils;
031import org.kuali.common.util.base.Exceptions;
032
033import com.google.common.base.Stopwatch;
034import com.google.common.collect.Iterables;
035
036/**
037 * Format time, bytes, counts, dates, and transfer rates into human friendly form
038 * 
039 * @author Jeff Caddel
040 * @since May 27, 2010 6:46:17 PM
041 */
042public class FormatUtils {
043
044        public static final double SECOND = 1000;
045        public static final double MINUTE = 60 * SECOND;
046        public static final double HOUR = 60 * MINUTE;
047        public static final double DAY = 24 * HOUR;
048        public static final double YEAR = 365 * DAY;
049
050        /**
051         * This is the format produced by the {@code toString()} method of {@code java.util.Date} instance
052         */
053        public static final String JAVA_UTIL_DATE_TO_STRING_FORMAT = "EEE MMM d HH:mm:ss zzz y";
054        private static final SimpleDateFormat JAVA_UTIL_DATE_TO_STRING_FORMATTER = new SimpleDateFormat(JAVA_UTIL_DATE_TO_STRING_FORMAT);
055
056        private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
057        private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT);
058
059        private static final List<String> TIME_TOKENS = Arrays.asList("ms", "s", "m", "h", "d", "y");
060        private static final List<Long> TIME_MULTIPLIERS = getTimeMultipliers();
061
062        private static final List<String> SIZE_TOKENS = Arrays.asList("b", "k", "m", "g", "t", "p", "e");
063        private static final int BASE = 1024;
064
065        private static NumberFormat largeSizeFormatter = NumberFormat.getInstance();
066        private static NumberFormat sizeFormatter = NumberFormat.getInstance();
067        private static NumberFormat timeFormatter = NumberFormat.getInstance();
068        private static NumberFormat rateFormatter = NumberFormat.getInstance();
069        private static NumberFormat countFormatter = NumberFormat.getInstance();
070        private static NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
071        private static NumberFormat integerFormatter = NumberFormat.getInstance();
072
073        static {
074                integerFormatter.setGroupingUsed(false);
075                integerFormatter.setMaximumFractionDigits(0);
076                integerFormatter.setMinimumFractionDigits(0);
077                sizeFormatter.setGroupingUsed(false);
078                sizeFormatter.setMaximumFractionDigits(1);
079                sizeFormatter.setMinimumFractionDigits(1);
080                largeSizeFormatter.setGroupingUsed(false);
081                largeSizeFormatter.setMaximumFractionDigits(3);
082                largeSizeFormatter.setMinimumFractionDigits(3);
083                timeFormatter.setGroupingUsed(false);
084                timeFormatter.setMaximumFractionDigits(3);
085                timeFormatter.setMinimumFractionDigits(3);
086                rateFormatter.setGroupingUsed(false);
087                rateFormatter.setMaximumFractionDigits(3);
088                rateFormatter.setMinimumFractionDigits(3);
089                countFormatter.setGroupingUsed(true);
090                countFormatter.setMaximumFractionDigits(0);
091                countFormatter.setMinimumFractionDigits(0);
092        }
093
094        public static String getCurrency(double number) {
095                return currencyFormatter.format(number);
096        }
097
098        /**
099         * 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.
100         * 
101         * <pre>
102         *   1  == 1 byte
103         *   1b == 1 byte
104         *   1k == 1 kilobyte == 1024   bytes ==                     1,024 bytes
105         *   1m == 1 megabyte == 1024^2 bytes ==                 1,048,576 bytes
106         *   1g == 1 gigabyte == 1024^3 bytes ==             1,073,741,824 bytes
107         *   1t == 1 terabyte == 1024^4 bytes ==         1,099,511,627,776 bytes
108         *   1p == 1 petabyte == 1024^5 bytes ==     1,125,899,906,842,624 bytes
109         *   1e == 1 exabyte  == 1024^6 bytes == 1,152,921,504,606,846,976 bytes
110         * </pre>
111         */
112        public static long getBytes(String size) {
113                return getBytes(size, SIZE_TOKENS, BASE);
114        }
115
116        public static long getBytes(String size, List<String> tokens, int base) {
117                Assert.notBlank(size);
118                for (int i = 0; i < tokens.size(); i++) {
119                        String token = tokens.get(i);
120                        long multiplier = (long) Math.pow(base, i);
121                        if (StringUtils.endsWithIgnoreCase(size, token)) {
122                                return getByteValue(size, token, multiplier);
123                        }
124                }
125                // Assume bytes
126                return getByteValue(size, "", 1);
127        }
128
129        protected static long getByteValue(String time, String suffix, long multiplier) {
130                int len = StringUtils.length(time);
131                String substring = StringUtils.substring(time, 0, len - suffix.length());
132                Double value = new Double(substring);
133                value = value * multiplier;
134                return value.longValue();
135        }
136
137        /**
138         * 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.
139         * 
140         * <pre>
141         *   1   == 1 millisecond
142         *   1ms == 1 millisecond
143         *   1s  == 1 second ==           1000 milliseconds
144         *   1m  == 1 minute ==         60,000 milliseconds
145         *   1h  == 1 hour   ==      3,600,000 milliseconds 
146         *   1d  == 1 day    ==     86,400,000 milliseconds
147         *   1y  == 1 year   == 31,536,000,000 milliseconds
148         * </pre>
149         */
150        public static long getMillis(String time) {
151                return getMillis(time, TIME_TOKENS, TIME_MULTIPLIERS);
152        }
153
154        /**
155         * 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.
156         * 
157         * <pre>
158         *   1   == 1 millisecond
159         *   1ms == 1 millisecond
160         *   1s  == 1 second ==           1000 milliseconds
161         *   1m  == 1 minute ==         60,000 milliseconds
162         *   1h  == 1 hour   ==      3,600,000 milliseconds 
163         *   1d  == 1 day    ==     86,400,000 milliseconds
164         *   1y  == 1 year   == 31,536,000,000 milliseconds
165         * </pre>
166         */
167        public static int getMillisAsInt(String time) {
168                Long millis = getMillis(time);
169                if (millis <= Integer.MAX_VALUE) {
170                        return millis.intValue();
171                } else {
172                        throw Exceptions.illegalArgument("[%s] converts to [%s]. maximum allowable integer value is [%s]", time, millis, Integer.MAX_VALUE);
173                }
174        }
175
176        public static long getMillis(String time, List<String> tokens, List<Long> multipliers) {
177                Assert.notBlank(time);
178                Assert.isTrue(tokens.size() == multipliers.size());
179                for (int i = 0; i < tokens.size(); i++) {
180                        String token = tokens.get(i);
181                        long multiplier = multipliers.get(i);
182                        if (StringUtils.endsWithIgnoreCase(time, token)) {
183                                return getTimeValue(time, token, multiplier);
184                        }
185                }
186                // Assume milliseconds
187                return getTimeValue(time, "", 1);
188        }
189
190        protected static long getTimeValue(String time, String suffix, long multiplier) {
191                int len = StringUtils.length(time);
192                String substring = StringUtils.substring(time, 0, len - suffix.length());
193                Double value = new Double(substring);
194                value = value * multiplier;
195                return value.longValue();
196        }
197
198        /**
199         * Parse a date from the string. The string must be in the same format returned by the getDate() methods
200         */
201        public static Date parseDate(String date) {
202                try {
203                        synchronized (DATE_FORMATTER) {
204                                return DATE_FORMATTER.parse(date);
205                        }
206                } catch (ParseException e) {
207                        throw new IllegalArgumentException("Can't parse [" + date + "]", e);
208                }
209        }
210
211        /**
212         * Return a formatted date
213         */
214        public static String dateAsString(long millis) {
215                return dateAsString(new Date(millis));
216        }
217
218        /**
219         * Return a formatted date
220         */
221        public static String dateAsString(Date date) {
222                synchronized (JAVA_UTIL_DATE_TO_STRING_FORMATTER) {
223                        return JAVA_UTIL_DATE_TO_STRING_FORMATTER.format(date);
224                }
225        }
226
227        /**
228         * Return a formatted date
229         */
230        public static String dateAsString(long millis, String timezone) {
231                return dateAsString(new Date(millis), timezone);
232        }
233
234        /**
235         * Return a formatted date
236         */
237        public static String dateAsString(Date date, String timezone) {
238                SimpleDateFormat sdf = new SimpleDateFormat(JAVA_UTIL_DATE_TO_STRING_FORMAT);
239                sdf.setTimeZone(TimeZone.getTimeZone(timezone));
240                return sdf.format(date);
241        }
242
243        /**
244         * Return a formatted date
245         */
246        public static String getDate(long millis) {
247                return getDate(new Date(millis));
248        }
249
250        /**
251         * Return a formatted date
252         */
253        public static String getDate(Date date) {
254                synchronized (DATE_FORMATTER) {
255                        return DATE_FORMATTER.format(date);
256                }
257        }
258
259        /**
260         * 
261         */
262        public static String getThroughputInSeconds(long millis, long count, String label) {
263                double seconds = millis / SECOND;
264                double countPerSecond = count / seconds;
265                synchronized (countFormatter) {
266                        return countFormatter.format(countPerSecond) + " " + label;
267                }
268        }
269
270        /**
271         * 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
272         */
273        public static String getRate(long millis, long bytes) {
274                return getRate(millis, bytes, rateFormatter);
275        }
276
277        /**
278         * 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
279         */
280        public static String getRate(long millis, long bytes, NumberFormat rateFormatter) {
281                double seconds = millis / SECOND;
282                double bytesPerSecond = bytes / seconds;
283                Size bandwidthLevel = getSizeEnum(bytesPerSecond);
284                double transferRate = bytesPerSecond / bandwidthLevel.getValue();
285                synchronized (rateFormatter) {
286                        return rateFormatter.format(transferRate) + " " + bandwidthLevel.getRateLabel();
287                }
288        }
289
290        /**
291         * Return a formatted <code>count</code> representing the number of keys in the map
292         */
293        public static String getCount(Map<?, ?> map) {
294                return getCount(map.keySet());
295        }
296
297        /**
298         * Return a formatted <code>count</code> representing the number of elements in the iterable
299         */
300        public static String getCount(Iterable<?> iterable) {
301                return getCount(Iterables.size(iterable));
302        }
303
304        /**
305         * Return a formatted <code>count</code>
306         */
307        public static String getCount(long count) {
308                synchronized (countFormatter) {
309                        return countFormatter.format(count);
310                }
311        }
312
313        /**
314         * Given milliseconds, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
315         * exactly 365 days per year.
316         */
317        public static String getTime(long millis) {
318                return getTime(millis, timeFormatter);
319        }
320
321        /**
322         * Given a stopwatch, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
323         * exactly 365 days per year.
324         */
325        public static String getTime(Stopwatch stopwatch) {
326                return getTime(stopwatch.elapsed(MILLISECONDS));
327        }
328
329        /**
330         * Given milliseconds, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
331         * exactly 365 days per year.
332         */
333        public static String getTime(long millis, NumberFormat formatter) {
334                long abs = Math.abs(millis);
335                synchronized (formatter) {
336                        if (abs < SECOND) {
337                                return millis + "ms";
338                        } else if (abs < MINUTE) {
339                                return formatter.format(millis / SECOND) + "s";
340                        } else if (abs < HOUR) {
341                                return formatter.format(millis / MINUTE) + "m";
342                        } else if (abs < DAY) {
343                                return formatter.format(millis / HOUR) + "h";
344                        } else if (abs < YEAR) {
345                                return formatter.format(millis / DAY) + "d";
346                        } else {
347                                return formatter.format(millis / YEAR) + "y";
348                        }
349                }
350        }
351
352        /**
353         * Given a number of bytes return bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, or exabytes as appropriate.
354         */
355        public static String getSize(long bytes) {
356                return getSize(bytes, (Size) null);
357        }
358
359        /**
360         * Given a number of bytes return bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, or exabytes as appropriate.
361         */
362        public static String getIntegerSize(long bytes) {
363                return getIntegerSize(bytes, null);
364        }
365
366        /**
367         * Given a number of bytes return a string formatted into the unit of measure indicated
368         */
369        public static String getIntegerSize(long bytes, final Size unitOfMeasure) {
370                Size uom = (unitOfMeasure == null) ? getSizeEnum(bytes) : unitOfMeasure;
371                StringBuilder sb = new StringBuilder();
372                synchronized (integerFormatter) {
373                        sb.append(integerFormatter.format(bytes / (double) uom.getValue()));
374                }
375                sb.append(uom.getSizeLabel());
376                return sb.toString();
377        }
378
379        /**
380         * Given a number of bytes return a string formatted into the unit of measure indicated
381         */
382        public static String getSize(long bytes, NumberFormat formatter) {
383                return getSize(bytes, null, formatter);
384        }
385
386        /**
387         * Given a number of bytes return a string formatted into the unit of measure indicated
388         */
389        public static String getSize(long bytes, final Size unitOfMeasure, NumberFormat formatter) {
390                Size uom = (unitOfMeasure == null) ? getSizeEnum(bytes) : unitOfMeasure;
391                StringBuilder sb = new StringBuilder();
392                synchronized (formatter) {
393                        sb.append(formatter.format(bytes / (double) uom.getValue()));
394                }
395                sb.append(uom.getSizeLabel());
396                return sb.toString();
397        }
398
399        /**
400         * Given a number of bytes return a string formatted into the unit of measure indicated
401         */
402        public static String getSize(long bytes, Size unitOfMeasure) {
403                Size uom = (unitOfMeasure == null) ? getSizeEnum(bytes) : unitOfMeasure;
404                StringBuilder sb = new StringBuilder();
405                sb.append(getFormattedSize(bytes, uom));
406                sb.append(uom.getSizeLabel());
407                return sb.toString();
408        }
409
410        public static String getFormattedSize(long bytes, Size size) {
411                switch (size) {
412                case BYTE:
413                        return bytes + "";
414                case KB:
415                case MB:
416                case GB:
417                        synchronized (sizeFormatter) {
418                                return sizeFormatter.format(bytes / (double) size.getValue());
419                        }
420                default:
421                        synchronized (largeSizeFormatter) {
422                                return largeSizeFormatter.format(bytes / (double) size.getValue());
423                        }
424                }
425        }
426
427        public static Size getSizeEnum(double bytes) {
428                bytes = Math.abs(bytes);
429                if (bytes < Size.KB.getValue()) {
430                        return Size.BYTE;
431                } else if (bytes < Size.MB.getValue()) {
432                        return Size.KB;
433                } else if (bytes < Size.GB.getValue()) {
434                        return Size.MB;
435                } else if (bytes < Size.TB.getValue()) {
436                        return Size.GB;
437                } else if (bytes < Size.PB.getValue()) {
438                        return Size.TB;
439                } else if (bytes < Size.EB.getValue()) {
440                        return Size.PB;
441                } else {
442                        return Size.EB;
443                }
444        }
445
446        protected static final List<Long> getTimeMultipliers() {
447                List<Long> m = new ArrayList<Long>();
448                m.add(1L);
449                m.add(new Double(SECOND).longValue());
450                m.add(new Double(MINUTE).longValue());
451                m.add(new Double(HOUR).longValue());
452                m.add(new Double(DAY).longValue());
453                m.add(new Double(YEAR).longValue());
454                return m;
455        }
456}