ICU-20622 Fixing several MeasureFormat problems
This commit is contained in:
parent
506c935bf5
commit
6ce3295e4d
icu4c/source
icu4j/main
classes/core/src/com/ibm/icu/text
tests/core/src/com/ibm/icu/dev/test/format
@ -55,28 +55,23 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MeasureFormat)
|
||||
class NumericDateFormatters : public UMemory {
|
||||
public:
|
||||
// Formats like H:mm
|
||||
SimpleDateFormat hourMinute;
|
||||
UnicodeString hourMinute;
|
||||
|
||||
// formats like M:ss
|
||||
SimpleDateFormat minuteSecond;
|
||||
UnicodeString minuteSecond;
|
||||
|
||||
// formats like H:mm:ss
|
||||
SimpleDateFormat hourMinuteSecond;
|
||||
UnicodeString hourMinuteSecond;
|
||||
|
||||
// Constructor that takes the actual patterns for hour-minute,
|
||||
// minute-second, and hour-minute-second respectively.
|
||||
NumericDateFormatters(
|
||||
const UnicodeString &hm,
|
||||
const UnicodeString &ms,
|
||||
const UnicodeString &hms,
|
||||
UErrorCode &status) :
|
||||
hourMinute(hm, status),
|
||||
minuteSecond(ms, status),
|
||||
hourMinuteSecond(hms, status) {
|
||||
const TimeZone *gmt = TimeZone::getGMT();
|
||||
hourMinute.setTimeZone(*gmt);
|
||||
minuteSecond.setTimeZone(*gmt);
|
||||
hourMinuteSecond.setTimeZone(*gmt);
|
||||
const UnicodeString &hms) :
|
||||
hourMinute(hm),
|
||||
minuteSecond(ms),
|
||||
hourMinuteSecond(hms) {
|
||||
}
|
||||
private:
|
||||
NumericDateFormatters(const NumericDateFormatters &other);
|
||||
@ -233,8 +228,7 @@ static NumericDateFormatters *loadNumericDateFormatters(
|
||||
NumericDateFormatters *result = new NumericDateFormatters(
|
||||
loadNumericDateFormatterPattern(resource, "hm", status),
|
||||
loadNumericDateFormatterPattern(resource, "ms", status),
|
||||
loadNumericDateFormatterPattern(resource, "hms", status),
|
||||
status);
|
||||
loadNumericDateFormatterPattern(resource, "hms", status));
|
||||
if (U_FAILURE(status)) {
|
||||
delete result;
|
||||
return NULL;
|
||||
@ -706,55 +700,6 @@ UnicodeString &MeasureFormat::formatMeasure(
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
// Formats hours-minutes-seconds as 5:37:23 or similar.
|
||||
UnicodeString &MeasureFormat::formatNumeric(
|
||||
const Formattable *hms, // always length 3
|
||||
int32_t bitMap, // 1=hourset, 2=minuteset, 4=secondset
|
||||
UnicodeString &appendTo,
|
||||
UErrorCode &status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
UDate millis =
|
||||
(UDate) (((uprv_trunc(hms[0].getDouble(status)) * 60.0
|
||||
+ uprv_trunc(hms[1].getDouble(status))) * 60.0
|
||||
+ uprv_trunc(hms[2].getDouble(status))) * 1000.0);
|
||||
switch (bitMap) {
|
||||
case 5: // hs
|
||||
case 7: // hms
|
||||
return formatNumeric(
|
||||
millis,
|
||||
cache->getNumericDateFormatters()->hourMinuteSecond,
|
||||
UDAT_SECOND_FIELD,
|
||||
hms[2],
|
||||
appendTo,
|
||||
status);
|
||||
break;
|
||||
case 6: // ms
|
||||
return formatNumeric(
|
||||
millis,
|
||||
cache->getNumericDateFormatters()->minuteSecond,
|
||||
UDAT_SECOND_FIELD,
|
||||
hms[2],
|
||||
appendTo,
|
||||
status);
|
||||
break;
|
||||
case 3: // hm
|
||||
return formatNumeric(
|
||||
millis,
|
||||
cache->getNumericDateFormatters()->hourMinute,
|
||||
UDAT_MINUTE_FIELD,
|
||||
hms[1],
|
||||
appendTo,
|
||||
status);
|
||||
break;
|
||||
default:
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return appendTo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void appendRange(
|
||||
const UnicodeString &src,
|
||||
int32_t start,
|
||||
@ -770,71 +715,112 @@ static void appendRange(
|
||||
dest.append(src, end, src.length() - end);
|
||||
}
|
||||
|
||||
// Formats time like 5:37:23
|
||||
|
||||
// Formats numeric time duration as 5:00:47 or 3:54.
|
||||
UnicodeString &MeasureFormat::formatNumeric(
|
||||
UDate date, // Time since epoch 1:30:00 would be 5400000
|
||||
const DateFormat &dateFmt, // h:mm, m:ss, or h:mm:ss
|
||||
UDateFormatField smallestField, // seconds in 5:37:23.5
|
||||
const Formattable &smallestAmount, // 23.5 for 5:37:23.5
|
||||
const Formattable *hms, // always length 3
|
||||
int32_t bitMap, // 1=hour set, 2=minute set, 4=second set
|
||||
UnicodeString &appendTo,
|
||||
UErrorCode &status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
// Format the smallest amount with this object's NumberFormat
|
||||
UnicodeString smallestAmountFormatted;
|
||||
|
||||
// We keep track of the integer part of smallest amount so that
|
||||
// we can replace it later so that we get '0:00:09.3' instead of
|
||||
// '0:00:9.3'
|
||||
FieldPosition intFieldPosition(UNUM_INTEGER_FIELD);
|
||||
(*numberFormat)->format(
|
||||
smallestAmount, smallestAmountFormatted, intFieldPosition, status);
|
||||
if (
|
||||
intFieldPosition.getBeginIndex() == 0 &&
|
||||
intFieldPosition.getEndIndex() == 0) {
|
||||
UnicodeString pattern;
|
||||
|
||||
double hours = hms[0].getDouble(status);
|
||||
double minutes = hms[1].getDouble(status);
|
||||
double seconds = hms[2].getDouble(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
// All possible combinations: "h", "m", "s", "hm", "hs", "ms", "hms"
|
||||
if (bitMap == 5 || bitMap == 7) { // "hms" & "hs" (we add minutes if "hs")
|
||||
pattern = cache->getNumericDateFormatters()->hourMinuteSecond;
|
||||
hours = uprv_trunc(hours);
|
||||
minutes = uprv_trunc(minutes);
|
||||
} else if (bitMap == 3) { // "hm"
|
||||
pattern = cache->getNumericDateFormatters()->hourMinute;
|
||||
hours = uprv_trunc(hours);
|
||||
} else if (bitMap == 6) { // "ms"
|
||||
pattern = cache->getNumericDateFormatters()->minuteSecond;
|
||||
minutes = uprv_trunc(minutes);
|
||||
} else { // h m s, handled outside formatNumeric. No value is also an error.
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
// Format time. draft becomes something like '5:30:45'
|
||||
// #13606: DateFormat is not thread-safe, but MeasureFormat advertises itself as thread-safe.
|
||||
FieldPosition smallestFieldPosition(smallestField);
|
||||
UnicodeString draft;
|
||||
static UMutex dateFmtMutex;
|
||||
umtx_lock(&dateFmtMutex);
|
||||
dateFmt.format(date, draft, smallestFieldPosition, status);
|
||||
umtx_unlock(&dateFmtMutex);
|
||||
|
||||
// If we find field for smallest amount replace it with the formatted
|
||||
// smallest amount from above taking care to replace the integer part
|
||||
// with what is in original time. For example, If smallest amount
|
||||
// is 9.35s and the formatted time is 0:00:09 then 9.35 becomes 09.35
|
||||
// and replacing yields 0:00:09.35
|
||||
if (smallestFieldPosition.getBeginIndex() != 0 ||
|
||||
smallestFieldPosition.getEndIndex() != 0) {
|
||||
appendRange(draft, 0, smallestFieldPosition.getBeginIndex(), appendTo);
|
||||
appendRange(
|
||||
smallestAmountFormatted,
|
||||
0,
|
||||
intFieldPosition.getBeginIndex(),
|
||||
appendTo);
|
||||
appendRange(
|
||||
draft,
|
||||
smallestFieldPosition.getBeginIndex(),
|
||||
smallestFieldPosition.getEndIndex(),
|
||||
appendTo);
|
||||
appendRange(
|
||||
smallestAmountFormatted,
|
||||
intFieldPosition.getEndIndex(),
|
||||
appendTo);
|
||||
appendRange(
|
||||
draft,
|
||||
smallestFieldPosition.getEndIndex(),
|
||||
appendTo);
|
||||
} else {
|
||||
appendTo.append(draft);
|
||||
const DecimalFormat *numberFormatter = dynamic_cast<const DecimalFormat*>(numberFormat->get());
|
||||
if (!numberFormatter) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return appendTo;
|
||||
}
|
||||
number::LocalizedNumberFormatter numberFormatter2;
|
||||
if (auto* lnf = numberFormatter->toNumberFormatter(status)) {
|
||||
numberFormatter2 = lnf->integerWidth(number::IntegerWidth::zeroFillTo(2));
|
||||
} else {
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
FormattedStringBuilder fsb;
|
||||
|
||||
UBool protect = FALSE;
|
||||
const int32_t patternLength = pattern.length();
|
||||
for (int32_t i = 0; i < patternLength; i++) {
|
||||
char16_t c = pattern[i];
|
||||
|
||||
// Also set the proper field in this switch
|
||||
// We don't use DateFormat.Field because this is not a date / time, is a duration.
|
||||
double value = 0;
|
||||
switch (c) {
|
||||
case u'H': value = hours; break;
|
||||
case u'm': value = minutes; break;
|
||||
case u's': value = seconds; break;
|
||||
}
|
||||
|
||||
// For undefined field we use UNUM_FIELD_COUNT, for historical reasons.
|
||||
// See cleanup bug: https://unicode-org.atlassian.net/browse/ICU-20665
|
||||
// But we give it a clear name, to keep "the ugly part" in one place.
|
||||
constexpr UNumberFormatFields undefinedField = UNUM_FIELD_COUNT;
|
||||
|
||||
// There is not enough info to add Field(s) for the unit because all we have are plain
|
||||
// text patterns. For example in "21:51" there is no text for something like "hour",
|
||||
// while in something like "21h51" there is ("h"). But we can't really tell...
|
||||
switch (c) {
|
||||
case u'H':
|
||||
case u'm':
|
||||
case u's':
|
||||
if (protect) {
|
||||
fsb.appendCodePoint(c, undefinedField, status);
|
||||
} else {
|
||||
UnicodeString tmp;
|
||||
if ((i + 1 < patternLength) && pattern[i + 1] == c) { // doubled
|
||||
tmp = numberFormatter2.formatDouble(value, status).toString(status);
|
||||
i++;
|
||||
} else {
|
||||
numberFormatter->format(value, tmp, status);
|
||||
}
|
||||
// TODO: Use proper Field
|
||||
fsb.append(tmp, undefinedField, status);
|
||||
}
|
||||
break;
|
||||
case u'\'':
|
||||
// '' is escaped apostrophe
|
||||
if ((i + 1 < patternLength) && pattern[i + 1] == c) {
|
||||
fsb.appendCodePoint(c, undefinedField, status);
|
||||
i++;
|
||||
} else {
|
||||
protect = !protect;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fsb.appendCodePoint(c, undefinedField, status);
|
||||
}
|
||||
}
|
||||
|
||||
appendTo.append(fsb.toTempUnicodeString());
|
||||
|
||||
return appendTo;
|
||||
}
|
||||
|
||||
|
@ -384,14 +384,6 @@ class U_I18N_API MeasureFormat : public Format {
|
||||
int32_t bitMap, // 1=hour set, 2=minute set, 4=second set
|
||||
UnicodeString &appendTo,
|
||||
UErrorCode &status) const;
|
||||
|
||||
UnicodeString &formatNumeric(
|
||||
UDate date,
|
||||
const DateFormat &dateFmt,
|
||||
UDateFormatField smallestField,
|
||||
const Formattable &smallestAmount,
|
||||
UnicodeString &appendTo,
|
||||
UErrorCode &status) const;
|
||||
};
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
@ -75,6 +75,8 @@ private:
|
||||
void TestUnitPerUnitResolution();
|
||||
void TestIndividualPluralFallback();
|
||||
void Test20332_PersonUnits();
|
||||
void TestNumericTime();
|
||||
void TestNumericTimeSomeSpecialFormats();
|
||||
void verifyFormat(
|
||||
const char *description,
|
||||
const MeasureFormat &fmt,
|
||||
@ -175,6 +177,8 @@ void MeasureFormatTest::runIndexedTest(
|
||||
TESTCASE_AUTO(TestUnitPerUnitResolution);
|
||||
TESTCASE_AUTO(TestIndividualPluralFallback);
|
||||
TESTCASE_AUTO(Test20332_PersonUnits);
|
||||
TESTCASE_AUTO(TestNumericTime);
|
||||
TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
@ -1837,6 +1841,32 @@ void MeasureFormatTest::TestFormatPeriodEn() {
|
||||
{t_6h_56_92m, UPRV_LENGTHOF(t_6h_56_92m), "6:56,92"},
|
||||
{t_3h_5h, UPRV_LENGTHOF(t_3h_5h), "3 Std., 5 Std."}};
|
||||
|
||||
ExpectedResult numericDataBn[] = {
|
||||
{t_1m_59_9996s, UPRV_LENGTHOF(t_1m_59_9996s), "\\u09E7:\\u09EB\\u09EF.\\u09EF\\u09EF\\u09EF\\u09EC"},
|
||||
{t_19m, UPRV_LENGTHOF(t_19m), "\\u09E7\\u09EF \\u09AE\\u09BF\\u0983"},
|
||||
{t_1h_23_5s, UPRV_LENGTHOF(t_1h_23_5s), "\\u09E7:\\u09E6\\u09E6:\\u09E8\\u09E9.\\u09EB"},
|
||||
{t_1h_0m_23s, UPRV_LENGTHOF(t_1h_0m_23s), "\\u09E7:\\u09E6\\u09E6:\\u09E8\\u09E9"},
|
||||
{t_1h_23_5m, UPRV_LENGTHOF(t_1h_23_5m), "\\u09E7:\\u09E8\\u09E9.\\u09EB"},
|
||||
{t_5h_17m, UPRV_LENGTHOF(t_5h_17m), "\\u09EB:\\u09E7\\u09ED"},
|
||||
{t_19m_28s, UPRV_LENGTHOF(t_19m_28s), "\\u09E7\\u09EF:\\u09E8\\u09EE"},
|
||||
{t_2y_5M_3w_4d, UPRV_LENGTHOF(t_2y_5M_3w_4d), "\\u09E8 \\u09AC\\u099B\\u09B0, \\u09EB \\u09AE\\u09BE\\u09B8, \\u09E9 \\u09B8\\u09AA\\u09CD\\u09A4\\u09BE\\u09B9, \\u09EA \\u09A6\\u09BF\\u09A8"},
|
||||
{t_0h_0m_17s, UPRV_LENGTHOF(t_0h_0m_17s), "\\u09E6:\\u09E6\\u09E6:\\u09E7\\u09ED"},
|
||||
{t_6h_56_92m, UPRV_LENGTHOF(t_6h_56_92m), "\\u09EC:\\u09EB\\u09EC.\\u09EF\\u09E8"},
|
||||
{t_3h_5h, UPRV_LENGTHOF(t_3h_5h), "\\u09E9 \\u0998\\u0983, \\u09EB \\u0998\\u0983"}};
|
||||
|
||||
ExpectedResult numericDataBnLatn[] = {
|
||||
{t_1m_59_9996s, UPRV_LENGTHOF(t_1m_59_9996s), "1:59.9996"},
|
||||
{t_19m, UPRV_LENGTHOF(t_19m), "19 \\u09AE\\u09BF\\u0983"},
|
||||
{t_1h_23_5s, UPRV_LENGTHOF(t_1h_23_5s), "1:00:23.5"},
|
||||
{t_1h_0m_23s, UPRV_LENGTHOF(t_1h_0m_23s), "1:00:23"},
|
||||
{t_1h_23_5m, UPRV_LENGTHOF(t_1h_23_5m), "1:23.5"},
|
||||
{t_5h_17m, UPRV_LENGTHOF(t_5h_17m), "5:17"},
|
||||
{t_19m_28s, UPRV_LENGTHOF(t_19m_28s), "19:28"},
|
||||
{t_2y_5M_3w_4d, UPRV_LENGTHOF(t_2y_5M_3w_4d), "2 \\u09AC\\u099B\\u09B0, 5 \\u09AE\\u09BE\\u09B8, 3 \\u09B8\\u09AA\\u09CD\\u09A4\\u09BE\\u09B9, 4 \\u09A6\\u09BF\\u09A8"},
|
||||
{t_0h_0m_17s, UPRV_LENGTHOF(t_0h_0m_17s), "0:00:17"},
|
||||
{t_6h_56_92m, UPRV_LENGTHOF(t_6h_56_92m), "6:56.92"},
|
||||
{t_3h_5h, UPRV_LENGTHOF(t_3h_5h), "3 \\u0998\\u0983, 5 \\u0998\\u0983"}};
|
||||
|
||||
Locale en(Locale::getEnglish());
|
||||
LocalPointer<NumberFormat> nf(NumberFormat::createInstance(en, status));
|
||||
if (U_FAILURE(status)) {
|
||||
@ -1893,6 +1923,30 @@ void MeasureFormatTest::TestFormatPeriodEn() {
|
||||
return;
|
||||
}
|
||||
verifyFormat("de NUMERIC", mf, numericDataDe, UPRV_LENGTHOF(numericDataDe));
|
||||
|
||||
Locale bengali("bn");
|
||||
nf.adoptInstead(NumberFormat::createInstance(bengali, status));
|
||||
if (!assertSuccess("Error creating number format de object", status)) {
|
||||
return;
|
||||
}
|
||||
nf->setMaximumFractionDigits(4);
|
||||
mf = MeasureFormat(bengali, UMEASFMT_WIDTH_NUMERIC, (NumberFormat *) nf->clone(), status);
|
||||
if (!assertSuccess("Error creating measure format bn NUMERIC", status)) {
|
||||
return;
|
||||
}
|
||||
verifyFormat("bn NUMERIC", mf, numericDataBn, UPRV_LENGTHOF(numericDataBn));
|
||||
|
||||
Locale bengaliLatin("bn-u-nu-latn");
|
||||
nf.adoptInstead(NumberFormat::createInstance(bengaliLatin, status));
|
||||
if (!assertSuccess("Error creating number format de object", status)) {
|
||||
return;
|
||||
}
|
||||
nf->setMaximumFractionDigits(4);
|
||||
mf = MeasureFormat(bengaliLatin, UMEASFMT_WIDTH_NUMERIC, (NumberFormat *) nf->clone(), status);
|
||||
if (!assertSuccess("Error creating measure format bn-u-nu-latn NUMERIC", status)) {
|
||||
return;
|
||||
}
|
||||
verifyFormat("bn-u-nu-latn NUMERIC", mf, numericDataBnLatn, UPRV_LENGTHOF(numericDataBnLatn));
|
||||
}
|
||||
|
||||
void MeasureFormatTest::Test10219FractionalPlurals() {
|
||||
@ -1967,7 +2021,7 @@ void MeasureFormatTest::TestGreek() {
|
||||
"1 \\u03B7\\u03BC\\u03AD\\u03C1\\u03B1",
|
||||
"1 \\u03B5\\u03B2\\u03B4.",
|
||||
"1 \\u03BC\\u03AE\\u03BD.",
|
||||
"1 \\u03AD\\u03C4.", // year (one)
|
||||
"1 \\u03AD\\u03C4.", // year (one)
|
||||
// "el_GR" 7 wide
|
||||
"7 \\u03B4\\u03B5\\u03C5\\u03C4\\u03B5\\u03C1\\u03CC\\u03BB\\u03B5\\u03C0\\u03C4\\u03B1",
|
||||
"7 \\u03BB\\u03B5\\u03C0\\u03C4\\u03AC",
|
||||
@ -1979,7 +2033,7 @@ void MeasureFormatTest::TestGreek() {
|
||||
// "el_GR" 7 short
|
||||
"7 \\u03B4\\u03B5\\u03C5\\u03C4.",
|
||||
"7 \\u03BB\\u03B5\\u03C0.",
|
||||
"7 \\u03CE\\u03C1.", // hour (other)
|
||||
"7 \\u03CE\\u03C1.", // hour (other)
|
||||
"7 \\u03B7\\u03BC\\u03AD\\u03C1\\u03B5\\u03C2",
|
||||
"7 \\u03B5\\u03B2\\u03B4.",
|
||||
"7 \\u03BC\\u03AE\\u03BD.",
|
||||
@ -2000,7 +2054,7 @@ void MeasureFormatTest::TestGreek() {
|
||||
"1 \\u03B7\\u03BC\\u03AD\\u03C1\\u03B1",
|
||||
"1 \\u03B5\\u03B2\\u03B4.",
|
||||
"1 \\u03BC\\u03AE\\u03BD.",
|
||||
"1 \\u03AD\\u03C4.", // year (one)
|
||||
"1 \\u03AD\\u03C4.", // year (one)
|
||||
// "el" 7 wide
|
||||
"7 \\u03B4\\u03B5\\u03C5\\u03C4\\u03B5\\u03C1\\u03CC\\u03BB\\u03B5\\u03C0\\u03C4\\u03B1",
|
||||
"7 \\u03BB\\u03B5\\u03C0\\u03C4\\u03AC",
|
||||
@ -2012,7 +2066,7 @@ void MeasureFormatTest::TestGreek() {
|
||||
// "el" 7 short
|
||||
"7 \\u03B4\\u03B5\\u03C5\\u03C4.",
|
||||
"7 \\u03BB\\u03B5\\u03C0.",
|
||||
"7 \\u03CE\\u03C1.", // hour (other)
|
||||
"7 \\u03CE\\u03C1.", // hour (other)
|
||||
"7 \\u03B7\\u03BC\\u03AD\\u03C1\\u03B5\\u03C2",
|
||||
"7 \\u03B5\\u03B2\\u03B4.",
|
||||
"7 \\u03BC\\u03AE\\u03BD.",
|
||||
@ -2691,6 +2745,79 @@ void MeasureFormatTest::Test20332_PersonUnits() {
|
||||
}
|
||||
}
|
||||
|
||||
void MeasureFormatTest::TestNumericTime() {
|
||||
IcuTestErrorCode status(*this, "TestNumericTime");
|
||||
|
||||
MeasureFormat fmt("en", UMEASFMT_WIDTH_NUMERIC, status);
|
||||
|
||||
Measure hours(112, MeasureUnit::createHour(status), status);
|
||||
Measure minutes(113, MeasureUnit::createMinute(status), status);
|
||||
Measure seconds(114, MeasureUnit::createSecond(status), status);
|
||||
Measure fhours(112.8765, MeasureUnit::createHour(status), status);
|
||||
Measure fminutes(113.8765, MeasureUnit::createMinute(status), status);
|
||||
Measure fseconds(114.8765, MeasureUnit::createSecond(status), status);
|
||||
assertSuccess("", status);
|
||||
|
||||
verifyFormat("hours", fmt, &hours, 1, "112h");
|
||||
verifyFormat("minutes", fmt, &minutes, 1, "113m");
|
||||
verifyFormat("seconds", fmt, &seconds, 1, "114s");
|
||||
|
||||
verifyFormat("fhours", fmt, &fhours, 1, "112.876h");
|
||||
verifyFormat("fminutes", fmt, &fminutes, 1, "113.876m");
|
||||
verifyFormat("fseconds", fmt, &fseconds, 1, "114.876s");
|
||||
|
||||
Measure hoursMinutes[2] = {hours, minutes};
|
||||
verifyFormat("hoursMinutes", fmt, hoursMinutes, 2, "112:113");
|
||||
Measure hoursSeconds[2] = {hours, seconds};
|
||||
verifyFormat("hoursSeconds", fmt, hoursSeconds, 2, "112:00:114");
|
||||
Measure minutesSeconds[2] = {minutes, seconds};
|
||||
verifyFormat("minutesSeconds", fmt, minutesSeconds, 2, "113:114");
|
||||
|
||||
Measure hoursFminutes[2] = {hours, fminutes};
|
||||
verifyFormat("hoursFminutes", fmt, hoursFminutes, 2, "112:113.876");
|
||||
Measure hoursFseconds[2] = {hours, fseconds};
|
||||
verifyFormat("hoursFseconds", fmt, hoursFseconds, 2, "112:00:114.876");
|
||||
Measure minutesFseconds[2] = {minutes, fseconds};
|
||||
verifyFormat("hoursMminutesFsecondsinutes", fmt, minutesFseconds, 2, "113:114.876");
|
||||
|
||||
Measure fhoursMinutes[2] = {fhours, minutes};
|
||||
verifyFormat("fhoursMinutes", fmt, fhoursMinutes, 2, "112:113");
|
||||
Measure fhoursSeconds[2] = {fhours, seconds};
|
||||
verifyFormat("fhoursSeconds", fmt, fhoursSeconds, 2, "112:00:114");
|
||||
Measure fminutesSeconds[2] = {fminutes, seconds};
|
||||
verifyFormat("fminutesSeconds", fmt, fminutesSeconds, 2, "113:114");
|
||||
|
||||
Measure fhoursFminutes[2] = {fhours, fminutes};
|
||||
verifyFormat("fhoursFminutes", fmt, fhoursFminutes, 2, "112:113.876");
|
||||
Measure fhoursFseconds[2] = {fhours, fseconds};
|
||||
verifyFormat("fhoursFseconds", fmt, fhoursFseconds, 2, "112:00:114.876");
|
||||
Measure fminutesFseconds[2] = {fminutes, fseconds};
|
||||
verifyFormat("fminutesFseconds", fmt, fminutesFseconds, 2, "113:114.876");
|
||||
|
||||
Measure hoursMinutesSeconds[3] = {hours, minutes, seconds};
|
||||
verifyFormat("hoursMinutesSeconds", fmt, hoursMinutesSeconds, 3, "112:113:114");
|
||||
Measure fhoursFminutesFseconds[3] = {fhours, fminutes, fseconds};
|
||||
verifyFormat("fhoursFminutesFseconds", fmt, fhoursFminutesFseconds, 3, "112:113:114.876");
|
||||
}
|
||||
|
||||
void MeasureFormatTest::TestNumericTimeSomeSpecialFormats() {
|
||||
IcuTestErrorCode status(*this, "TestNumericTimeSomeSpecialFormats");
|
||||
|
||||
Measure fhours(2.8765432, MeasureUnit::createHour(status), status);
|
||||
Measure fminutes(3.8765432, MeasureUnit::createMinute(status), status);
|
||||
assertSuccess("", status);
|
||||
|
||||
Measure fhoursFminutes[2] = {fhours, fminutes};
|
||||
|
||||
// Latvian is one of the very few locales 0-padding the hour
|
||||
MeasureFormat fmtLt("lt", UMEASFMT_WIDTH_NUMERIC, status);
|
||||
verifyFormat("Latvian fhoursFminutes", fmtLt, fhoursFminutes, 2, "02:03,877");
|
||||
|
||||
// Danish is one of the very few locales using '.' as separator
|
||||
MeasureFormat fmtDa("da", UMEASFMT_WIDTH_NUMERIC, status);
|
||||
verifyFormat("Danish fhoursFminutes", fmtDa, fhoursFminutes, 2, "2.03,877");
|
||||
}
|
||||
|
||||
|
||||
void MeasureFormatTest::verifyFieldPosition(
|
||||
const char *description,
|
||||
|
@ -23,7 +23,6 @@ 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;
|
||||
@ -31,6 +30,7 @@ import java.util.MissingResourceException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.ibm.icu.impl.DontCareFieldPosition;
|
||||
import com.ibm.icu.impl.FormattedStringBuilder;
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.SimpleCache;
|
||||
@ -38,6 +38,7 @@ import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.number.LongNameHandler;
|
||||
import com.ibm.icu.impl.number.RoundingUtils;
|
||||
import com.ibm.icu.number.FormattedNumber;
|
||||
import com.ibm.icu.number.IntegerWidth;
|
||||
import com.ibm.icu.number.LocalizedNumberFormatter;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||
@ -47,7 +48,6 @@ 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;
|
||||
@ -655,28 +655,28 @@ public class MeasureFormat extends UFormat {
|
||||
}
|
||||
|
||||
static class NumericFormatters {
|
||||
private DateFormat hourMinute;
|
||||
private DateFormat minuteSecond;
|
||||
private DateFormat hourMinuteSecond;
|
||||
private String hourMinute;
|
||||
private String minuteSecond;
|
||||
private String hourMinuteSecond;
|
||||
|
||||
public NumericFormatters(
|
||||
DateFormat hourMinute,
|
||||
DateFormat minuteSecond,
|
||||
DateFormat hourMinuteSecond) {
|
||||
String hourMinute,
|
||||
String minuteSecond,
|
||||
String hourMinuteSecond) {
|
||||
this.hourMinute = hourMinute;
|
||||
this.minuteSecond = minuteSecond;
|
||||
this.hourMinuteSecond = hourMinuteSecond;
|
||||
}
|
||||
|
||||
public DateFormat getHourMinute() {
|
||||
public String getHourMinute() {
|
||||
return hourMinute;
|
||||
}
|
||||
|
||||
public DateFormat getMinuteSecond() {
|
||||
public String getMinuteSecond() {
|
||||
return minuteSecond;
|
||||
}
|
||||
|
||||
public DateFormat getHourMinuteSecond() {
|
||||
public String getHourMinuteSecond() {
|
||||
return hourMinuteSecond;
|
||||
}
|
||||
}
|
||||
@ -823,12 +823,10 @@ public class MeasureFormat extends UFormat {
|
||||
}
|
||||
|
||||
// type is one of "hm", "ms" or "hms"
|
||||
private static DateFormat loadNumericDurationFormat(ICUResourceBundle r, String type) {
|
||||
private static String 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;
|
||||
return r.getString().replace("h", "H");
|
||||
}
|
||||
|
||||
// Returns hours in [0]; minutes in [1]; seconds in [2] out of measures array. If
|
||||
@ -861,116 +859,77 @@ public class MeasureFormat extends UFormat {
|
||||
// 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) {
|
||||
String pattern;
|
||||
|
||||
// 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);
|
||||
// All possible combinations: "h", "m", "s", "hm", "hs", "ms", "hms"
|
||||
if (hms[0] != null && hms[2] != null) { // "hms" & "hs" (we add minutes if "hs")
|
||||
pattern = numericFormatters.getHourMinuteSecond();
|
||||
if (hms[1] == null)
|
||||
hms[1] = 0;
|
||||
hms[1] = Math.floor(hms[1].doubleValue());
|
||||
hms[0] = Math.floor(hms[0].doubleValue());
|
||||
} else if (hms[0] != null && hms[1] != null) { // "hm"
|
||||
pattern = numericFormatters.getHourMinute();
|
||||
hms[0] = Math.floor(hms[0].doubleValue());
|
||||
} else if (hms[1] != null && hms[2] != null) { // "ms"
|
||||
pattern = numericFormatters.getMinuteSecond();
|
||||
hms[1] = Math.floor(hms[1].doubleValue());
|
||||
} else { // h m s, handled outside formatNumeric. No value is also an error.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// We can create it on demand, but all of the patterns (right now) have mm and ss.
|
||||
// So unless it is hours only we will need a 0-padded 2 digits formatter.
|
||||
LocalizedNumberFormatter numberFormatter2 = numberFormatter.integerWidth(IntegerWidth.zeroFillTo(2));
|
||||
FormattedStringBuilder fsb = new FormattedStringBuilder();
|
||||
|
||||
boolean protect = false;
|
||||
for (int i = 0; i < pattern.length(); i++) {
|
||||
char c = pattern.charAt(i);
|
||||
|
||||
// Also set the proper field in this switch
|
||||
// We don't use DateFormat.Field because this is not a date / time, is a duration.
|
||||
Number value = 0;
|
||||
switch (c) {
|
||||
case 'H': value = hms[0]; break;
|
||||
case 'm': value = hms[1]; break;
|
||||
case 's': value = hms[2]; break;
|
||||
}
|
||||
}
|
||||
// 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.nextFieldPosition(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();
|
||||
// There is not enough info to add Field(s) for the unit because all we have are plain
|
||||
// text patterns. For example in "21:51" there is no text for something like "hour",
|
||||
// while in something like "21h51" there is ("h"). But we can't really tell...
|
||||
switch (c) {
|
||||
case 'H':
|
||||
case 'm':
|
||||
case 's':
|
||||
if (protect) {
|
||||
fsb.appendCodePoint(c, null);
|
||||
} else {
|
||||
if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) { // doubled
|
||||
fsb.append(numberFormatter2.format(value), null); // TODO: Use proper Field
|
||||
i++;
|
||||
} else {
|
||||
fsb.append(numberFormatter.format(value), null); // TODO: Use proper Field
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
// '' is escaped apostrophe
|
||||
if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) {
|
||||
fsb.appendCodePoint(c, null);
|
||||
i++;
|
||||
} else {
|
||||
protect = !protect;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fsb.appendCodePoint(c, null);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
appendable.append(fsb);
|
||||
} catch (IOException e) {
|
||||
throw new ICUUncheckedIOException(e);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
@ -1623,6 +1624,30 @@ public class MeasureUnitTest extends TestFmwk {
|
||||
{_0h_0m_17s, "0:00:17"},
|
||||
{_6h_56_92m, "6:56,92"},
|
||||
{_3h_5h, "3 Std., 5 Std."}};
|
||||
Object[][] numericDataBn = {
|
||||
{_1m_59_9996s, "১:৫৯.৯৯৯৬"},
|
||||
{_19m, "১৯ মিঃ"},
|
||||
{_1h_23_5s, "১:০০:২৩.৫"},
|
||||
{_1h_0m_23s, "১:০০:২৩"},
|
||||
{_1h_23_5m, "১:২৩.৫"},
|
||||
{_5h_17m, "৫:১৭"},
|
||||
{_19m_28s, "১৯:২৮"},
|
||||
{_2y_5M_3w_4d, "২ বছর, ৫ মাস, ৩ সপ্তাহ, ৪ দিন"},
|
||||
{_0h_0m_17s, "০:০০:১৭"},
|
||||
{_6h_56_92m, "৬:৫৬.৯২"},
|
||||
{_3h_5h, "৩ ঘঃ, ৫ ঘঃ"}};
|
||||
Object[][] numericDataBnLatn = {
|
||||
{_1m_59_9996s, "1:59.9996"},
|
||||
{_19m, "19 মিঃ"},
|
||||
{_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 বছর, 5 মাস, 3 সপ্তাহ, 4 দিন"},
|
||||
{_0h_0m_17s, "0:00:17"},
|
||||
{_6h_56_92m, "6:56.92"},
|
||||
{_3h_5h, "3 ঘঃ, 5 ঘঃ"}};
|
||||
|
||||
NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH);
|
||||
nf.setMaximumFractionDigits(4);
|
||||
@ -1650,6 +1675,17 @@ public class MeasureUnitTest extends TestFmwk {
|
||||
mf = MeasureFormat.getInstance(Locale.GERMAN, FormatWidth.NUMERIC, nf);
|
||||
verifyFormatPeriod("de NUMERIC(Java Locale)", mf, numericDataDe);
|
||||
|
||||
ULocale bengali = ULocale.forLanguageTag("bn");
|
||||
nf = NumberFormat.getNumberInstance(bengali);
|
||||
nf.setMaximumFractionDigits(4);
|
||||
mf = MeasureFormat.getInstance(bengali, FormatWidth.NUMERIC, nf);
|
||||
verifyFormatPeriod("bn NUMERIC(Java Locale)", mf, numericDataBn);
|
||||
|
||||
bengali = ULocale.forLanguageTag("bn-u-nu-latn");
|
||||
nf = NumberFormat.getNumberInstance(bengali);
|
||||
nf.setMaximumFractionDigits(4);
|
||||
mf = MeasureFormat.getInstance(bengali, FormatWidth.NUMERIC, nf);
|
||||
verifyFormatPeriod("bn NUMERIC(Java Locale)", mf, numericDataBnLatn);
|
||||
}
|
||||
|
||||
private void verifyFormatPeriod(String desc, MeasureFormat mf, Object[][] testData) {
|
||||
@ -2979,4 +3015,65 @@ public class MeasureUnitTest extends TestFmwk {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestNumericTimeNonLatin() {
|
||||
ULocale ulocale = ULocale.forLanguageTag("bn");
|
||||
MeasureFormat fmt = MeasureFormat.getInstance(ulocale, FormatWidth.NUMERIC);
|
||||
String actual = fmt.formatMeasures(new Measure(12, MeasureUnit.MINUTE), new Measure(39.12345, MeasureUnit.SECOND));
|
||||
assertEquals("Incorect digits", "১২:৩৯.১২৩", actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestNumericTime() {
|
||||
MeasureFormat fmt = MeasureFormat.getInstance(ULocale.forLanguageTag("en"), FormatWidth.NUMERIC);
|
||||
|
||||
Measure hours = new Measure(112, MeasureUnit.HOUR);
|
||||
Measure minutes = new Measure(113, MeasureUnit.MINUTE);
|
||||
Measure seconds = new Measure(114, MeasureUnit.SECOND);
|
||||
Measure fhours = new Measure(112.8765, MeasureUnit.HOUR);
|
||||
Measure fminutes = new Measure(113.8765, MeasureUnit.MINUTE);
|
||||
Measure fseconds = new Measure(114.8765, MeasureUnit.SECOND);
|
||||
|
||||
Assert.assertEquals("112h", fmt.formatMeasures(hours));
|
||||
Assert.assertEquals("113m", fmt.formatMeasures(minutes));
|
||||
Assert.assertEquals("114s", fmt.formatMeasures(seconds));
|
||||
|
||||
Assert.assertEquals("112.876h", fmt.formatMeasures(fhours));
|
||||
Assert.assertEquals("113.876m", fmt.formatMeasures(fminutes));
|
||||
Assert.assertEquals("114.876s", fmt.formatMeasures(fseconds));
|
||||
|
||||
Assert.assertEquals("112:113", fmt.formatMeasures(hours, minutes));
|
||||
Assert.assertEquals("112:00:114", fmt.formatMeasures(hours, seconds));
|
||||
Assert.assertEquals("113:114", fmt.formatMeasures(minutes, seconds));
|
||||
|
||||
Assert.assertEquals("112:113.876", fmt.formatMeasures(hours, fminutes));
|
||||
Assert.assertEquals("112:00:114.876", fmt.formatMeasures(hours, fseconds));
|
||||
Assert.assertEquals("113:114.876", fmt.formatMeasures(minutes, fseconds));
|
||||
|
||||
Assert.assertEquals("112:113", fmt.formatMeasures(fhours, minutes));
|
||||
Assert.assertEquals("112:00:114", fmt.formatMeasures(fhours, seconds));
|
||||
Assert.assertEquals("113:114", fmt.formatMeasures(fminutes, seconds));
|
||||
|
||||
Assert.assertEquals("112:113.876", fmt.formatMeasures(fhours, fminutes));
|
||||
Assert.assertEquals("112:00:114.876", fmt.formatMeasures(fhours, fseconds));
|
||||
Assert.assertEquals("113:114.876", fmt.formatMeasures(fminutes, fseconds));
|
||||
|
||||
Assert.assertEquals("112:113:114", fmt.formatMeasures(hours, minutes, seconds));
|
||||
Assert.assertEquals("112:113:114.876", fmt.formatMeasures(fhours, fminutes, fseconds));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestNumericTimeSomeSpecialFormats() {
|
||||
Measure fhours = new Measure(2.8765432, MeasureUnit.HOUR);
|
||||
Measure fminutes = new Measure(3.8765432, MeasureUnit.MINUTE);
|
||||
|
||||
// Latvian is one of the very few locales 0-padding the hour
|
||||
MeasureFormat fmt = MeasureFormat.getInstance(ULocale.forLanguageTag("lt"), FormatWidth.NUMERIC);
|
||||
Assert.assertEquals("02:03,877", fmt.formatMeasures(fhours, fminutes));
|
||||
|
||||
// Danish is one of the very few locales using '.' as separator
|
||||
fmt = MeasureFormat.getInstance(ULocale.forLanguageTag("da"), FormatWidth.NUMERIC);
|
||||
Assert.assertEquals("2.03,877", fmt.formatMeasures(fhours, fminutes));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user