ICU-7724 Merge support for non-contiguous numbering systems

X-SVN-Rev: 28469
This commit is contained in:
John Emmons 2010-08-13 22:17:10 +00:00
parent 8fd81d1a6b
commit c86940b784
9 changed files with 224 additions and 61 deletions

View file

@ -27,7 +27,8 @@ public final class DateNumberFormat extends NumberFormat {
private static final long serialVersionUID = -6315692826916346953L;
private char zeroDigit;
private char[] digits;
private char zeroDigit; // For backwards compatibility
private char minusSign;
private boolean positiveOnly = false;
@ -38,17 +39,19 @@ public final class DateNumberFormat extends NumberFormat {
private int maxIntDigits;
private int minIntDigits;
public DateNumberFormat(ULocale loc, char zeroDigitIn, String nsName) {
initialize(loc,zeroDigitIn,nsName);
public DateNumberFormat(ULocale loc, String digitString, String nsName) {
initialize(loc,digitString,nsName);
}
/* public DateNumberFormat(char zeroDigit, char minusSign) {
this.zeroDigit = zeroDigit;
this.minusSign = minusSign;
public DateNumberFormat(ULocale loc, char zeroDigit, String nsName) {
StringBuffer buf = new StringBuffer();
for ( int i = 0 ; i < 10 ; i++ ) {
buf.append((char)(zeroDigit+i));
}
initialize(loc,buf.toString(),nsName);
}
*/
private void initialize(ULocale loc,char zeroDigitIn,String nsName) {
private void initialize(ULocale loc,String digitString,String nsName) {
char[] elems = CACHE.get(loc);
if (elems == null) {
// Missed cache
@ -67,13 +70,19 @@ public final class DateNumberFormat extends NumberFormat {
minusString = "-";
}
}
elems = new char[2];
elems[0] = zeroDigitIn;
elems[1] = minusString.charAt(0);
elems = new char[11];
for ( int i = 0 ; i < 10 ; i++ ) {
elems[i] = digitString.charAt(i);
}
elems[10] = minusString.charAt(0);
CACHE.put(loc, elems);
}
zeroDigit = elems[0];
minusSign = elems[1];
digits = new char[10];
for ( int i = 0 ; i < 10 ; i++ ) {
digits[i] = elems[i];
}
minusSign = elems[10];
}
public void setMaximumIntegerDigits(int newValue) {
@ -98,11 +107,24 @@ public final class DateNumberFormat extends NumberFormat {
}
public char getZeroDigit() {
return zeroDigit;
if ( digits != null ) {
return digits[0];
} else {
return zeroDigit;
}
}
public void setZeroDigit(char zero) {
zeroDigit = zero;
if ( digits == null ) {
digits = new char[10];
}
digits[0] = zero;
if (Character.digit(zero,10) == 0) {
for ( int i = 1 ; i < 10 ; i++ ) {
digits[i] = (char)(zero+i);
}
}
}
public StringBuffer format(double number, StringBuffer toAppendTo,
@ -126,7 +148,7 @@ public final class DateNumberFormat extends NumberFormat {
int limit = decimalBuf.length < maxIntDigits ? decimalBuf.length : maxIntDigits;
int index = limit - 1;
while (true) {
decimalBuf[index] = (char)((number % 10) + zeroDigit);
decimalBuf[index] = digits[(number % 10)];
number /= 10;
if (index == 0 || number == 0) {
break;
@ -135,7 +157,7 @@ public final class DateNumberFormat extends NumberFormat {
}
int padding = minIntDigits - (limit - index);
for (; padding > 0; padding--) {
decimalBuf[--index] = zeroDigit;
decimalBuf[--index] = digits[0];
}
int length = limit - index;
toAppendTo.append(decimalBuf, index, length);
@ -182,10 +204,17 @@ public final class DateNumberFormat extends NumberFormat {
}
negative = true;
} else {
int digit = ch - zeroDigit;
int digit = ch - digits[0];
if (digit < 0 || 9 < digit) {
digit = UCharacter.digit(ch);
}
if (digit < 0 || 9 < digit) {
for ( digit = 0 ; digit < 10 ; digit++ ) {
if ( ch == digits[digit]) {
break;
}
}
}
if (0 <= digit && digit <= 9 && num < PARSE_THRESHOLD) {
sawNumber = true;
num = num * 10 + digit;
@ -208,9 +237,27 @@ public final class DateNumberFormat extends NumberFormat {
return false;
}
DateNumberFormat other = (DateNumberFormat)obj;
for (int i = 0 ; i < 10 ; i++) {
char check1, check2;
if ( digits != null ) {
check1 = digits[i];
} else {
check1 = (char)(zeroDigit+i);
}
if ( other.digits != null ) {
check2 = other.digits[i];
} else {
check2 = (char)(other.zeroDigit+i);
}
if (check1 != check2) {
return false;
}
}
return (this.maxIntDigits == other.maxIntDigits
&& this.minIntDigits == other.minIntDigits
&& this.zeroDigit == other.zeroDigit
&& this.minusSign == other.minusSign
&& this.positiveOnly == other.positiveOnly);
}

View file

@ -1206,8 +1206,8 @@ public class DecimalFormat extends NumberFormat {
// }
int i;
char zero = symbols.getZeroDigit();
int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero
char [] digits = symbols.getDigits();
char grouping = currencySignCount > 0 ? symbols.getMonetaryGroupingSeparator() :
symbols.getGroupingSeparator();
char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() :
@ -1329,13 +1329,13 @@ public class DecimalFormat extends NumberFormat {
}
}
result.append((i < digitList.count)
? (char) (digitList.digits[i] + zeroDelta)
: zero);
? digits[digitList.getDigitValue(i)]
: digits[0]);
}
// For ICU compatibility and format 0 to 0E0 with pattern "#E0" [Richard/GCL]
if (digitList.isZero() && (totalDigits == 0)) {
result.append(zero);
result.append(digits[0]);
}
// Record field information
@ -1412,11 +1412,11 @@ public class DecimalFormat extends NumberFormat {
expDig = 1;
}
for (i = digitList.decimalAt; i < expDig; ++i)
result.append(zero);
result.append(digits[0]);
}
for (i = 0; i < digitList.decimalAt; ++i) {
result.append((i < digitList.count) ? (char) (digitList.digits[i] + zeroDelta)
: zero);
result.append((i < digitList.count) ? digits[digitList.getDigitValue(i)]
: digits[0]);
}
// [Spark/CDL] Add attribute for exponent part.
if (parseAttr) {
@ -1464,12 +1464,11 @@ public class DecimalFormat extends NumberFormat {
if (i < digitList.decimalAt && digitIndex < digitList.count
&& sigCount < maxSigDig) {
// Output a real digit
byte d = digitList.digits[digitIndex++];
result.append((char) (d + zeroDelta));
result.append(digits[digitList.getDigitValue(digitIndex++)]);
++sigCount;
} else {
// Output a zero (leading or trailing)
result.append(zero);
result.append(digits[0]);
if (sigCount > 0) {
++sigCount;
}
@ -1502,7 +1501,7 @@ public class DecimalFormat extends NumberFormat {
// then print a zero. Otherwise we won't print _any_ digits, and we won't be
// able to parse this string.
if (!fractionPresent && result.length() == sizeBeforeIntegerPart)
result.append(zero);
result.append(digits[0]);
// [Spark/CDL] Add attribute for integer part.
if (parseAttr) {
addAttribute(Field.INTEGER, intBegin, result.length());
@ -1546,16 +1545,16 @@ public class DecimalFormat extends NumberFormat {
// decimal but before any significant digits. These are only output if
// abs(number being formatted) < 1.0.
if (-1 - i > (digitList.decimalAt - 1)) {
result.append(zero);
result.append(digits[0]);
continue;
}
// Output a digit, if we have any precision left, or a zero if we
// don't. We don't want to output noise digits.
if (!isInteger && digitIndex < digitList.count) {
result.append((char) (digitList.digits[digitIndex++] + zeroDelta));
result.append(digits[digitList.getDigitValue(digitIndex++)]);
} else {
result.append(zero);
result.append(digits[0]);
}
// If we reach the maximum number of significant digits, or if we output
@ -2121,7 +2120,7 @@ public class DecimalFormat extends NumberFormat {
// DigitList, and adjust the exponent as needed.
digits.decimalAt = digits.count = 0;
char zero = symbols.getZeroDigit();
char [] digitSymbols = symbols.getDigits();
char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() : symbols
.getDecimalSeparator();
char grouping = symbols.getGroupingSeparator();
@ -2174,9 +2173,17 @@ public class DecimalFormat extends NumberFormat {
// standard Unicode digit range. If this fails, try using the standard
// Unicode digit ranges by calling UCharacter.digit(). If this also fails,
// digit will have a value outside the range 0..9.
digit = ch - zero;
digit = ch - digitSymbols[0];
if (digit < 0 || digit > 9)
digit = UCharacter.digit(ch, 10);
if (digit < 0 || digit > 9) {
for ( digit = 0 ; digit < 10 ; digit++) {
if ( ch == digitSymbols[digit] )
break;
}
}
if (digit == 0) {
// Cancel out backup setting (see grouping handler below)
@ -2324,7 +2331,7 @@ public class DecimalFormat extends NumberFormat {
DigitList exponentDigits = new DigitList();
exponentDigits.count = 0;
while (pos < text.length()) {
digit = text.charAt(pos) - zero;
digit = text.charAt(pos) - digitSymbols[0];
if (digit < 0 || digit > 9) {
// Can't parse "[1E0]" when pattern is "0.###E0;[0.###E0]"
// Should update reassign the value of 'ch' in the code: digit
@ -5392,6 +5399,15 @@ public class DecimalFormat extends NumberFormat {
// Constants for characters used in programmatic (unlocalized) patterns.
static final char PATTERN_ZERO_DIGIT = '0';
static final char PATTERN_ONE_DIGIT = '1';
static final char PATTERN_TWO_DIGIT = '2';
static final char PATTERN_THREE_DIGIT = '3';
static final char PATTERN_FOUR_DIGIT = '4';
static final char PATTERN_FIVE_DIGIT = '5';
static final char PATTERN_SIX_DIGIT = '6';
static final char PATTERN_SEVEN_DIGIT = '7';
static final char PATTERN_EIGHT_DIGIT = '8';
static final char PATTERN_NINE_DIGIT = '9';
static final char PATTERN_GROUPING_SEPARATOR = ',';
static final char PATTERN_DECIMAL_SEPARATOR = '.';
static final char PATTERN_DIGIT = '#';

View file

@ -160,7 +160,27 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
* @stable ICU 2.0
*/
public char getZeroDigit() {
return zeroDigit;
if ( digits != null ) {
return digits[0];
} else {
return zeroDigit;
}
}
/**
* Returns the array of characters used as digits, in order from 0 through 9
* @return The array
* @draft ICU 4.6
*/
public char[] getDigits() {
if ( digits != null ) {
return digits;
} else {
char [] digitArray = new char[10];
for ( int i = 0 ; i < 10 ; i++ ) {
digitArray[i] = (char) (zeroDigit + i);
}
return digitArray;
}
}
/**
@ -169,7 +189,16 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
* @stable ICU 2.0
*/
public void setZeroDigit(char zeroDigit) {
this.zeroDigit = zeroDigit;
if ( digits != null ) {
this.digits[0] = zeroDigit;
if (Character.digit(zeroDigit,10) == 0) {
for ( int i = 1 ; i < 10 ; i++ ) {
this.digits[i] = (char)(zeroDigit+i);
}
}
} else {
this.zeroDigit = zeroDigit;
}
}
/**
@ -692,8 +721,22 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
return false;
}
}
if ( other.digits == null ) {
for (int i = 0 ; i < 10 ; i++) {
if (digits[i] != other.zeroDigit + i) {
return false;
}
}
} else {
for (int i = 0 ; i < 10 ; i++) {
if (digits[i] != other.digits[i]) {
return false;
}
}
}
return (zeroDigit == other.zeroDigit &&
return (
groupingSeparator == other.groupingSeparator &&
decimalSeparator == other.decimalSeparator &&
percent == other.percent &&
@ -717,7 +760,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
* @stable ICU 2.0
*/
public int hashCode() {
int result = zeroDigit;
int result = digits[0];
result = result * 37 + groupingSeparator;
result = result * 37 + decimalSeparator;
return result;
@ -735,11 +778,32 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
String nsName;
// Attempt to set the zero digit based on the numbering system for the locale requested
NumberingSystem ns = NumberingSystem.getInstance(locale);
if ( ns != null && ns.getRadix() == 10 && !ns.isAlgorithmic()) {
zeroDigit = ns.getDescription().charAt(0);
digits = new char[10];
if ( ns != null && ns.getRadix() == 10 && !ns.isAlgorithmic() &&
NumberingSystem.isValidDigitString(ns.getDescription())) {
String digitString = ns.getDescription();
digits[0] = digitString.charAt(0);
digits[1] = digitString.charAt(1);
digits[2] = digitString.charAt(2);
digits[3] = digitString.charAt(3);
digits[4] = digitString.charAt(4);
digits[5] = digitString.charAt(5);
digits[6] = digitString.charAt(6);
digits[7] = digitString.charAt(7);
digits[8] = digitString.charAt(8);
digits[9] = digitString.charAt(9);
nsName = ns.getName();
} else {
zeroDigit = DecimalFormat.PATTERN_ZERO_DIGIT;
digits[0] = DecimalFormat.PATTERN_ZERO_DIGIT;
digits[1] = DecimalFormat.PATTERN_ONE_DIGIT;
digits[2] = DecimalFormat.PATTERN_TWO_DIGIT;
digits[3] = DecimalFormat.PATTERN_THREE_DIGIT;
digits[4] = DecimalFormat.PATTERN_FOUR_DIGIT;
digits[5] = DecimalFormat.PATTERN_FIVE_DIGIT;
digits[6] = DecimalFormat.PATTERN_SIX_DIGIT;
digits[7] = DecimalFormat.PATTERN_SEVEN_DIGIT;
digits[8] = DecimalFormat.PATTERN_EIGHT_DIGIT;
digits[9] = DecimalFormat.PATTERN_NINE_DIGIT;
nsName = "latn"; // Default numbering system
}
@ -928,12 +992,19 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
}
/**
* Character used for zero.
* Character used for zero. This remains only for backward compatibility
* purposes. The digits array below is now used to actively store the digits.
*
* @serial
* @see #getZeroDigit
*/
private char zeroDigit;
/**
* Array of characters used for the digits 0-9 in order.
*
*/
private char digits[];
/**
* Character used for thousands separator.

View file

@ -112,6 +112,11 @@ final class DigitList {
ensureCapacity(count+1, count);
digits[count++] = (byte) digit;
}
public byte getDigitValue(int i) {
return (byte) (digits[i] - '0');
}
/**
* Utility routine to get the value of the digit list
* If (count == 0) this throws a NumberFormatException, which

View file

@ -229,31 +229,24 @@ public class NumberingSystem {
* Convenience method to determine if a given digit string is valid for use as a
* descriptor of a numeric ( non-algorithmic ) numbering system. In order for
* a digit string to be valid, it must meet the following criteria:
* 1. It must only contain characters that are decimal digits as defined by Unicode.
* 2. It must contain characters that are contiguous code points.
* 3. Digits must be in Unicode's basic multilingual plane.
* 1. Digits must be in Unicode's basic multilingual plane.
* @stable ICU 4.2
*/
public static boolean isValidDigitString(String str) {
int c;
int prev = 0;
int i = 0;
UCharacterIterator it = UCharacterIterator.getInstance(str);
it.setToStart();
while ( (c = it.nextCodePoint()) != UCharacterIterator.DONE) {
if ( UCharacter.digit(c) != i ) { // Digits outside the Unicode decimal digit class are not currently supported
return false;
}
if ( prev != 0 && c != prev + 1 ) { // Non-contiguous digits are not currently supported
return false;
}
if ( UCharacter.isSupplementary(c)) { // Digits outside the BMP are not currently supported
return false;
}
i++;
prev = c;
}
if ( i != 10 ) {
return false;
}
return true;
}

View file

@ -512,10 +512,10 @@ public class SimpleDateFormat extends DateFormat {
if ( ns.isAlgorithmic() ) {
numberFormat = NumberFormat.getInstance(locale);
} else {
char digit0 = ns.getDescription().charAt(0);
String digitString = ns.getDescription();
String nsName = ns.getName();
// Use a NumberFormat optimized for date formatting
numberFormat = new DateNumberFormat(locale, digit0, nsName);
numberFormat = new DateNumberFormat(locale, digitString, nsName);
}
}
// Note: deferring calendar calculation until when we really need it.
@ -3218,7 +3218,8 @@ public class SimpleDateFormat extends DateFormat {
ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
nf.setGroupingUsed(false);
if (fullOverride) {
setNumberFormat(nf);
} else {

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c38484617b3198bdc540b0b59dec75c3155c8e88a63998be574951d07ccadc82
size 7071589
oid sha256:d6534073fc07de8c23461ec75763b7618b28976075acf49ade47ec22460ca2e1
size 7071711

View file

@ -3768,6 +3768,33 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
}
}
public void TestFormalChineseDate() {
String pattern = "y\u5e74M\u6708d\u65e5";
String override = "y=hanidec;M=hans;d=hans";
// create formatter
SimpleDateFormat sdf = new SimpleDateFormat(pattern,override,ULocale.CHINA);
Calendar cal = Calendar.getInstance(ULocale.ENGLISH);
cal.clear(Calendar.MILLISECOND);
cal.set(2009, 6, 28, 0,0,0);
FieldPosition pos = new FieldPosition(0);
StringBuffer result = new StringBuffer();
sdf.format(cal,result,pos);
String res1 = result.toString();
String expected = "\u4e8c\u3007\u3007\u4e5d\u5e74\u4e03\u6708\u4e8c\u5341\u516b\u65e5";
if (! res1.equals(expected)) {
errln((String)"FAIL: -> " + result.toString() + " expected -> " + expected);
}
ParsePosition pp = new ParsePosition(0);
Date parsedate = sdf.parse(expected, pp);
long time1 = parsedate.getTime();
long time2 = cal.getTimeInMillis();
if ( time1 != time2 ) {
errln("FAIL: parsed -> " + parsedate.toString() + " expected -> " + cal.toString());
}
}
}

View file

@ -1256,6 +1256,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
ULocale loc4 = new ULocale("hi_IN@numbers=foobar");
ULocale loc5 = new ULocale("ar_EG"); // ar_EG uses arab numbering system
ULocale loc6 = new ULocale("ar_MA"); // ar_MA users latn numbering system
ULocale loc7 = new ULocale("en_US@numbers=hanidec"); // hanidec is a non-contiguous ns
NumberFormat fmt1 = NumberFormat.getInstance(loc1);
NumberFormat fmt2 = NumberFormat.getInstance(loc2);
@ -1263,6 +1264,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
NumberFormat fmt4 = NumberFormat.getInstance(loc4);
NumberFormat fmt5 = NumberFormat.getInstance(loc5);
NumberFormat fmt6 = NumberFormat.getInstance(loc6);
NumberFormat fmt7 = NumberFormat.getInstance(loc7);
expect2(fmt1,1234.567,"\u0e51,\u0e52\u0e53\u0e54.\u0e55\u0e56\u0e57");
expect3(fmt2,5678.0,"\u05d4\u05f3\u05ea\u05e8\u05e2\u05f4\u05d7");
@ -1270,6 +1272,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
expect2(fmt4,1234.567,"\u0967,\u0968\u0969\u096a.\u096b\u096c\u096d");
expect2(fmt5,1234.567,"\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666\u0667");
expect2(fmt6,1234.567,"1.234,567");
expect2(fmt7,1234.567, "\u4e00,\u4e8c\u4e09\u56db.\u4e94\u516d\u4e03");
}