ICU-9622 Adding support for date/time skeletons

This commit is contained in:
Mihai Nita 2018-12-12 10:22:04 -08:00 committed by Markus Scherer
parent 8aa5d23d7d
commit 2666d18e54
8 changed files with 166 additions and 38 deletions

View file

@ -173,6 +173,16 @@ PatternProps::skipWhiteSpace(const UChar *s, int32_t length) {
return s;
}
int32_t
PatternProps::skipWhiteSpace(const UnicodeString& s, int32_t start) {
int32_t i = start;
int32_t length = s.length();
while(i<length && isWhiteSpace(s.charAt(i))) {
++i;
}
return i;
}
const UChar *
PatternProps::trimWhiteSpace(const UChar *s, int32_t &length) {
if(length<=0 || (!isWhiteSpace(s[0]) && !isWhiteSpace(s[length-1]))) {

View file

@ -17,6 +17,7 @@
#ifndef __PATTERNPROPS_H__
#define __PATTERNPROPS_H__
#include "unicode/unistr.h"
#include "unicode/utypes.h"
U_NAMESPACE_BEGIN
@ -63,6 +64,12 @@ public:
*/
static const UChar *skipWhiteSpace(const UChar *s, int32_t length);
/**
* Skips over Pattern_White_Space starting at index start in s.
* @return The smallest index at or after start with a non-white space character.
*/
static int32_t skipWhiteSpace(const UnicodeString &s, int32_t start);
/**
* @return s except with leading and trailing Pattern_White_Space removed and length adjusted.
*/

View file

@ -1673,7 +1673,6 @@ void MessageFormat::cacheExplicitFormats(UErrorCode& status) {
}
}
Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeString& style,
Formattable::Type& formattableType, UParseError& parseError,
UErrorCode& ec) {
@ -1683,6 +1682,7 @@ Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeStrin
Format* fmt = NULL;
int32_t typeID, styleID;
DateFormat::EStyle date_style;
int32_t firstNonSpace;
switch (typeID = findKeyword(type, TYPE_IDS)) {
case 0: // number
@ -1702,11 +1702,10 @@ Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeStrin
fmt = createIntegerFormat(fLocale, ec);
break;
default: // pattern or skeleton
int32_t i = 0;
for (; PatternProps::isWhiteSpace(style.charAt(i)); i++);
if (style.compare(i, 2, u"::", 0, 2) == 0) {
firstNonSpace = PatternProps::skipWhiteSpace(style, 0);
if (style.compare(firstNonSpace, 2, u"::", 0, 2) == 0) {
// Skeleton
UnicodeString skeleton = style.tempSubString(i + 2);
UnicodeString skeleton = style.tempSubString(firstNonSpace + 2);
fmt = number::NumberFormatter::forSkeleton(skeleton, ec).locale(fLocale).toFormat(ec);
} else {
// Pattern
@ -1725,19 +1724,27 @@ Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeStrin
case 1: // date
case 2: // time
formattableType = Formattable::kDate;
styleID = findKeyword(style, DATE_STYLE_IDS);
date_style = (styleID >= 0) ? DATE_STYLES[styleID] : DateFormat::kDefault;
if (typeID == 1) {
fmt = DateFormat::createDateInstance(date_style, fLocale);
firstNonSpace = PatternProps::skipWhiteSpace(style, 0);
if (style.compare(firstNonSpace, 2, u"::", 0, 2) == 0) {
// Skeleton
UnicodeString skeleton = style.tempSubString(firstNonSpace + 2);
fmt = DateFormat::createInstanceForSkeleton(skeleton, fLocale, ec);
} else {
fmt = DateFormat::createTimeInstance(date_style, fLocale);
}
// Pattern
styleID = findKeyword(style, DATE_STYLE_IDS);
date_style = (styleID >= 0) ? DATE_STYLES[styleID] : DateFormat::kDefault;
if (styleID < 0 && fmt != NULL) {
SimpleDateFormat* sdtfmt = dynamic_cast<SimpleDateFormat*>(fmt);
if (sdtfmt != NULL) {
sdtfmt->applyPattern(style);
if (typeID == 1) {
fmt = DateFormat::createDateInstance(date_style, fLocale);
} else {
fmt = DateFormat::createTimeInstance(date_style, fLocale);
}
if (styleID < 0 && fmt != NULL) {
SimpleDateFormat* sdtfmt = dynamic_cast<SimpleDateFormat*>(fmt);
if (sdtfmt != NULL) {
sdtfmt->applyPattern(style);
}
}
}
break;

View file

@ -204,6 +204,9 @@ class NumberFormat;
* <td><i>argStyleText</i>
* <td><code>new SimpleDateFormat(argStyleText, getLocale(), status)</code>
* <tr>
* <td><i>argSkeletonText</i>
* <td><code>DateFormat::createInstanceForSkeleton(argSkeletonText, getLocale(), status)</code>
* <tr>
* <td rowspan=6><code>time</code>
* <td><i>(none)</i>
* <td><code>DateFormat.createTimeInstance(kDefault, getLocale(), status)</code>
@ -994,6 +997,8 @@ private:
void cacheExplicitFormats(UErrorCode& status);
int32_t skipLeadingSpaces(UnicodeString& style);
Format* createAppropriateFormat(UnicodeString& type,
UnicodeString& style,
Formattable::Type& formattableType,

View file

@ -21,6 +21,7 @@
#include "tmsgfmt.h"
#include "cmemory.h"
#include "loctest.h"
#include "unicode/format.h"
#include "unicode/decimfmt.h"
@ -72,6 +73,8 @@ TestMessageFormat::runIndexedTest(int32_t index, UBool exec,
TESTCASE_AUTO(TestDecimals);
TESTCASE_AUTO(TestArgIsPrefixOfAnother);
TESTCASE_AUTO(TestMessageFormatNumberSkeleton);
TESTCASE_AUTO(TestMessageFormatDateSkeleton);
TESTCASE_AUTO(TestMessageFormatTimeSkeleton);
TESTCASE_AUTO_END;
}
@ -1085,7 +1088,7 @@ void TestMessageFormat::testFormat()
result = msg.format(
*fmt,
result,
//FieldPosition(0),
//FieldPosition(FieldPosition::DONT_CARE),
fp,
err);
@ -1099,7 +1102,7 @@ void TestMessageFormat::testFormat()
result = msg.format(
ft_arr,
result,
//FieldPosition(0),
//FieldPosition(FieldPosition::DONT_CARE),
fp,
err);
@ -2020,7 +2023,7 @@ void TestMessageFormat::TestMessageFormatNumberSkeleton() {
status.setScope(cas.messagePattern);
MessageFormat msgf(cas.messagePattern, cas.localeName, status);
UnicodeString sb;
FieldPosition fpos(0);
FieldPosition fpos(FieldPosition::DONT_CARE);
Formattable argsArray[] = {{cas.arg}};
Formattable args(argsArray, 1);
msgf.format(args, sb, status);
@ -2029,4 +2032,47 @@ void TestMessageFormat::TestMessageFormatNumberSkeleton() {
}
}
void TestMessageFormat::doTheRealDateTimeSkeletonTesting(UDate testDate,
const char16_t* messagePattern, const char* localeName, const char16_t* expected,
IcuTestErrorCode& status) {
status.setScope(messagePattern);
MessageFormat msgf(messagePattern, localeName, status);
UnicodeString sb;
FieldPosition fpos(FieldPosition::DONT_CARE);
Formattable argsArray[] = { Formattable(testDate, Formattable::kIsDate) };
Formattable args(argsArray, 1);
msgf.format(args, sb, status);
assertEquals(messagePattern, expected, sb);
}
void TestMessageFormat::TestMessageFormatDateSkeleton() {
IcuTestErrorCode status(*this, "TestMessageFormatDateSkeleton");
UDate date = LocaleTest::date(2021-1900, UCAL_NOVEMBER, 23, 16, 42, 55);
doTheRealDateTimeSkeletonTesting(date, u"{0,date,::MMMMd}", "en", u"November 23", status);
doTheRealDateTimeSkeletonTesting(date, u"{0,date,::yMMMMdjm}", "en", u"November 23, 2021, 4:42 PM", status);
doTheRealDateTimeSkeletonTesting(date, u"{0,date, :: yMMMMd }", "en", u"November 23, 2021", status);
doTheRealDateTimeSkeletonTesting(date, u"{0,date,::yMMMMd}", "fr", u"23 novembre 2021", status);
doTheRealDateTimeSkeletonTesting(date, u"Expiration: {0,date,::yMMM}!", "en", u"Expiration: Nov 2021!", status);
// pattern literal
doTheRealDateTimeSkeletonTesting(date, u"{0,date,'::'yMMMMd}", "en", u"::2021November23", status);
}
void TestMessageFormat::TestMessageFormatTimeSkeleton() {
IcuTestErrorCode status(*this, "TestMessageFormatTimeSkeleton");
UDate date = LocaleTest::date(2021-1900, UCAL_NOVEMBER, 23, 16, 42, 55);
doTheRealDateTimeSkeletonTesting(date, u"{0,time,::MMMMd}", "en", u"November 23", status);
doTheRealDateTimeSkeletonTesting(date, u"{0,time,::yMMMMdjm}", "en", u"November 23, 2021, 4:42 PM", status);
doTheRealDateTimeSkeletonTesting(date, u"{0,time, :: yMMMMd }", "en", u"November 23, 2021", status);
doTheRealDateTimeSkeletonTesting(date, u"{0,time,::yMMMMd}", "fr", u"23 novembre 2021", status);
doTheRealDateTimeSkeletonTesting(date, u"Expiration: {0,time,::yMMM}!", "en", u"Expiration: Nov 2021!", status);
// pattern literal
doTheRealDateTimeSkeletonTesting(date, u"{0,time,'::'yMMMMd}", "en", u"::2021November23", status);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -122,9 +122,14 @@ public:
void TestDecimals();
void TestArgIsPrefixOfAnother();
void TestMessageFormatNumberSkeleton();
void TestMessageFormatDateSkeleton();
void TestMessageFormatTimeSkeleton();
private:
UnicodeString GetPatternAndSkipSyntax(const MessagePattern& pattern);
void doTheRealDateTimeSkeletonTesting(UDate testDate,
const char16_t* messagePattern, const char* localeName, const char16_t* expected,
IcuTestErrorCode& status);
};
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -202,6 +202,9 @@ import com.ibm.icu.util.ULocale.Category;
* <td><i>argStyleText</i>
* <td><code>new SimpleDateFormat(argStyleText, getLocale())</code>
* <tr>
* <td><i>argSkeletonText</i>
* <td><code>DateFormat.getInstanceForSkeleton(argSkeletonText, getLocale())</code>
* <tr>
* <td rowspan=6><code>time</code>
* <td><i>(none)</i>
* <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
@ -2188,6 +2191,16 @@ public class MessageFormat extends UFormat {
DATE_MODIFIER_LONG = 3,
DATE_MODIFIER_FULL = 4;
Format dateTimeFormatForPatternOrSkeleton(String style) {
// Ignore leading whitespace when looking for "::", the skeleton signal sequence
int i = PatternProps.skipWhiteSpace(style, 0);
if (style.regionMatches(i, "::", 0, 2)) { // Skeleton
return DateFormat.getInstanceForSkeleton(style.substring(i + 2), ulocale);
} else { // Pattern
return new SimpleDateFormat(style, ulocale);
}
}
// Creates an appropriate Format object for the type and style passed.
// Both arguments cannot be null.
private Format createAppropriateFormat(String type, String style) {
@ -2210,8 +2223,7 @@ public class MessageFormat extends UFormat {
break;
default: // pattern or skeleton
// Ignore leading whitespace when looking for "::", the skeleton signal sequence
int i = 0;
for (; PatternProps.isWhiteSpace(style.charAt(i)); i++);
int i = PatternProps.skipWhiteSpace(style, 0);
if (style.regionMatches(i, "::", 0, 2)) {
// Skeleton
newFormat = NumberFormatter.forSkeleton(style.substring(i + 2)).locale(ulocale).toFormat();
@ -2239,8 +2251,8 @@ public class MessageFormat extends UFormat {
case DATE_MODIFIER_FULL:
newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale);
break;
default:
newFormat = new SimpleDateFormat(style, ulocale);
default: // pattern or skeleton
newFormat = dateTimeFormatForPatternOrSkeleton(style);
break;
}
break;
@ -2261,8 +2273,8 @@ public class MessageFormat extends UFormat {
case DATE_MODIFIER_FULL:
newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale);
break;
default:
newFormat = new SimpleDateFormat(style, ulocale);
default: // pattern or skeleton
newFormat = dateTimeFormatForPatternOrSkeleton(style);
break;
}
break;

View file

@ -40,6 +40,8 @@ import com.ibm.icu.text.MessagePattern;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.text.UFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
@ -111,7 +113,7 @@ public class TestMessageFormat extends TestFmwk {
errln("Number format creation failed for " + locale[i].getDisplayName());
continue;
}
FieldPosition pos = new FieldPosition(0);
FieldPosition pos = new FieldPosition(FieldPosition_DONT_CARE);
buffer.setLength(0);
form.format(myNumber, buffer, pos);
parsePos.setIndex(0);
@ -215,7 +217,7 @@ public class TestMessageFormat extends TestFmwk {
//it_out << "Pat out: " << form.toPattern(buffer));
StringBuffer result = new StringBuffer();
FieldPosition fieldpos = new FieldPosition(0);
FieldPosition fieldpos = new FieldPosition(FieldPosition_DONT_CARE);
form.format(testArgs, result, fieldpos);
assertEquals("format", testResultStrings[i], result.toString());
@ -260,7 +262,7 @@ public class TestMessageFormat extends TestFmwk {
return;
}
Object testArgs1[] = { "abc", "def" };
FieldPosition fieldpos = new FieldPosition(0);
FieldPosition fieldpos = new FieldPosition(FieldPosition_DONT_CARE);
assertEquals("format",
"There are abc files on def",
form.format(testArgs1, buffer2, fieldpos).toString());
@ -458,7 +460,7 @@ public class TestMessageFormat extends TestFmwk {
MessageFormat msg = new MessageFormat(formatStr, Locale.ENGLISH);
result.setLength(0);
FieldPosition pos = new FieldPosition(0);
FieldPosition pos = new FieldPosition(FieldPosition_DONT_CARE);
result = msg.format(
arguments,
result,
@ -512,7 +514,7 @@ public class TestMessageFormat extends TestFmwk {
String compareStr = "On Aug 8, 1997, it began.";
MessageFormat msg = new MessageFormat(formatStr);
FieldPosition fp = new FieldPosition(0);
FieldPosition fp = new FieldPosition(FieldPosition_DONT_CARE);
try {
msg.format(new Date(871068000000L),
@ -923,7 +925,7 @@ public class TestMessageFormat extends TestFmwk {
MessageFormat msg = new MessageFormat(formatStr, ULocale.US);
result.setLength(0);
FieldPosition pos = new FieldPosition(0);
FieldPosition pos = new FieldPosition(FieldPosition_DONT_CARE);
result = msg.format(
arguments,
result,
@ -940,7 +942,7 @@ public class TestMessageFormat extends TestFmwk {
msg.setFormatsByArgumentIndex(fmts);
result.setLength(0);
pos = new FieldPosition(0);
pos = new FieldPosition(FieldPosition_DONT_CARE);
result = msg.format(
arguments,
result,
@ -952,7 +954,7 @@ public class TestMessageFormat extends TestFmwk {
Format newFmt = NumberFormat.getCurrencyInstance(ULocale.GERMAN);
msg.setFormatByArgumentIndex(0, newFmt);
result.setLength(0);
pos = new FieldPosition(0);
pos = new FieldPosition(FieldPosition_DONT_CARE);
result = msg.format(
arguments,
result,
@ -1008,7 +1010,7 @@ public class TestMessageFormat extends TestFmwk {
String compareStr = "On Aug 8, 1997, it began.";
MessageFormat msg = new MessageFormat(formatStr);
FieldPosition fp = new FieldPosition(0);
FieldPosition fp = new FieldPosition(FieldPosition_DONT_CARE);
try {
msg.format(arguments.get("startDate"), result, fp);
@ -1118,7 +1120,7 @@ public class TestMessageFormat extends TestFmwk {
gotException = false;
try {
Object args[] = {new Long(42)};
msg.format(args, new StringBuffer(), new FieldPosition(0));
msg.format(args, new StringBuffer(), new FieldPosition(FieldPosition_DONT_CARE));
} catch (IllegalArgumentException e) {
gotException = true;
}
@ -1131,7 +1133,7 @@ public class TestMessageFormat extends TestFmwk {
gotException = false;
try {
Object args[] = {new Long(42)};
msg.format((Object) args, new StringBuffer(), new FieldPosition(0));
msg.format((Object) args, new StringBuffer(), new FieldPosition(FieldPosition_DONT_CARE));
} catch (IllegalArgumentException e) {
gotException = true;
}
@ -1878,7 +1880,7 @@ public class TestMessageFormat extends TestFmwk {
map.put("_oOo_", new Integer(3));
StringBuffer result = new StringBuffer();
assertEquals("trim-named-arg format() failed", "x 3 y",
m.format(map, result, new FieldPosition(0)).toString());
m.format(map, result, new FieldPosition(FieldPosition_DONT_CARE)).toString());
}
@Test
@ -2116,10 +2118,44 @@ public class TestMessageFormat extends TestFmwk {
MessageFormat msgf = new MessageFormat(messagePattern, locale);
StringBuffer sb = new StringBuffer();
FieldPosition fpos = new FieldPosition(0);
FieldPosition fpos = new FieldPosition(FieldPosition_DONT_CARE);
msgf.format(new Object[] { arg }, sb, fpos);
assertEquals(messagePattern, expected, sb.toString());
}
}
private static void doTheRealDateTimeSkeletonTesting(Date date, String messagePattern, ULocale locale, String expected) {
MessageFormat msgf = new MessageFormat(messagePattern, locale);
StringBuffer sb = new StringBuffer();
FieldPosition fpos = new FieldPosition(FieldPosition_DONT_CARE);
msgf.format(new Object[] { date }, sb, fpos);
assertEquals(messagePattern, expected, sb.toString());
}
@Test
public void TestMessageFormatDateSkeleton() {
Date date = new GregorianCalendar(2021, Calendar.NOVEMBER, 23, 16, 42, 55).getTime();
doTheRealDateTimeSkeletonTesting(date, "{0,date,::MMMMd}", ULocale.ENGLISH, "November 23");
doTheRealDateTimeSkeletonTesting(date, "{0,date,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021, 4:42 PM");
doTheRealDateTimeSkeletonTesting(date, "{0,date, :: yMMMMd }", ULocale.ENGLISH, "November 23, 2021");
doTheRealDateTimeSkeletonTesting(date, "{0,date,::yMMMMd}", ULocale.FRENCH, "23 novembre 2021");
doTheRealDateTimeSkeletonTesting(date, "Expiration: {0,date,::yMMM}!", ULocale.ENGLISH, "Expiration: Nov 2021!");
doTheRealDateTimeSkeletonTesting(date, "{0,date,'::'yMMMMd}", ULocale.ENGLISH, "::2021November23"); // pattern literal
}
@Test
public void TestMessageFormatTimeSkeleton() {
Date date = new GregorianCalendar(2021, Calendar.NOVEMBER, 23, 16, 42, 55).getTime();
doTheRealDateTimeSkeletonTesting(date, "{0,time,::MMMMd}", ULocale.ENGLISH, "November 23");
doTheRealDateTimeSkeletonTesting(date, "{0,time,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021, 4:42 PM");
doTheRealDateTimeSkeletonTesting(date, "{0,time, :: yMMMMd }", ULocale.ENGLISH, "November 23, 2021");
doTheRealDateTimeSkeletonTesting(date, "{0,time,::yMMMMd}", ULocale.FRENCH, "23 novembre 2021");
doTheRealDateTimeSkeletonTesting(date, "Expiration: {0,time,::yMMM}!", ULocale.ENGLISH, "Expiration: Nov 2021!");
doTheRealDateTimeSkeletonTesting(date, "{0,time,'::'yMMMMd}", ULocale.ENGLISH, "::2021November23"); // pattern literal
}
}