From: Travis Keep Date: Wed, 24 Jul 2013 23:07:31 +0000 (+0000) Subject: ICU-10274 Add compound duration formatting for JAVA. X-Git-Tag: milestone-59-0-1~2736 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4b1b47ddb0c8d908bdd01e8dd9c758036ae812a7;p=icu ICU-10274 Add compound duration formatting for JAVA. X-SVN-Rev: 33980 --- diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java index ed31e418e4f..fa780b1d3f5 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/ListFormatter.java @@ -13,6 +13,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; +import java.util.MissingResourceException; + +import javax.management.StandardEmitterMBean; import com.ibm.icu.impl.ICUCache; import com.ibm.icu.impl.ICUResourceBundle; @@ -34,6 +37,43 @@ final public class ListFormatter { private final String middle; private final String end; private final ULocale locale; + + /** + * Indicates the style of Listformatter + * @deprecated internal use only. + * @internal + */ + public enum Style { + /** + * Standard style. + * @deprecated + * @internal + */ + STANDARD("standard"), + /** + * Style for full durations + * @deprecated + * @internal + */ + DURATION("duration"), + /** + * Style for durations in abbrevated form + * @deprecated + * @internal + */ + DURATION_SHORT("duration-short"); + + private final String name; + + Style(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + } /** * Internal: Create a ListFormatter from component strings, @@ -75,7 +115,7 @@ final public class ListFormatter { * @provisional This API might change or be removed in a future release. */ public static ListFormatter getInstance(ULocale locale) { - return cache.get(locale); + return getInstance(locale, Style.STANDARD); } /** @@ -88,7 +128,20 @@ final public class ListFormatter { * @provisional This API might change or be removed in a future release. */ public static ListFormatter getInstance(Locale locale) { - return getInstance(ULocale.forLocale(locale)); + return getInstance(ULocale.forLocale(locale), Style.STANDARD); + } + + /** + * Create a list formatter that is appropriate for a locale and style. + * + * @param locale the locale in question. + * @param style the style + * @return ListFormatter + * @deprecated Internal use only. + * @internal + */ + public static ListFormatter getInstance(ULocale locale, Style style) { + return cache.get(locale, style.getName()); } /** @@ -199,28 +252,39 @@ final public class ListFormatter { } private static class Cache { - private final ICUCache cache = - new SimpleCache(); + private final ICUCache cache = + new SimpleCache(); - public ListFormatter get(ULocale locale) { - ListFormatter result = cache.get(locale); + public ListFormatter get(ULocale locale, String style) { + String key = String.format("%s:%s", locale.toString(), style); + ListFormatter result = cache.get(key); if (result == null) { - result = load(locale); - cache.put(locale, result); + result = load(locale, style); + cache.put(key, result); } return result; } - private static ListFormatter load(ULocale ulocale) { + private static ListFormatter load(ULocale ulocale, String style) { ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale); - r = r.getWithFallback("listPattern/standard"); - return new ListFormatter( - r.getWithFallback("2").getString(), - r.getWithFallback("start").getString(), - r.getWithFallback("middle").getString(), - r.getWithFallback("end").getString(), - ulocale); + // TODO(Travis Keep): This try-catch is a hack to cover missing aliases + // for listPattern/duration and listPattern/duration-narrow in root.txt. + try { + return new ListFormatter( + r.getWithFallback("listPattern/" + style + "/2").getString(), + r.getWithFallback("listPattern/" + style + "/start").getString(), + r.getWithFallback("listPattern/" + style + "/middle").getString(), + r.getWithFallback("listPattern/" + style + "/end").getString(), + ulocale); + } catch (MissingResourceException e) { + return new ListFormatter( + r.getWithFallback("listPattern/standard/2").getString(), + r.getWithFallback("listPattern/standard/start").getString(), + r.getWithFallback("listPattern/standard/middle").getString(), + r.getWithFallback("listPattern/standard/end").getString(), + ulocale); + } } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java index 62c7e2011d9..0620b568fba 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java @@ -6,8 +6,10 @@ */ package com.ibm.icu.text; +import java.text.AttributedCharacterIterator; import java.text.FieldPosition; import java.text.ParsePosition; +import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -20,6 +22,7 @@ import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.util.TimePeriod; import com.ibm.icu.util.TimeUnit; import com.ibm.icu.util.TimeUnitAmount; +import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale.Category; import com.ibm.icu.util.UResourceBundle; @@ -96,9 +99,9 @@ public class TimeUnitFormat extends MeasureFormat { private transient Map> timeUnitToCountToPatterns; private transient PluralRules pluralRules; private transient ListFormatter listFormatter; - private transient MessageFormat hourMinute; - private transient MessageFormat minuteSecond; - private transient MessageFormat hourMinuteSecond; + private transient DateFormat hourMinute; + private transient DateFormat minuteSecond; + private transient DateFormat hourMinuteSecond; private transient boolean isReady; private int style; @@ -132,8 +135,7 @@ public class TimeUnitFormat extends MeasureFormat { } /** - * Create TimeUnitFormat given a ULocale and a formatting style: full or - * abbreviated. + * Create TimeUnitFormat given a ULocale and a formatting style. * @param locale locale of this time unit formatter. * @param style format style, either FULL_NAME or ABBREVIATED_NAME style. * @throws IllegalArgumentException if the style is not FULL_NAME or @@ -150,8 +152,7 @@ public class TimeUnitFormat extends MeasureFormat { } /** - * Create TimeUnitFormat given a Locale and a formatting style: full or - * abbreviated. + * Create TimeUnitFormat given a Locale and a formatting style. * @stable ICU 4.2 */ public TimeUnitFormat(Locale locale, int style) { @@ -226,12 +227,18 @@ public class TimeUnitFormat extends MeasureFormat { */ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { - if ( !(obj instanceof TimeUnitAmount) ) { - throw new IllegalArgumentException("can not format non TimeUnitAmount object"); + if ( !(obj instanceof TimeUnitAmount) && !(obj instanceof TimePeriod)) { + throw new IllegalArgumentException( + "can only format TimeUnitAmount or TimePeriod objects"); } if (!isReady) { setup(); } + if (obj instanceof TimePeriod) { + // TODO: set FieldPosition, see ICU tickets 10156 and 10157. + toAppendTo.append(formatTimePeriod((TimePeriod) obj)); + return toAppendTo; + } TimeUnitAmount amount = (TimeUnitAmount) obj; Map countToPattern = timeUnitToCountToPatterns.get(amount.getTimeUnit()); double number = amount.getNumber().doubleValue(); @@ -242,13 +249,7 @@ public class TimeUnitFormat extends MeasureFormat { return pattern.format(new Object[]{amount.getNumber()}, toAppendTo, pos); } - /** - * Formats a TimePeriod. Currently there is no way to parse a formatted TimePeriod. - * @param timePeriod the TimePeriod to format. - * @return the formatted string. - * @draft ICU 52 - */ - public String formatTimePeriod(TimePeriod timePeriod) { + private String formatTimePeriod(TimePeriod timePeriod) { if (!isReady) { setup(); } @@ -258,7 +259,7 @@ public class TimeUnitFormat extends MeasureFormat { return result; } } - String[] items = new String[timePeriod.size()]; + String[] items = new String[timePeriod.length()]; int idx = 0; for (TimeUnitAmount amount : timePeriod) { items[idx++] = format(amount); @@ -267,7 +268,12 @@ public class TimeUnitFormat extends MeasureFormat { } /** - * Parse a TimeUnitAmount. + * Parse a TimeUnitAmount. Parsing TimePeriod objects is not supported. + * If parseObject is called on a formatted TimePeriod string, it try to parse it + * as a TimeUnitAmount. For example, + * parseObject("5 hours and 34 minutes", pos) + * returns a TimeUnitAmount representing 5 hours and updates pos to point to the + * space after the s in hours. * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) * @stable ICU 4.0 */ @@ -373,75 +379,112 @@ public class TimeUnitFormat extends MeasureFormat { format = NumberFormat.getNumberInstance(locale); } pluralRules = PluralRules.forLocale(locale); - listFormatter = ListFormatter.getInstance(locale); - DateTimePatternGenerator df = DateTimePatternGenerator.getInstance(locale); - hourMinute = getPattern(df, "hm", locale, "{0}", "{1,number,00.###}", null); - minuteSecond = getPattern(df, "ms", locale, null, "{1}", "{2,number,00.###}"); - hourMinuteSecond = getPattern(df, "hms", locale, "{0}", "{1,number,00}", "{2,number,00.###}"); + if (style == FULL_NAME) { + listFormatter = ListFormatter.getInstance(locale, ListFormatter.Style.DURATION); + } else { + listFormatter = ListFormatter.getInstance(locale, ListFormatter.Style.DURATION_SHORT); + } + hourMinute = loadNumericDurationFormat(locale, "hm"); + minuteSecond = loadNumericDurationFormat(locale, "ms"); + hourMinuteSecond = loadNumericDurationFormat(locale, "hms"); timeUnitToCountToPatterns = new HashMap>(); - Set pluralKeywords = pluralRules.getKeywords(); setup("units/duration", timeUnitToCountToPatterns, FULL_NAME, pluralKeywords); setup("unitsShort/duration", timeUnitToCountToPatterns, ABBREVIATED_NAME, pluralKeywords); isReady = true; } - private MessageFormat getPattern(DateTimePatternGenerator dtpg, String skeleton, ULocale locale, - String h, String m, String s) { - String pat = dtpg.getBestPattern(skeleton); - StringBuilder buffer = new StringBuilder(); - for (Object item : new DateTimePatternGenerator.FormatParser().set(pat).getItems()) { - if (item instanceof DateTimePatternGenerator.VariableField) { - DateTimePatternGenerator.VariableField fld = (DateTimePatternGenerator.VariableField)item; - switch (fld.getType()) { - case DateTimePatternGenerator.HOUR: buffer.append(h); break; - case DateTimePatternGenerator.MINUTE: buffer.append(m); break; - case DateTimePatternGenerator.SECOND: buffer.append(s); break; - } - } else { - buffer.append(item); - } - } - return new MessageFormat(buffer.toString(), locale); + // type is one of "hm", "ms" or "hms" + private static DateFormat loadNumericDurationFormat(ULocale ulocale, String type) { + ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. + getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale); + r = r.getWithFallback(String.format("durationUnits/%s", type)); + // We replace 'h' with 'H' because 'h' does not make sense in the context of durations. + DateFormat result = new SimpleDateFormat(r.getString().replace("h", "H")); + result.setTimeZone(TimeZone.GMT_ZONE); + return result; } private String formatPeriodAsNumeric(TimePeriod timePeriod) { TimeUnit biggestUnit = null, smallestUnit = null; + Number smallestUnitAmount = null; for (TimeUnitAmount tua : timePeriod) { if (biggestUnit == null) { biggestUnit = tua.getTimeUnit(); } smallestUnit = tua.getTimeUnit(); + smallestUnitAmount = tua.getNumber(); } - // We have to trim the result of MessageFormat.format() not sure why. + long millis = (long) (((getAmountOrZero(timePeriod, TimeUnit.HOUR) * 60.0 + + getAmountOrZero(timePeriod, TimeUnit.MINUTE)) * 60.0 + + getAmountOrZero(timePeriod, TimeUnit.SECOND)) * 1000.0); + Date d = new Date(millis); if (biggestUnit == TimeUnit.HOUR && smallestUnit == TimeUnit.SECOND) { - return hourMinuteSecond.format(new Object[]{ - getZeroedAmount(timePeriod, TimeUnit.HOUR), - getZeroedAmount(timePeriod, TimeUnit.MINUTE), - getZeroedAmount(timePeriod, TimeUnit.SECOND)}).trim(); - + return numericFormat( + d, hourMinuteSecond, DateFormat.Field.SECOND, smallestUnitAmount); } if (biggestUnit == TimeUnit.MINUTE && smallestUnit == TimeUnit.SECOND) { - return minuteSecond.format(new Object[]{ - null, - getZeroedAmount(timePeriod, TimeUnit.MINUTE), - getZeroedAmount(timePeriod, TimeUnit.SECOND)}).trim(); - + return numericFormat( + d, minuteSecond, DateFormat.Field.SECOND, smallestUnitAmount); } if (biggestUnit == TimeUnit.HOUR && smallestUnit == TimeUnit.MINUTE) { - return hourMinute.format(new Object[]{ - getZeroedAmount(timePeriod, TimeUnit.HOUR), - getZeroedAmount(timePeriod, TimeUnit.MINUTE)}).trim(); + return numericFormat(d, hourMinute, DateFormat.Field.MINUTE, smallestUnitAmount); } return null; } - - private Number getZeroedAmount(TimePeriod timePeriod, TimeUnit timeUnit) { + + /** + * numericFormat allows us to show fractional durations using numeric + * style e.g 12:34:56.7. This function is necessary because there is no way to express + * fractions of durations other than seconds with current DateFormat objects. + * + * After formatting the duration using a DateFormat object in the usual way, it + * replaces the smallest field in the formatted string with the exact fractional + * amount of that smallest field formatted with this object's NumberFormat object. + * + * @param duration The duration to format in milliseconds. The loss of precision here + * is ok because we also pass in the exact amount of the smallest field. + * @param formatter formats the date. + * @param smallestField the smallest defined field in duration to be formatted. + * @param smallestAmount the exact fractional value of the smallest amount. + * @return duration formatted numeric style. + */ + private String numericFormat( + Date duration, + DateFormat formatter, + DateFormat.Field smallestField, + Number smallestAmount) { + // Format the smallest amount ahead of time. + String smallestAmountFormatted = format.format(smallestAmount); + + // Format the duration using the provided DateFormat object. The smallest + // field in this result will be missing the fractional part. + AttributedCharacterIterator iterator = formatter.formatToCharacterIterator(duration); + + // The final formatted duration will be written here. + StringBuilder builder = new StringBuilder(); + + // iterate through formatted text copying to 'builder' one character at a time. + // When we get to the smallest amount, skip over it and copy + // 'smallestAmountFormatted' to the builder instead. + for (iterator.first(); iterator.getIndex() < iterator.getEndIndex();) { + if (iterator.getAttributes().containsKey(smallestField)) { + builder.append(smallestAmountFormatted); + iterator.setIndex(iterator.getRunLimit(smallestField)); + } else { + builder.append(iterator.current()); + iterator.next(); + } + } + return builder.toString(); + } + + private static double getAmountOrZero(TimePeriod timePeriod, TimeUnit timeUnit) { TimeUnitAmount tua = timePeriod.getAmount(timeUnit); if (tua == null) { - return Double.valueOf(0); + return 0.0; } - return tua.getNumber(); + return tua.getNumber().doubleValue(); } private void setup(String resourceKey, Map> timeUnitToCountToPatterns, diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/TimePeriod.java b/icu4j/main/classes/core/src/com/ibm/icu/util/TimePeriod.java index a33df30d1ea..d14b571f2ac 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/TimePeriod.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/TimePeriod.java @@ -26,68 +26,40 @@ import java.util.NoSuchElementException; public final class TimePeriod implements Iterable { private final TimeUnitAmount[] fields; - private final int size; + private final int length; private final int hash; - - private TimePeriod(TimeUnitAmount[] fields, int size, int hash) { - this.fields = fields; - this.size = size; - this.hash = hash; - } /** - * Returns a new TimePeriod that matches the given time unit amounts. - * @param amounts the TimeUnitAmounts. Must be non-empty. Normalization of the - * amounts and inclusion/exclusion of 0 amounts is up to caller. The Number - * in each TimeUnitAmount must either be a Byte, Short, Integer, Long, Float, - * Double, BigInteger, or BigDecimal or it must implement Cloneable and have - * a public clone method. - * @return the new TimePeriod object - * @throws IllegalArgumentException if multiple TimeUnitAmount objects match - * the same time unit or if any but the smallest TimeUnit has a fractional value - * Or if amounts is empty. - * @draft ICU 52 - * @provisional This API might change or be removed in a future release. - */ - public static TimePeriod forAmounts(TimeUnitAmount ...amounts) { - return forAmounts(Arrays.asList(amounts)); - } - - /** - * Returns a new TimePeriod that matches the given time unit amounts. + * Constructor. * @param amounts the TimeUnitAmounts. Must be non-empty. Normalization of the * amounts and inclusion/exclusion of 0 amounts is up to caller. The Number * object in each TimeUnitAmount must not change. Otherwise the created * TimePeriod object may not work as expected. - * @return the new TimePeriod object * @throws IllegalArgumentException if multiple TimeUnitAmount objects match * the same time unit or if any but the smallest TimeUnit has a fractional value * Or if amounts is empty. * @draft ICU 52 * @provisional This API might change or be removed in a future release. */ - public static TimePeriod forAmounts(Iterable amounts) { - TimeUnitAmount[] fields = new TimeUnitAmount[TimeUnit.TIME_UNIT_COUNT]; - int size = 0; + public TimePeriod(TimeUnitAmount ...amounts) { + fields = new TimeUnitAmount[TimeUnit.TIME_UNIT_COUNT]; + int tempSize = 0; for (TimeUnitAmount tua : amounts) { int index = tua.getTimeUnit().getIndex(); if (fields[index] != null) { throw new IllegalArgumentException( "Only one TimeUnitAmount per unit allowed."); } - // This line is necessary to guarantee immutability of the TimePeriod - // class. A Number object, which is in TimeUnitAmount, need not be immutable, - // but Double is immutable. fields[index] = tua; - size++; + tempSize++; } - if (size == 0) { + length = tempSize; + if (length == 0) { throw new IllegalArgumentException( "There must be at least one TimeUnitAmount."); } - TimePeriod result = new TimePeriod(fields, size, computeHash(fields)); boolean fractionalFieldEncountered = false; - for (TimeUnitAmount tua : result) { + for (TimeUnitAmount tua : this) { if (fractionalFieldEncountered) { throw new IllegalArgumentException( "Only the smallest time unit can have a fractional amount."); @@ -97,7 +69,7 @@ public final class TimePeriod implements Iterable { fractionalFieldEncountered = true; } } - return result; + hash = computeHash(fields); } /** @@ -126,11 +98,10 @@ public final class TimePeriod implements Iterable { /** * Returns the number of TimeUnitAmount objects in this object. - * @internal - * @deprecated This API is ICU internal only. + * @draft ICU 52 */ - public int size() { - return size; + public int length() { + return length; } /** diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeUnitTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeUnitTest.java index 9f8b637f5f9..41f4e6a1090 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeUnitTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeUnitTest.java @@ -23,26 +23,39 @@ import com.ibm.icu.util.ULocale; * */ public class TimeUnitTest extends TestFmwk { - private static final TimePeriod _19m = TimePeriod.forAmounts( + private static final TimePeriod _19m = new TimePeriod( new TimeUnitAmount(19.0, TimeUnit.MINUTE)); - private static final TimePeriod _19m_28s = TimePeriod.forAmounts( + private static final TimePeriod _19m_28s = new TimePeriod( new TimeUnitAmount(19.0, TimeUnit.MINUTE), new TimeUnitAmount(28.0, TimeUnit.SECOND)); - private static final TimePeriod _1h_23_5s = TimePeriod.forAmounts( + private static final TimePeriod _1h_23_5s = new TimePeriod( new TimeUnitAmount(1.0, TimeUnit.HOUR), new TimeUnitAmount(23.5, TimeUnit.SECOND)); - private static final TimePeriod _1h_0m_23s = TimePeriod.forAmounts( + private static final TimePeriod _1h_23_5m = new TimePeriod( + new TimeUnitAmount(1.0, TimeUnit.HOUR), + new TimeUnitAmount(23.5, TimeUnit.MINUTE)); + private static final TimePeriod _1h_0m_23s = new TimePeriod( new TimeUnitAmount(1.0, TimeUnit.HOUR), new TimeUnitAmount(0.0, TimeUnit.MINUTE), new TimeUnitAmount(23.0, TimeUnit.SECOND)); - private static final TimePeriod _5h_17m = TimePeriod.forAmounts( + private static final TimePeriod _5h_17m = new TimePeriod( new TimeUnitAmount(5.0, TimeUnit.HOUR), new TimeUnitAmount(17.0, TimeUnit.MINUTE)); - private static final TimePeriod _2y_5M_3w_4d = TimePeriod.forAmounts( + private static final TimePeriod _2y_5M_3w_4d = new TimePeriod( new TimeUnitAmount(2.0, TimeUnit.YEAR), new TimeUnitAmount(5.0, TimeUnit.MONTH), new TimeUnitAmount(3.0, TimeUnit.WEEK), new TimeUnitAmount(4.0, TimeUnit.DAY)); + private static final TimePeriod _0h_0m_17s = new TimePeriod( + new TimeUnitAmount(0.0, TimeUnit.HOUR), + new TimeUnitAmount(0.0, TimeUnit.MINUTE), + new TimeUnitAmount(17.0, TimeUnit.SECOND)); + private static final TimePeriod _6h_56_92m = new TimePeriod( + new TimeUnitAmount(6.0, TimeUnit.HOUR), + new TimeUnitAmount(56.92, TimeUnit.MINUTE)); + private static final TimePeriod _1m_59_9996s = new TimePeriod( + new TimeUnitAmount(1.0, TimeUnit.MINUTE), + new TimeUnitAmount(59.9996, TimeUnit.SECOND)); public static void main(String[] args) throws Exception{ new TimeUnitTest().run(args); @@ -358,33 +371,52 @@ public class TimeUnitTest extends TestFmwk { public void TestFormatPeriodEn() { Object[][] fullData = { + {_1m_59_9996s, "1 minute, 59.9996 seconds"}, {_19m, "19 minutes"}, - {_1h_23_5s, "1 hour and 23.5 seconds"}, - {_1h_0m_23s, "1 hour, 0 minutes, and 23 seconds"}, - {_2y_5M_3w_4d, "2 years, 5 months, 3 weeks, and 4 days"}}; + {_1h_23_5s, "1 hour, 23.5 seconds"}, + {_1h_23_5m, "1 hour, 23.5 minutes"}, + {_1h_0m_23s, "1 hour, 0 minutes, 23 seconds"}, + {_2y_5M_3w_4d, "2 years, 5 months, 3 weeks, 4 days"}}; Object[][] abbrevData = { + {_1m_59_9996s, "1 min, 59.9996 secs"}, {_19m, "19 mins"}, - {_1h_23_5s, "1 hr and 23.5 secs"}, - {_1h_0m_23s, "1 hr, 0 mins, and 23 secs"}, - {_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, and 4 days"}}; + {_1h_23_5s, "1 hr, 23.5 secs"}, + {_1h_23_5m, "1 hr, 23.5 mins"}, + {_1h_0m_23s, "1 hr, 0 mins, 23 secs"}, + {_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, 4 days"}}; Object[][] numericData = { + {_1m_59_9996s, "1:59.9996"}, {_19m, "19 mins"}, {_1h_23_5s, "1:00:23.5"}, {_1h_0m_23s, "1:00:23"}, + {_1h_23_5m, "1:23.5"}, {_5h_17m, "5:17"}, {_19m_28s, "19:28"}, - {_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, and 4 days"}}; + {_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, 4 days"}, + {_0h_0m_17s, "0:00:17"}, + {_6h_56_92m, "6:56.92"}}; TimeUnitFormat tuf = new TimeUnitFormat(ULocale.ENGLISH, TimeUnitFormat.FULL_NAME); + NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH); + nf.setMaximumFractionDigits(4); + tuf.setNumberFormat(nf); verifyFormatPeriod("en FULL", tuf, fullData); tuf = new TimeUnitFormat(ULocale.ENGLISH, TimeUnitFormat.ABBREVIATED_NAME); + tuf.setNumberFormat(nf); verifyFormatPeriod("en ABBREV", tuf, abbrevData); tuf = new TimeUnitFormat(ULocale.ENGLISH, TimeUnitFormat.NUMERIC); + tuf.setNumberFormat(nf); verifyFormatPeriod("en NUMERIC", tuf, numericData); } + public void TestTimePeriodLength() { + assertEquals("length", 2, new TimePeriod( + new TimeUnitAmount(3.0, TimeUnit.HOUR), + new TimeUnitAmount(5.0, TimeUnit.MINUTE)).length()); + } + public void TestTimePeriodForAmounts() { try { - TimePeriod.forAmounts( + new TimePeriod( new TimeUnitAmount(3.0, TimeUnit.HOUR), new TimeUnitAmount(5.0, TimeUnit.HOUR)); errln("Expected IllegalArgumentException on duplicate TimeUnits."); @@ -392,13 +424,13 @@ public class TimeUnitTest extends TestFmwk { // expected } try { - TimePeriod.forAmounts(); + new TimePeriod(); errln("Expected IllegalArgumentException on missing TimeUnitAmounts."); } catch (IllegalArgumentException e) { // expected } try { - TimePeriod.forAmounts( + new TimePeriod( new TimeUnitAmount(3.5, TimeUnit.HOUR), new TimeUnitAmount(5.0, TimeUnit.MINUTE)); errln("Expected IllegalArgumentException. Only smallest time unit can have a fractional amount."); @@ -408,12 +440,12 @@ public class TimeUnitTest extends TestFmwk { } public void TestTimePeriodEqualsHashCode() { - TimePeriod our_19m_28s = TimePeriod.forAmounts( + TimePeriod our_19m_28s = new TimePeriod( new TimeUnitAmount(28.0, TimeUnit.SECOND), new TimeUnitAmount(19.0, TimeUnit.MINUTE)); assertEquals("TimePeriod equals", _19m_28s, our_19m_28s); assertEquals("Hash code", _19m_28s.hashCode(), our_19m_28s.hashCode()); - TimePeriod our_19m_29s = TimePeriod.forAmounts( + TimePeriod our_19m_29s = new TimePeriod( new TimeUnitAmount(29.0, TimeUnit.SECOND), new TimeUnitAmount(19.0, TimeUnit.MINUTE)); assertNotEquals("TimePeriod not equals", _19m_28s, our_19m_29s); @@ -427,7 +459,7 @@ public class TimeUnitTest extends TestFmwk { StringBuilder builder = new StringBuilder(); boolean failure = false; for (Object[] testCase : testData) { - String actual = tuf.formatTimePeriod((TimePeriod) testCase[0]); + String actual = tuf.format(testCase[0]); if (!testCase[1].equals(actual)) { builder.append(String.format("%s: Expected: '%s', got: '%s'\n", desc, testCase[1], actual)); failure = true;