X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/4388f060552cc537e71e957d32f35e9d75a61233..c5116b9f5a666b9d59f443b3770acd6ef64dc6c3:/icuSources/test/intltest/tufmtts.cpp diff --git a/icuSources/test/intltest/tufmtts.cpp b/icuSources/test/intltest/tufmtts.cpp index 90efa96f..91a26678 100644 --- a/icuSources/test/intltest/tufmtts.cpp +++ b/icuSources/test/intltest/tufmtts.cpp @@ -1,5 +1,7 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /******************************************************************** - * Copyright (c) 2008-2011, International Business Machines Corporation and + * Copyright (c) 2008-2016, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -7,11 +9,13 @@ #if !UCONFIG_NO_FORMATTING +#include "unicode/decimfmt.h" #include "unicode/tmunit.h" #include "unicode/tmutamt.h" #include "unicode/tmutfmt.h" -#include "tufmtts.h" #include "unicode/ustring.h" +#include "cmemory.h" +#include "intltest.h" //TODO: put as compilation flag //#define TUFMTTS_DEBUG 1 @@ -20,14 +24,77 @@ #include #endif -void TimeUnitTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) { - if (exec) logln("TestSuite TimeUnitTest"); - switch (index) { - TESTCASE(0, testBasic); - TESTCASE(1, testAPI); - TESTCASE(2, testGreek); - default: name = ""; break; +class TimeUnitTest : public IntlTest { + void runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/ ) { + if (exec) logln("TestSuite TimeUnitTest"); + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(testBasic); + TESTCASE_AUTO(testAPI); + TESTCASE_AUTO(testGreekWithFallback); + TESTCASE_AUTO(testGreekWithSanitization); + TESTCASE_AUTO(test10219Plurals); + TESTCASE_AUTO(TestBritishShortHourFallback); + TESTCASE_AUTO_END; } + +public: + /** + * Performs basic tests + **/ + void testBasic(); + + /** + * Performs API tests + **/ + void testAPI(); + + /** + * Performs tests for Greek + * This tests that requests for short unit names correctly fall back + * to long unit names for a locale where the locale data does not + * provide short unit names. As of CLDR 1.9, Greek is one such language. + **/ + void testGreekWithFallback(); + + /** + * Performs tests for Greek + * This tests that if the plural count listed in time unit format does not + * match those in the plural rules for the locale, those plural count in + * time unit format will be ingored and subsequently, fall back will kick in + * which is tested above. + * Without data sanitization, setNumberFormat() would crash. + * As of CLDR shiped in ICU4.8, Greek is one such language. + */ + void testGreekWithSanitization(); + + /** + * Performs unit test for ticket 10219 making sure that plurals work + * correctly with rounding. + */ + void test10219Plurals(); + + void TestBritishShortHourFallback(); +}; + +extern IntlTest *createTimeUnitTest() { + return new TimeUnitTest(); +} + +// This function is more lenient than equals operator as it considers integer 3 hours and +// double 3.0 hours to be equal +static UBool tmaEqual(const TimeUnitAmount& left, const TimeUnitAmount& right) { + if (left.getTimeUnitField() != right.getTimeUnitField()) { + return FALSE; + } + UErrorCode status = U_ZERO_ERROR; + if (!left.getNumber().isNumeric() || !right.getNumber().isNumeric()) { + return FALSE; + } + UBool result = left.getNumber().getDouble(status) == right.getNumber().getDouble(status); + if (U_FAILURE(status)) { + return FALSE; + } + return result; } /** @@ -36,7 +103,7 @@ void TimeUnitTest::runIndexedTest( int32_t index, UBool exec, const char* &name, void TimeUnitTest::testBasic() { const char* locales[] = {"en", "sl", "fr", "zh", "ar", "ru", "zh_Hant", "pa"}; for ( unsigned int locIndex = 0; - locIndex < sizeof(locales)/sizeof(locales[0]); + locIndex < UPRV_LENGTHOF(locales); ++locIndex ) { UErrorCode status = U_ZERO_ERROR; Locale loc(locales[locIndex]); @@ -58,7 +125,7 @@ void TimeUnitTest::testBasic() { std::cout << "time unit: " << j << "\n"; #endif double tests[] = {0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 5, 10, 100, 101.35}; - for (unsigned int i = 0; i < sizeof(tests)/sizeof(tests[0]); ++i) { + for (unsigned int i = 0; i < UPRV_LENGTHOF(tests); ++i) { #ifdef TUFMTTS_DEBUG std::cout << "number: " << tests[i] << "\n"; #endif @@ -77,14 +144,14 @@ void TimeUnitTest::testBasic() { Formattable result; ((Format*)formats[style])->parseObject(formatted, result, status); if (!assertSuccess("parseObject()", status)) return; - if (result != formattable) { + if (!tmaEqual(*((TimeUnitAmount *)result.getObject()), *((TimeUnitAmount *) formattable.getObject()))) { dataerrln("No round trip: "); } // other style parsing Formattable result_1; ((Format*)formats[1-style])->parseObject(formatted, result_1, status); if (!assertSuccess("parseObject()", status)) return; - if (result_1 != formattable) { + if (!tmaEqual(*((TimeUnitAmount *)result_1.getObject()), *((TimeUnitAmount *) formattable.getObject()))) { dataerrln("No round trip: "); } } @@ -116,10 +183,39 @@ void TimeUnitTest::testAPI() { TimeUnit::UTimeUnitFields field = tmunit_m->getTimeUnitField(); assertTrue("field of month time unit is month", (field == TimeUnit::UTIMEUNIT_MONTH)); - + + //===== Interoperability with MeasureUnit ====== + MeasureUnit **ptrs = new MeasureUnit *[TimeUnit::UTIMEUNIT_FIELD_COUNT]; + + ptrs[TimeUnit::UTIMEUNIT_YEAR] = MeasureUnit::createYear(status); + ptrs[TimeUnit::UTIMEUNIT_MONTH] = MeasureUnit::createMonth(status); + ptrs[TimeUnit::UTIMEUNIT_DAY] = MeasureUnit::createDay(status); + ptrs[TimeUnit::UTIMEUNIT_WEEK] = MeasureUnit::createWeek(status); + ptrs[TimeUnit::UTIMEUNIT_HOUR] = MeasureUnit::createHour(status); + ptrs[TimeUnit::UTIMEUNIT_MINUTE] = MeasureUnit::createMinute(status); + ptrs[TimeUnit::UTIMEUNIT_SECOND] = MeasureUnit::createSecond(status); + if (!assertSuccess("TimeUnit::createInstance", status)) return; + + for (TimeUnit::UTimeUnitFields j = TimeUnit::UTIMEUNIT_YEAR; + j < TimeUnit::UTIMEUNIT_FIELD_COUNT; + j = (TimeUnit::UTimeUnitFields)(j+1)) { + MeasureUnit *ptr = TimeUnit::createInstance(j, status); + if (!assertSuccess("TimeUnit::createInstance", status)) return; + // We have to convert *ptr to a MeasureUnit or else == will fail over + // differing types (TimeUnit vs. MeasureUnit). + assertTrue( + "Time unit should be equal to corresponding MeasureUnit", + MeasureUnit(*ptr) == *ptrs[j]); + delete ptr; + } delete tmunit; delete another; delete tmunit_m; + for (int i = 0; i < TimeUnit::UTIMEUNIT_FIELD_COUNT; ++i) { + delete ptrs[i]; + } + delete [] ptrs; + // //================= TimeUnitAmount ================= @@ -208,7 +304,7 @@ void TimeUnitTest::testAPI() { * to long unit names for a locale where the locale data does not * provide short unit names. As of CLDR 1.9, Greek is one such language. */ -void TimeUnitTest::testGreek() { +void TimeUnitTest::testGreekWithFallback() { UErrorCode status = U_ZERO_ERROR; const char* locales[] = {"el-GR", "el"}; @@ -217,57 +313,77 @@ void TimeUnitTest::testGreek() { const int numbers[] = {1, 7}; const UChar oneSecond[] = {0x0031, 0x0020, 0x03b4, 0x03b5, 0x03c5, 0x03c4, 0x03b5, 0x03c1, 0x03cc, 0x03bb, 0x03b5, 0x03c0, 0x03c4, 0x03bf, 0}; + const UChar oneSecondShort[] = {0x0031, 0x0020, 0x03b4, 0x03b5, 0x03c5, 0x03c4, 0x002e, 0}; const UChar oneMinute[] = {0x0031, 0x0020, 0x03bb, 0x03b5, 0x03c0, 0x03c4, 0x03cc, 0}; + const UChar oneMinuteShort[] = {0x0031, 0x0020, 0x03bb, 0x03b5, 0x03c0, 0x002e, 0}; const UChar oneHour[] = {0x0031, 0x0020, 0x03ce, 0x03c1, 0x03b1, 0}; const UChar oneDay[] = {0x0031, 0x0020, 0x03b7, 0x03bc, 0x03ad, 0x03c1, 0x03b1, 0}; const UChar oneMonth[] = {0x0031, 0x0020, 0x03bc, 0x03ae, 0x03bd, 0x03b1, 0x03c2, 0}; + const UChar oneMonthShort[] = {0x0031, 0x0020, 0x03bc, 0x03ae, 0x03bd, 0x002e, 0}; const UChar oneYear[] = {0x0031, 0x0020, 0x03ad, 0x03c4, 0x03bf, 0x03c2, 0}; + const UChar oneYearShort[] = {0x0031, 0x0020, 0x03ad, 0x03c4, 0x002e, 0}; const UChar sevenSeconds[] = {0x0037, 0x0020, 0x03b4, 0x03b5, 0x03c5, 0x03c4, 0x03b5, 0x03c1, 0x03cc, 0x03bb, 0x03b5, 0x03c0, 0x03c4, 0x03b1, 0}; + const UChar sevenSecondsShort[] = {0x0037, 0x0020, 0x03b4, 0x03b5, 0x03c5, 0x03c4, 0x002e, 0}; const UChar sevenMinutes[] = {0x0037, 0x0020, 0x03bb, 0x03b5, 0x03c0, 0x03c4, 0x03ac, 0}; + const UChar sevenMinutesShort[] = {0x0037, 0x0020, 0x03bb, 0x03b5, 0x03c0, 0x002e, 0}; const UChar sevenHours[] = {0x0037, 0x0020, 0x03ce, 0x03c1, 0x03b5, 0x03c2, 0}; + const UChar sevenHoursShort[] = {0x0037, 0x0020, 0x03ce, 0x03c1, 0x002e, 0}; const UChar sevenDays[] = {0x0037, 0x0020, 0x03b7, 0x03bc, 0x03ad, 0x03c1, 0x03b5, 0x03c2, 0}; const UChar sevenMonths[] = {0x0037, 0x0020, 0x03bc, 0x03ae, 0x03bd, 0x03b5, 0x3c2, 0}; + const UChar sevenMonthsShort[] = {0x0037, 0x0020, 0x03bc, 0x03ae, 0x03bd, 0x002e, 0}; const UChar sevenYears[] = {0x0037, 0x0020, 0x03ad, 0x03c4, 0x03b7, 0}; + const UChar sevenYearsShort[] = {0x0037, 0x0020, 0x03ad, 0x03c4, 0x002e, 0}; const UnicodeString oneSecondStr(oneSecond); + const UnicodeString oneSecondShortStr(oneSecondShort); const UnicodeString oneMinuteStr(oneMinute); + const UnicodeString oneMinuteShortStr(oneMinuteShort); const UnicodeString oneHourStr(oneHour); const UnicodeString oneDayStr(oneDay); const UnicodeString oneMonthStr(oneMonth); + const UnicodeString oneMonthShortStr(oneMonthShort); const UnicodeString oneYearStr(oneYear); + const UnicodeString oneYearShortStr(oneYearShort); const UnicodeString sevenSecondsStr(sevenSeconds); + const UnicodeString sevenSecondsShortStr(sevenSecondsShort); const UnicodeString sevenMinutesStr(sevenMinutes); + const UnicodeString sevenMinutesShortStr(sevenMinutesShort); const UnicodeString sevenHoursStr(sevenHours); + const UnicodeString sevenHoursShortStr(sevenHoursShort); const UnicodeString sevenDaysStr(sevenDays); const UnicodeString sevenMonthsStr(sevenMonths); + const UnicodeString sevenMonthsShortStr(sevenMonthsShort); const UnicodeString sevenYearsStr(sevenYears); + const UnicodeString sevenYearsShortStr(sevenYearsShort); - const UnicodeString expected[] = {oneSecondStr, oneMinuteStr, oneHourStr, oneDayStr, oneMonthStr, oneYearStr, - oneSecondStr, oneMinuteStr, oneHourStr, oneDayStr, oneMonthStr, oneYearStr, - sevenSecondsStr, sevenMinutesStr, sevenHoursStr, sevenDaysStr, sevenMonthsStr, sevenYearsStr, - sevenSecondsStr, sevenMinutesStr, sevenHoursStr, sevenDaysStr, sevenMonthsStr, sevenYearsStr, - oneSecondStr, oneMinuteStr, oneHourStr, oneDayStr, oneMonthStr, oneYearStr, - oneSecondStr, oneMinuteStr, oneHourStr, oneDayStr, oneMonthStr, oneYearStr, - sevenSecondsStr, sevenMinutesStr, sevenHoursStr, sevenDaysStr, sevenMonthsStr, sevenYearsStr, - sevenSecondsStr, sevenMinutesStr, sevenHoursStr, sevenDaysStr, sevenMonthsStr, sevenYearsStr}; + const UnicodeString expected[] = { + oneSecondStr, oneMinuteStr, oneHourStr, oneDayStr, oneMonthStr, oneYearStr, + oneSecondShortStr, oneMinuteShortStr, oneHourStr, oneDayStr, oneMonthShortStr, oneYearShortStr, + sevenSecondsStr, sevenMinutesStr, sevenHoursStr, sevenDaysStr, sevenMonthsStr, sevenYearsStr, + sevenSecondsShortStr, sevenMinutesShortStr, sevenHoursShortStr, sevenDaysStr, sevenMonthsShortStr, sevenYearsShortStr, + + oneSecondStr, oneMinuteStr, oneHourStr, oneDayStr, oneMonthStr, oneYearStr, + oneSecondShortStr, oneMinuteShortStr, oneHourStr, oneDayStr, oneMonthShortStr, oneYearShortStr, + sevenSecondsStr, sevenMinutesStr, sevenHoursStr, sevenDaysStr, sevenMonthsStr, sevenYearsStr, + sevenSecondsShortStr, sevenMinutesShortStr, sevenHoursShortStr, sevenDaysStr, sevenMonthsShortStr, sevenYearsShortStr}; int counter = 0; for ( unsigned int locIndex = 0; - locIndex < sizeof(locales)/sizeof(locales[0]); + locIndex < UPRV_LENGTHOF(locales); ++locIndex ) { Locale l = Locale::createFromName(locales[locIndex]); for ( unsigned int numberIndex = 0; - numberIndex < sizeof(numbers)/sizeof(int); + numberIndex < UPRV_LENGTHOF(numbers); ++numberIndex ) { for ( unsigned int styleIndex = 0; - styleIndex < sizeof(styles)/sizeof(styles[0]); + styleIndex < UPRV_LENGTHOF(styles); ++styleIndex ) { for ( unsigned int unitIndex = 0; - unitIndex < sizeof(tunits)/sizeof(tunits[0]); + unitIndex < UPRV_LENGTHOF(tunits); ++unitIndex ) { TimeUnitAmount *tamt = new TimeUnitAmount(numbers[numberIndex], tunits[unitIndex], status); @@ -323,4 +439,92 @@ void TimeUnitTest::testGreek() { } } +// Test bug9042 +void TimeUnitTest::testGreekWithSanitization() { + + UErrorCode status = U_ZERO_ERROR; + Locale elLoc("el"); + NumberFormat* numberFmt = NumberFormat::createInstance(Locale("el"), status); + if (!assertSuccess("NumberFormat::createInstance for el locale", status, TRUE)) return; + numberFmt->setMaximumFractionDigits(1); + + TimeUnitFormat* timeUnitFormat = new TimeUnitFormat(elLoc, status); + if (!assertSuccess("TimeUnitFormat::TimeUnitFormat for el locale", status)) return; + + timeUnitFormat->setNumberFormat(*numberFmt, status); + + delete numberFmt; + delete timeUnitFormat; +} + +void TimeUnitTest::test10219Plurals() { + Locale usLocale("en_US"); + double values[2] = {1.588, 1.011}; + UnicodeString expected[2][3] = { + {"1 minute", "1.5 minutes", "1.58 minutes"}, + {"1 minute", "1.0 minutes", "1.01 minutes"} + }; + UErrorCode status = U_ZERO_ERROR; + TimeUnitFormat tuf(usLocale, status); + if (U_FAILURE(status)) { + dataerrln("generating TimeUnitFormat Object failed: %s", u_errorName(status)); + return; + } + LocalPointer nf((DecimalFormat *) NumberFormat::createInstance(usLocale, status)); + if (U_FAILURE(status)) { + dataerrln("generating NumberFormat Object failed: %s", u_errorName(status)); + return; + } + for (int32_t j = 0; j < UPRV_LENGTHOF(values); ++j) { + for (int32_t i = 0; i < UPRV_LENGTHOF(expected[j]); ++i) { + nf->setMinimumFractionDigits(i); + nf->setMaximumFractionDigits(i); + nf->setRoundingMode(DecimalFormat::kRoundDown); + tuf.setNumberFormat(*nf, status); + if (U_FAILURE(status)) { + dataerrln("setting NumberFormat failed: %s", u_errorName(status)); + return; + } + UnicodeString actual; + Formattable fmt; + LocalPointer tamt( + new TimeUnitAmount(values[j], TimeUnit::UTIMEUNIT_MINUTE, status), status); + if (U_FAILURE(status)) { + dataerrln("generating TimeUnitAmount Object failed: %s", u_errorName(status)); + return; + } + fmt.adoptObject(tamt.orphan()); + tuf.format(fmt, actual, status); + if (U_FAILURE(status)) { + dataerrln("Actual formatting failed: %s", u_errorName(status)); + return; + } + if (expected[j][i] != actual) { + errln("Expected " + expected[j][i] + ", got " + actual); + } + } + } + + // test parsing + Formattable result; + ParsePosition pos; + UnicodeString formattedString = "1 minutes"; + tuf.parseObject(formattedString, result, pos); + if (formattedString.length() != pos.getIndex()) { + errln("Expect parsing to go all the way to the end of the string."); + } +} + +void TimeUnitTest::TestBritishShortHourFallback() { + // See ticket #11986 "incomplete fallback in MeasureFormat". + UErrorCode status = U_ZERO_ERROR; + Formattable oneHour(new TimeUnitAmount(1, TimeUnit::UTIMEUNIT_HOUR, status)); + Locale en_GB("en_GB"); + TimeUnitFormat formatter(en_GB, UTMUTFMT_ABBREVIATED_STYLE, status); + UnicodeString result; + formatter.format(oneHour, result, status); + assertSuccess("TestBritishShortHourFallback()", status); + assertEquals("TestBritishShortHourFallback()", UNICODE_STRING_SIMPLE("1 hr"), result, TRUE); +} + #endif