All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.text.MeasureFormat Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
 **********************************************************************
 * Copyright (c) 2004-2016, International Business Machines
 * Corporation and others.  All Rights Reserved.
 **********************************************************************
 * Author: Alan Liu
 * Created: April 20, 2004
 * Since: ICU 3.0
 **********************************************************************
 */
package com.ibm.icu.text;

import java.io.Externalizable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamException;
import java.math.RoundingMode;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.concurrent.ConcurrentHashMap;

import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.number.LongNameHandler;
import com.ibm.icu.number.FormattedNumber;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.number.Rounder;
import com.ibm.icu.text.ListFormatter.FormattedListBuilder;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ICUUncheckedIOException;
import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
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;

// If you update the examples in the doc, don't forget to update MesaureUnitTest.TestExamplesInDocs too.
/**
 * A formatter for Measure objects.
 *
 * 

* IMPORTANT: New users are strongly encouraged to see if * {@link NumberFormatter} fits their use case. Although not deprecated, this * class, MeasureFormat, is provided for backwards compatibility only. *


* *

* To format a Measure object, first create a formatter object using a MeasureFormat factory method. Then * use that object's format or formatMeasures methods. * * Here is sample code: * *

 * MeasureFormat fmtFr = MeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.SHORT);
 * Measure measure = new Measure(23, MeasureUnit.CELSIUS);
 *
 * // Output: 23 °C
 * System.out.println(fmtFr.format(measure));
 *
 * Measure measureF = new Measure(70, MeasureUnit.FAHRENHEIT);
 *
 * // Output: 70 °F
 * System.out.println(fmtFr.format(measureF));
 *
 * MeasureFormat fmtFrFull = MeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.WIDE);
 * // Output: 70 pieds et 5,3 pouces
 * System.out.println(fmtFrFull.formatMeasures(new Measure(70, MeasureUnit.FOOT),
 *         new Measure(5.3, MeasureUnit.INCH)));
 *
 * // Output: 1 pied et 1 pouce
 * System.out.println(
 *         fmtFrFull.formatMeasures(new Measure(1, MeasureUnit.FOOT), new Measure(1, MeasureUnit.INCH)));
 *
 * MeasureFormat fmtFrNarrow = MeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.NARROW);
 * // Output: 1′ 1″
 * System.out.println(fmtFrNarrow.formatMeasures(new Measure(1, MeasureUnit.FOOT),
 *         new Measure(1, MeasureUnit.INCH)));
 *
 * MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
 *
 * // Output: 1 inch, 2 feet
 * fmtEn.formatMeasures(new Measure(1, MeasureUnit.INCH), new Measure(2, MeasureUnit.FOOT));
 * 
*

* This class does not do conversions from one unit to another. It simply formats whatever units it is * given *

* This class is immutable and thread-safe so long as its deprecated subclass, TimeUnitFormat, is never * used. TimeUnitFormat is not thread-safe, and is mutable. Although this class has existing subclasses, * this class does not support new sub-classes. * * @see com.ibm.icu.text.UFormat * @author Alan Liu * @stable ICU 3.0 */ public class MeasureFormat extends UFormat { // Generated by serialver from JDK 1.4.1_01 static final long serialVersionUID = -7182021401701778240L; private final transient FormatWidth formatWidth; // PluralRules is documented as being immutable which implies thread-safety. private final transient PluralRules rules; private final transient NumericFormatters numericFormatters; private final transient NumberFormat numberFormat; private final transient LocalizedNumberFormatter numberFormatter; private static final SimpleCache localeToNumericDurationFormatters = new SimpleCache(); private static final Map hmsTo012 = new HashMap(); static { hmsTo012.put(MeasureUnit.HOUR, 0); hmsTo012.put(MeasureUnit.MINUTE, 1); hmsTo012.put(MeasureUnit.SECOND, 2); } // For serialization: sub-class types. private static final int MEASURE_FORMAT = 0; private static final int TIME_UNIT_FORMAT = 1; private static final int CURRENCY_FORMAT = 2; /** * Formatting width enum. * * @stable ICU 53 */ // Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum // when adding an enum value. public enum FormatWidth { /** * Spell out everything. * * @stable ICU 53 */ WIDE(ListFormatter.Style.DURATION, UnitWidth.FULL_NAME, UnitWidth.FULL_NAME), /** * Abbreviate when possible. * * @stable ICU 53 */ SHORT(ListFormatter.Style.DURATION_SHORT, UnitWidth.SHORT, UnitWidth.ISO_CODE), /** * Brief. Use only a symbol for the unit when possible. * * @stable ICU 53 */ NARROW(ListFormatter.Style.DURATION_NARROW, UnitWidth.NARROW, UnitWidth.SHORT), /** * Identical to NARROW except when formatMeasures is called with an hour and minute; minute and * second; or hour, minute, and second Measures. In these cases formatMeasures formats as 5:37:23 * instead of 5h, 37m, 23s. * * @stable ICU 53 */ NUMERIC(ListFormatter.Style.DURATION_NARROW, UnitWidth.NARROW, UnitWidth.SHORT), /** * The default format width for getCurrencyFormat(), which is to show the symbol for currency * (UnitWidth.SHORT) but wide for other units. * * @internal Use {@link #getCurrencyFormat()} * @deprecated ICU 61 This API is ICU internal only. */ @Deprecated DEFAULT_CURRENCY(ListFormatter.Style.DURATION, UnitWidth.FULL_NAME, UnitWidth.SHORT); private final ListFormatter.Style listFormatterStyle; /** * The {@link UnitWidth} (used for newer NumberFormatter API) that corresponds to this * FormatWidth (used for the older APIs) for all units except currencies. */ final UnitWidth unitWidth; /** * The {@link UnitWidth} (used for newer NumberFormatter API) that corresponds to this * FormatWidth (used for the older APIs) for currencies. */ final UnitWidth currencyWidth; private FormatWidth(ListFormatter.Style style, UnitWidth unitWidth, UnitWidth currencyWidth) { this.listFormatterStyle = style; this.unitWidth = unitWidth; this.currencyWidth = currencyWidth; } ListFormatter.Style getListFormatterStyle() { return listFormatterStyle; } } /** * Create a format from the locale, formatWidth, and format. * * @param locale * the locale. * @param formatWidth * hints how long formatted strings should be. * @return The new MeasureFormat object. * @stable ICU 53 */ public static MeasureFormat getInstance(ULocale locale, FormatWidth formatWidth) { return getInstance(locale, formatWidth, NumberFormat.getInstance(locale)); } /** * Create a format from the {@link java.util.Locale} and formatWidth. * * @param locale * the {@link java.util.Locale}. * @param formatWidth * hints how long formatted strings should be. * @return The new MeasureFormat object. * @stable ICU 54 */ public static MeasureFormat getInstance(Locale locale, FormatWidth formatWidth) { return getInstance(ULocale.forLocale(locale), formatWidth); } /** * Create a format from the locale, formatWidth, and format. * * @param locale * the locale. * @param formatWidth * hints how long formatted strings should be. * @param format * This is defensively copied. * @return The new MeasureFormat object. * @stable ICU 53 */ public static MeasureFormat getInstance( ULocale locale, FormatWidth formatWidth, NumberFormat format) { return new MeasureFormat(locale, formatWidth, format, null, null); } /** * Create a format from the {@link java.util.Locale}, formatWidth, and format. * * @param locale * the {@link java.util.Locale}. * @param formatWidth * hints how long formatted strings should be. * @param format * This is defensively copied. * @return The new MeasureFormat object. * @stable ICU 54 */ public static MeasureFormat getInstance( Locale locale, FormatWidth formatWidth, NumberFormat format) { return getInstance(ULocale.forLocale(locale), formatWidth, format); } /** * Able to format Collection<? extends Measure>, Measure[], and Measure by delegating to * formatMeasures. If the pos argument identifies a NumberFormat field, then its indices are set to * the beginning and end of the first such field encountered. MeasureFormat itself does not supply * any fields. * * Calling a formatMeasures method is preferred over calling this method as they give * better performance. * * @param obj * must be a Collection<? extends Measure>, Measure[], or Measure object. * @param toAppendTo * Formatted string appended here. * @param fpos * Identifies a field in the formatted text. * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) * * @stable ICU53 */ @Override public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition fpos) { int prevLength = toAppendTo.length(); fpos.setBeginIndex(0); fpos.setEndIndex(0); if (obj instanceof Collection) { Collection coll = (Collection) obj; Measure[] measures = new Measure[coll.size()]; int idx = 0; for (Object o : coll) { if (!(o instanceof Measure)) { throw new IllegalArgumentException(obj.toString()); } measures[idx++] = (Measure) o; } formatMeasuresInternal(toAppendTo, fpos, measures); } else if (obj instanceof Measure[]) { formatMeasuresInternal(toAppendTo, fpos, (Measure[]) obj); } else if (obj instanceof Measure) { FormattedNumber result = formatMeasure((Measure) obj); result.populateFieldPosition(fpos); // No offset: toAppendTo.length() is considered below result.appendTo(toAppendTo); } else { throw new IllegalArgumentException(obj.toString()); } if (prevLength > 0 && fpos.getEndIndex() != 0) { fpos.setBeginIndex(fpos.getBeginIndex() + prevLength); fpos.setEndIndex(fpos.getEndIndex() + prevLength); } return toAppendTo; } /** * Parses text from a string to produce a Measure. * * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) * @throws UnsupportedOperationException * Not supported. * @draft ICU 53 (Retain) * @provisional This API might change or be removed in a future release. */ @Override public Measure parseObject(String source, ParsePosition pos) { throw new UnsupportedOperationException(); } /** * Format a sequence of measures. Uses the ListFormatter unit lists. So, for example, one could * format “3 feet, 2 inches”. Zero values are formatted (eg, “3 feet, 0 inches”). It is the caller’s * responsibility to have the appropriate values in appropriate order, and using the appropriate * Number values. Typically the units should be in descending order, with all but the last Measure * having integer values (eg, not “3.2 feet, 2 inches”). * * @param measures * a sequence of one or more measures. * @return the formatted string. * @stable ICU 53 */ public final String formatMeasures(Measure... measures) { return formatMeasures(new StringBuilder(), DontCareFieldPosition.INSTANCE, measures).toString(); } // NOTE: For formatMeasureRange(), see http://bugs.icu-project.org/trac/ticket/12454 /** * Formats a single measure per unit. * * An example of such a formatted string is "3.5 meters per second." * * @param measure * the measure object. In above example, 3.5 meters. * @param perUnit * the per unit. In above example, it is MeasureUnit.SECOND * @param appendTo * formatted string appended here. * @param pos * The field position. * @return appendTo. * @stable ICU 55 */ public StringBuilder formatMeasurePerUnit( Measure measure, MeasureUnit perUnit, StringBuilder appendTo, FieldPosition pos) { FormattedNumber result = getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD, measure.getUnit(), perUnit).format(measure.getNumber()); result.populateFieldPosition(pos, appendTo.length()); result.appendTo(appendTo); return appendTo; } /** * Formats a sequence of measures. * * If the fieldPosition argument identifies a NumberFormat field, then its indices are set to the * beginning and end of the first such field encountered. MeasureFormat itself does not supply any * fields. * * @param appendTo * the formatted string appended here. * @param fpos * Identifies a field in the formatted text. * @param measures * the measures to format. * @return appendTo. * @see MeasureFormat#formatMeasures(Measure...) * @stable ICU 53 */ public StringBuilder formatMeasures( StringBuilder appendTo, FieldPosition fpos, Measure... measures) { int prevLength = appendTo.length(); formatMeasuresInternal(appendTo, fpos, measures); if (prevLength > 0 && fpos.getEndIndex() > 0) { fpos.setBeginIndex(fpos.getBeginIndex() + prevLength); fpos.setEndIndex(fpos.getEndIndex() + prevLength); } return appendTo; } private void formatMeasuresInternal( Appendable appendTo, FieldPosition fieldPosition, Measure... measures) { // fast track for trivial cases if (measures.length == 0) { return; } if (measures.length == 1) { FormattedNumber result = formatMeasure(measures[0]); result.populateFieldPosition(fieldPosition); result.appendTo(appendTo); return; } if (formatWidth == FormatWidth.NUMERIC) { // If we have just hour, minute, or second follow the numeric // track. Number[] hms = toHMS(measures); if (hms != null) { formatNumeric(hms, appendTo); return; } } ListFormatter listFormatter = ListFormatter.getInstance(getLocale(), formatWidth.getListFormatterStyle()); if (fieldPosition != DontCareFieldPosition.INSTANCE) { formatMeasuresSlowTrack(listFormatter, appendTo, fieldPosition, measures); return; } // Fast track: No field position. String[] results = new String[measures.length]; for (int i = 0; i < measures.length; i++) { if (i == measures.length - 1) { results[i] = formatMeasure(measures[i]).toString(); } else { results[i] = formatMeasureInteger(measures[i]).toString(); } } FormattedListBuilder builder = listFormatter.format(Arrays.asList(results), -1); builder.appendTo(appendTo); } /** * Gets the display name of the specified {@link MeasureUnit} corresponding to the current locale and * format width. * * @param unit * The unit for which to get a display name. * @return The display name in the locale and width specified in {@link MeasureFormat#getInstance}, * or null if there is no display name available for the specified unit. * * @stable ICU 58 */ public String getUnitDisplayName(MeasureUnit unit) { return LongNameHandler.getUnitDisplayName(getLocale(), unit, formatWidth.unitWidth); } /** * Two MeasureFormats, a and b, are equal if and only if they have the same formatWidth, locale, and * equal number formats. * * @stable ICU 53 */ @Override public final boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof MeasureFormat)) { return false; } MeasureFormat rhs = (MeasureFormat) other; // A very slow but safe implementation. return getWidth() == rhs.getWidth() && getLocale().equals(rhs.getLocale()) && getNumberFormatInternal().equals(rhs.getNumberFormatInternal()); } /** * {@inheritDoc} * * @stable ICU 53 */ @Override public final int hashCode() { // A very slow but safe implementation. return (getLocale().hashCode() * 31 + getNumberFormatInternal().hashCode()) * 31 + getWidth().hashCode(); } /** * Get the format width this instance is using. * * @stable ICU 53 */ public MeasureFormat.FormatWidth getWidth() { return formatWidth; } /** * Get the locale of this instance. * * @stable ICU 53 */ public final ULocale getLocale() { return getLocale(ULocale.VALID_LOCALE); } /** * Get a copy of the number format. * * @stable ICU 53 */ public NumberFormat getNumberFormat() { return (NumberFormat) numberFormat.clone(); } /** * Get a copy of the number format without cloning. Internal method. */ NumberFormat getNumberFormatInternal() { return numberFormat; } /** * Return a formatter for CurrencyAmount objects in the given locale. * * @param locale * desired locale * @return a formatter object * @stable ICU 3.0 */ public static MeasureFormat getCurrencyFormat(ULocale locale) { return new CurrencyFormat(locale); } /** * Return a formatter for CurrencyAmount objects in the given {@link java.util.Locale}. * * @param locale * desired {@link java.util.Locale} * @return a formatter object * @stable ICU 54 */ public static MeasureFormat getCurrencyFormat(Locale locale) { return getCurrencyFormat(ULocale.forLocale(locale)); } /** * Return a formatter for CurrencyAmount objects in the default FORMAT locale. * * @return a formatter object * @see Category#FORMAT * @stable ICU 3.0 */ public static MeasureFormat getCurrencyFormat() { return getCurrencyFormat(ULocale.getDefault(Category.FORMAT)); } // This method changes the NumberFormat object as well to match the new locale. MeasureFormat withLocale(ULocale locale) { return MeasureFormat.getInstance(locale, getWidth()); } MeasureFormat withNumberFormat(NumberFormat format) { return new MeasureFormat(getLocale(), this.formatWidth, format, this.rules, this.numericFormatters); } MeasureFormat(ULocale locale, FormatWidth formatWidth) { this(locale, formatWidth, null, null, null); } private MeasureFormat( ULocale locale, FormatWidth formatWidth, NumberFormat numberFormat, PluralRules rules, NumericFormatters formatters) { // Needed for getLocale(ULocale.VALID_LOCALE). setLocale(locale, locale); this.formatWidth = formatWidth; if (rules == null) { rules = PluralRules.forLocale(locale); } this.rules = rules; if (numberFormat == null) { numberFormat = NumberFormat.getInstance(locale); } else { numberFormat = (NumberFormat) numberFormat.clone(); } this.numberFormat = numberFormat; if (formatters == null && formatWidth == FormatWidth.NUMERIC) { formatters = localeToNumericDurationFormatters.get(locale); if (formatters == null) { formatters = loadNumericFormatters(locale); localeToNumericDurationFormatters.put(locale, formatters); } } this.numericFormatters = formatters; if (!(numberFormat instanceof DecimalFormat)) { throw new IllegalArgumentException(); } numberFormatter = ((DecimalFormat) numberFormat).toNumberFormatter() .unitWidth(formatWidth.unitWidth); } MeasureFormat( ULocale locale, FormatWidth formatWidth, NumberFormat numberFormat, PluralRules rules) { this(locale, formatWidth, numberFormat, rules, null); if (formatWidth == FormatWidth.NUMERIC) { throw new IllegalArgumentException( "The format width 'numeric' is not allowed by this constructor"); } } static class NumericFormatters { private DateFormat hourMinute; private DateFormat minuteSecond; private DateFormat hourMinuteSecond; public NumericFormatters( DateFormat hourMinute, DateFormat minuteSecond, DateFormat hourMinuteSecond) { this.hourMinute = hourMinute; this.minuteSecond = minuteSecond; this.hourMinuteSecond = hourMinuteSecond; } public DateFormat getHourMinute() { return hourMinute; } public DateFormat getMinuteSecond() { return minuteSecond; } public DateFormat getHourMinuteSecond() { return hourMinuteSecond; } } private static NumericFormatters loadNumericFormatters(ULocale locale) { ICUResourceBundle r = (ICUResourceBundle) UResourceBundle .getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale); return new NumericFormatters(loadNumericDurationFormat(r, "hm"), loadNumericDurationFormat(r, "ms"), loadNumericDurationFormat(r, "hms")); } /// BEGIN NUMBER FORMATTER CACHING MACHINERY /// static final int NUMBER_FORMATTER_STANDARD = 1; static final int NUMBER_FORMATTER_CURRENCY = 2; static final int NUMBER_FORMATTER_INTEGER = 3; static class NumberFormatterCacheEntry { int type; MeasureUnit unit; MeasureUnit perUnit; LocalizedNumberFormatter formatter; } // formatter1 is most recently used. private transient NumberFormatterCacheEntry formatter1 = null; private transient NumberFormatterCacheEntry formatter2 = null; private transient NumberFormatterCacheEntry formatter3 = null; private synchronized LocalizedNumberFormatter getUnitFormatterFromCache( int type, MeasureUnit unit, MeasureUnit perUnit) { if (formatter1 != null) { if (formatter1.type == type && formatter1.unit == unit && formatter1.perUnit == perUnit) { return formatter1.formatter; } if (formatter2 != null) { if (formatter2.type == type && formatter2.unit == unit && formatter2.perUnit == perUnit) { return formatter2.formatter; } if (formatter3 != null) { if (formatter3.type == type && formatter3.unit == unit && formatter3.perUnit == perUnit) { return formatter3.formatter; } } } } // No hit; create a new formatter. LocalizedNumberFormatter formatter; if (type == NUMBER_FORMATTER_STANDARD) { formatter = getNumberFormatter().unit(unit).perUnit(perUnit) .unitWidth(formatWidth.unitWidth); } else if (type == NUMBER_FORMATTER_CURRENCY) { formatter = NumberFormatter.withLocale(getLocale()).unit(unit).perUnit(perUnit) .unitWidth(formatWidth.currencyWidth); } else { assert type == NUMBER_FORMATTER_INTEGER; formatter = getNumberFormatter().unit(unit).perUnit(perUnit).unitWidth(formatWidth.unitWidth) .rounding(Rounder.integer().withMode(RoundingMode.DOWN)); } formatter3 = formatter2; formatter2 = formatter1; formatter1 = new NumberFormatterCacheEntry(); formatter1.type = type; formatter1.unit = unit; formatter1.perUnit = perUnit; formatter1.formatter = formatter; return formatter; } synchronized void clearCache() { formatter1 = null; formatter2 = null; formatter3 = null; } // Can be overridden by subclasses: LocalizedNumberFormatter getNumberFormatter() { return numberFormatter; } /// END NUMBER FORMATTER CACHING MACHINERY /// private FormattedNumber formatMeasure(Measure measure) { MeasureUnit unit = measure.getUnit(); if (unit instanceof Currency) { return getUnitFormatterFromCache(NUMBER_FORMATTER_CURRENCY, unit, null) .format(measure.getNumber()); } else { return getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD, unit, null) .format(measure.getNumber()); } } private FormattedNumber formatMeasureInteger(Measure measure) { return getUnitFormatterFromCache(NUMBER_FORMATTER_INTEGER, measure.getUnit(), null) .format(measure.getNumber()); } private void formatMeasuresSlowTrack( ListFormatter listFormatter, Appendable appendTo, FieldPosition fieldPosition, Measure... measures) { String[] results = new String[measures.length]; // Zero out our field position so that we can tell when we find our field. FieldPosition fpos = new FieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField()); int fieldPositionFoundIndex = -1; for (int i = 0; i < measures.length; ++i) { FormattedNumber result; if (i == measures.length - 1) { result = formatMeasure(measures[i]); } else { result = formatMeasureInteger(measures[i]); } if (fieldPositionFoundIndex == -1) { result.populateFieldPosition(fpos); if (fpos.getEndIndex() != 0) { fieldPositionFoundIndex = i; } } results[i] = result.toString(); } ListFormatter.FormattedListBuilder builder = listFormatter.format(Arrays.asList(results), fieldPositionFoundIndex); // Fix up FieldPosition indexes if our field is found. if (builder.getOffset() != -1) { fieldPosition.setBeginIndex(fpos.getBeginIndex() + builder.getOffset()); fieldPosition.setEndIndex(fpos.getEndIndex() + builder.getOffset()); } builder.appendTo(appendTo); } // type is one of "hm", "ms" or "hms" private static DateFormat loadNumericDurationFormat(ICUResourceBundle r, String type) { 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; } // Returns hours in [0]; minutes in [1]; seconds in [2] out of measures array. If // unsuccessful, e.g measures has other measurements besides hours, minutes, seconds; // hours, minutes, seconds are out of order; or have negative values, returns null. // If hours, minutes, or seconds is missing from measures the corresponding element in // returned array will be null. private static Number[] toHMS(Measure[] measures) { Number[] result = new Number[3]; int lastIdx = -1; for (Measure m : measures) { if (m.getNumber().doubleValue() < 0.0) { return null; } Integer idxObj = hmsTo012.get(m.getUnit()); if (idxObj == null) { return null; } int idx = idxObj.intValue(); if (idx <= lastIdx) { // hour before minute before second return null; } lastIdx = idx; result[idx] = m.getNumber(); } return result; } // Formats numeric time duration as 5:00:47 or 3:54. In the process, it replaces any null // values in hms with 0. private void formatNumeric(Number[] hms, Appendable appendable) { // find the start and end of non-nil values in hms array. We have to know if we // have hour-minute; minute-second; or hour-minute-second. int startIndex = -1; int endIndex = -1; for (int i = 0; i < hms.length; i++) { if (hms[i] != null) { endIndex = i; if (startIndex == -1) { startIndex = endIndex; } } else { // Replace nil value with 0. hms[i] = Integer.valueOf(0); } } // convert hours, minutes, seconds into milliseconds. long millis = (long) (((Math.floor(hms[0].doubleValue()) * 60.0 + Math.floor(hms[1].doubleValue())) * 60.0 + Math.floor(hms[2].doubleValue())) * 1000.0); Date d = new Date(millis); if (startIndex == 0 && endIndex == 2) { // if hour-minute-second formatNumeric(d, numericFormatters.getHourMinuteSecond(), DateFormat.Field.SECOND, hms[endIndex], appendable); } else if (startIndex == 1 && endIndex == 2) { // if minute-second formatNumeric(d, numericFormatters.getMinuteSecond(), DateFormat.Field.SECOND, hms[endIndex], appendable); } else if (startIndex == 0 && endIndex == 1) { // if hour-minute formatNumeric(d, numericFormatters.getHourMinute(), DateFormat.Field.MINUTE, hms[endIndex], appendable); } else { throw new IllegalStateException(); } } // Formats a duration as 5:00:37 or 23:59. // duration is a particular duration after epoch. // formatter is a hour-minute-second, hour-minute, or minute-second formatter. // smallestField denotes what the smallest field is in duration: either // hour, minute, or second. // smallestAmount is the value of that smallest field. for 5:00:37.3, // smallestAmount is 37.3. This smallest field is formatted with this object's // NumberFormat instead of formatter. // appendTo is where the formatted string is appended. private void formatNumeric( Date duration, DateFormat formatter, DateFormat.Field smallestField, Number smallestAmount, Appendable appendTo) { // Format the smallest amount ahead of time. String smallestAmountFormatted; // Format the smallest amount using this object's number format, but keep track // of the integer portion of this formatted amount. We have to replace just the // integer part with the corresponding value from formatting the date. Otherwise // when formatting 0 minutes 9 seconds, we may get "00:9" instead of "00:09" FieldPosition intFieldPosition = new FieldPosition(NumberFormat.INTEGER_FIELD); FormattedNumber result = getNumberFormatter().format(smallestAmount); result.populateFieldPosition(intFieldPosition); smallestAmountFormatted = result.toString(); // Give up if there is no integer field. if (intFieldPosition.getBeginIndex() == 0 && intFieldPosition.getEndIndex() == 0) { throw new IllegalStateException(); } // Format our duration as a date, but keep track of where the smallest field is // so that we can use it to replace the integer portion of the smallest value. // #13606: DateFormat is not thread-safe, but MeasureFormat advertises itself as thread-safe. FieldPosition smallestFieldPosition = new FieldPosition(smallestField); String draft; synchronized (formatter) { draft = formatter.format(duration, new StringBuffer(), smallestFieldPosition).toString(); } try { // If we find the smallest field if (smallestFieldPosition.getBeginIndex() != 0 || smallestFieldPosition.getEndIndex() != 0) { // add everything up to the start of the smallest field in duration. appendTo.append(draft, 0, smallestFieldPosition.getBeginIndex()); // add everything in the smallest field up to the integer portion appendTo.append(smallestAmountFormatted, 0, intFieldPosition.getBeginIndex()); // Add the smallest field in formatted duration in lieu of the integer portion // of smallest field appendTo.append(draft, smallestFieldPosition.getBeginIndex(), smallestFieldPosition.getEndIndex()); // Add the rest of the smallest field appendTo.append(smallestAmountFormatted, intFieldPosition.getEndIndex(), smallestAmountFormatted.length()); appendTo.append(draft, smallestFieldPosition.getEndIndex(), draft.length()); } else { // As fallback, just use the formatted duration. appendTo.append(draft); } } catch (IOException e) { throw new ICUUncheckedIOException(e); } } Object toTimeUnitProxy() { return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), TIME_UNIT_FORMAT); } Object toCurrencyProxy() { return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), CURRENCY_FORMAT); } private Object writeReplace() throws ObjectStreamException { return new MeasureProxy(getLocale(), formatWidth, getNumberFormatInternal(), MEASURE_FORMAT); } static class MeasureProxy implements Externalizable { private static final long serialVersionUID = -6033308329886716770L; private ULocale locale; private FormatWidth formatWidth; private NumberFormat numberFormat; private int subClass; private HashMap keyValues; public MeasureProxy(ULocale locale, FormatWidth width, NumberFormat numberFormat, int subClass) { this.locale = locale; this.formatWidth = width; this.numberFormat = numberFormat; this.subClass = subClass; this.keyValues = new HashMap(); } // Must have public constructor, to enable Externalizable public MeasureProxy() { } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeByte(0); // version out.writeUTF(locale.toLanguageTag()); out.writeByte(formatWidth.ordinal()); out.writeObject(numberFormat); out.writeByte(subClass); out.writeObject(keyValues); } @Override @SuppressWarnings("unchecked") public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { in.readByte(); // version. locale = ULocale.forLanguageTag(in.readUTF()); formatWidth = fromFormatWidthOrdinal(in.readByte() & 0xFF); numberFormat = (NumberFormat) in.readObject(); if (numberFormat == null) { throw new InvalidObjectException("Missing number format."); } subClass = in.readByte() & 0xFF; // This cast is safe because the serialized form of hashtable can have // any object as the key and any object as the value. keyValues = (HashMap) in.readObject(); if (keyValues == null) { throw new InvalidObjectException("Missing optional values map."); } } private TimeUnitFormat createTimeUnitFormat() throws InvalidObjectException { int style; if (formatWidth == FormatWidth.WIDE) { style = TimeUnitFormat.FULL_NAME; } else if (formatWidth == FormatWidth.SHORT) { style = TimeUnitFormat.ABBREVIATED_NAME; } else { throw new InvalidObjectException("Bad width: " + formatWidth); } TimeUnitFormat result = new TimeUnitFormat(locale, style); result.setNumberFormat(numberFormat); return result; } private Object readResolve() throws ObjectStreamException { switch (subClass) { case MEASURE_FORMAT: return MeasureFormat.getInstance(locale, formatWidth, numberFormat); case TIME_UNIT_FORMAT: return createTimeUnitFormat(); case CURRENCY_FORMAT: return MeasureFormat.getCurrencyFormat(locale); default: throw new InvalidObjectException("Unknown subclass: " + subClass); } } } private static FormatWidth fromFormatWidthOrdinal(int ordinal) { FormatWidth[] values = FormatWidth.values(); if (ordinal < 0 || ordinal >= values.length) { return FormatWidth.SHORT; } return values[ordinal]; } private static final Map localeIdToRangeFormat = new ConcurrentHashMap(); /** * Return a formatter (compiled SimpleFormatter pattern) for a range, such as "{0}–{1}". * * @param forLocale * locale to get the format for * @param width * the format width * @return range formatter, such as "{0}–{1}" * @internal * @deprecated This API is ICU internal only. */ @Deprecated public static String getRangeFormat(ULocale forLocale, FormatWidth width) { // TODO fix Hack for French if (forLocale.getLanguage().equals("fr")) { return getRangeFormat(ULocale.ROOT, width); } String result = localeIdToRangeFormat.get(forLocale); if (result == null) { ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle .getBundleInstance(ICUData.ICU_BASE_NAME, forLocale); ULocale realLocale = rb.getULocale(); if (!forLocale.equals(realLocale)) { // if the child would inherit, then add a cache entry // for it. result = localeIdToRangeFormat.get(forLocale); if (result != null) { localeIdToRangeFormat.put(forLocale, result); return result; } } // At this point, both the forLocale and the realLocale don't have an item // So we have to make one. NumberingSystem ns = NumberingSystem.getInstance(forLocale); String resultString = null; try { resultString = rb .getStringWithFallback("NumberElements/" + ns.getName() + "/miscPatterns/range"); } catch (MissingResourceException ex) { resultString = rb.getStringWithFallback("NumberElements/latn/patterns/range"); } result = SimpleFormatterImpl .compileToStringMinMaxArguments(resultString, new StringBuilder(), 2, 2); localeIdToRangeFormat.put(forLocale, result); if (!forLocale.equals(realLocale)) { localeIdToRangeFormat.put(realLocale, result); } } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy