package er.extensions.formatters; import java.text.FieldPosition; import java.text.NumberFormat; import java.text.ParsePosition; import java.util.concurrent.TimeUnit; /** * User-presentable time duration format as days, hours, minutes and seconds. * * Usage example: * <code> * StopWatch w = new StopWatch(); * w.start(); * ... perform long task ... * w.stop(); * ERXTimeDurationFormatter f = new ERXTimeDurationFormatter(TimeUnit.MILLISECONDS); * * String message = "The task took " + f.format(w.getTime()); * </code> * * @author kieran * */ public class ERXTimeDurationFormatter extends NumberFormat { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; private final TimeUnit _timeUnit; private final boolean _showLargestUnitOnly; private final boolean _omitSecondsPart; /** * Defaults to TimeUnit.SECONDS, showing all time units and showing seconds part of the time description. */ public ERXTimeDurationFormatter() { this(TimeUnit.SECONDS, false, false); } /** * Defaults to showing all time units and showing seconds part of the time description. * * @param timeUnit the unit of time which is milliseconds, seconds, etc. */ public ERXTimeDurationFormatter(TimeUnit timeUnit) { this(timeUnit, false, false); } /** * @param timeUnit the unit of time which is milliseconds, seconds, etc. * @param showLargestUnitOnly display the largest time unit (days, hours, minutes or seconds) that the time value rounds down to * @param omitSecondsPart imit the seconds unit from the format. */ public ERXTimeDurationFormatter(TimeUnit timeUnit, boolean showLargestUnitOnly, boolean omitSecondsPart) { super(); _timeUnit = timeUnit; _showLargestUnitOnly = showLargestUnitOnly; _omitSecondsPart = omitSecondsPart; } @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { return format((long)number, toAppendTo, pos); } @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition fieldPosition) { long seconds = TimeUnit.SECONDS.convert(number, _timeUnit); return toAppendTo.append(timePeriodDescription(seconds, _showLargestUnitOnly, _omitSecondsPart)); } /** * @param secondsValue * @param resultIfNull * @param showLargestUnitOnly rounded to the nearest hour, minute of second as appropriate * @param omitSecondsPart when the value is greater than one minute * @return a user friendly description of a time duration seconds value */ private String timePeriodDescription(long value, boolean showLargestUnitOnly, boolean omitSecondsPart) { boolean shouldStopAddingComponents = false; if (value == 0) { StringBuilder b = new StringBuilder(); b.append("less than 1 "); if (omitSecondsPart) { b.append("minute"); } else { b.append("second"); } //~ if (omitSecondsPart) return b.toString(); } boolean isNegative = value < 0L; if (isNegative) { value = -value; } //~ if (isNegative) long secondsPart = value % 60L; // Convert value to remaining minutes value = (value - secondsPart) / 60L; long minutesPart = value % 60L; // Convert value to remaining hours value = (value - minutesPart) / 60L; long hoursPart = value % 24L; // Convert value to remaining days value = (value - hoursPart) / 24L; StringBuilder b = new StringBuilder(); if (value > 0) { b.append(value); if (value > 1) { b.append(" days"); } else { b.append(" day"); } //~ if (value > 1) if (showLargestUnitOnly) shouldStopAddingComponents = true; } if (hoursPart > 0 && !shouldStopAddingComponents) { if (b.length() > 0) { b.append(", "); } b.append(hoursPart); if (hoursPart > 1) { b.append(" hours"); } else { b.append(" hour"); } //~ if (hoursPart > 1) if (showLargestUnitOnly) shouldStopAddingComponents = true; } if (minutesPart > 0 && !shouldStopAddingComponents) { if (b.length() > 0) { b.append(", "); } b.append(minutesPart); if (minutesPart > 1) { b.append(" minutes"); } else { b.append(" minute"); } //~ if (minutesPart > 1) if (showLargestUnitOnly) shouldStopAddingComponents = true; } if (secondsPart > 0 && !shouldStopAddingComponents && !omitSecondsPart) { if (b.length() > 0) { b.append(", "); } b.append(secondsPart); if (secondsPart > 1) { b.append(" seconds"); } else { b.append(" second"); } //~ if (secondsPart > 1) if (showLargestUnitOnly) shouldStopAddingComponents = true; } if (isNegative) { b.insert(0, "- "); } //~ if (isNegative) return b.toString(); } /** * I know it is lame, but parsing is unsupported. * * @see java.text.NumberFormat#parse(java.lang.String, java.text.ParsePosition) */ @Override public Number parse(String source, ParsePosition parsePosition) { throw new UnsupportedOperationException("This class does not support parsing."); } }