diff --git a/jdk/src/share/classes/java/text/SimpleDateFormat.java b/jdk/src/share/classes/java/text/SimpleDateFormat.java index 38893c9d1ca..e543899fe95 100644 --- a/jdk/src/share/classes/java/text/SimpleDateFormat.java +++ b/jdk/src/share/classes/java/text/SimpleDateFormat.java @@ -373,6 +373,24 @@ public class SimpleDateFormat extends DateFormat { */ private String pattern; + /** + * Saved numberFormat and pattern. + * @see SimpleDateFormat#checkNegativeNumberExpression + */ + transient private NumberFormat originalNumberFormat; + transient private String originalNumberPattern; + + /** + * The minus sign to be used with format and parse. + */ + transient private char minusSign = '-'; + + /** + * True when a negative sign follows a number. + * (True as default in Arabic.) + */ + transient private boolean hasFollowingMinusSign = false; + /** * The compiled pattern. */ @@ -1226,6 +1244,8 @@ public class SimpleDateFormat extends DateFormat { */ public Date parse(String text, ParsePosition pos) { + checkNegativeNumberExpression(); + int start = pos.index; int oldStart = start; int textLength = text.length(); @@ -1271,14 +1291,42 @@ public class SimpleDateFormat extends DateFormat { // digit text (e.g., "20010704") with a pattern which // has no delimiters between fields, like "yyyyMMdd". boolean obeyCount = false; + + // In Arabic, a minus sign for a negative number is put after + // the number. Even in another locale, a minus sign can be + // put after a number using DateFormat.setNumberFormat(). + // If both the minus sign and the field-delimiter are '-', + // subParse() needs to determine whether a '-' after a number + // in the given text is a delimiter or is a minus sign for the + // preceding number. We give subParse() a clue based on the + // information in compiledPattern. + boolean useFollowingMinusSignAsDelimiter = false; + if (i < compiledPattern.length) { int nextTag = compiledPattern[i] >>> 8; - if (!(nextTag == TAG_QUOTE_ASCII_CHAR || nextTag == TAG_QUOTE_CHARS)) { + if (!(nextTag == TAG_QUOTE_ASCII_CHAR || + nextTag == TAG_QUOTE_CHARS)) { obeyCount = true; } + + if (hasFollowingMinusSign && + (nextTag == TAG_QUOTE_ASCII_CHAR || + nextTag == TAG_QUOTE_CHARS)) { + int c; + if (nextTag == TAG_QUOTE_ASCII_CHAR) { + c = compiledPattern[i] & 0xff; + } else { + c = compiledPattern[i+1]; + } + + if (c == minusSign) { + useFollowingMinusSignAsDelimiter = true; + } + } } start = subParse(text, start, tag, count, obeyCount, - ambiguousYear, pos); + ambiguousYear, pos, + useFollowingMinusSignAsDelimiter); if (start < 0) { pos.index = oldStart; return null; @@ -1514,8 +1562,8 @@ public class SimpleDateFormat extends DateFormat { */ private int subParse(String text, int start, int patternCharIndex, int count, boolean obeyCount, boolean[] ambiguousYear, - ParsePosition origPos) - { + ParsePosition origPos, + boolean useFollowingMinusSignAsDelimiter) { Number number = null; int value = 0; ParsePosition pos = new ParsePosition(0); @@ -1540,10 +1588,10 @@ public class SimpleDateFormat extends DateFormat { // a number value. We handle further, more generic cases below. We need // to handle some of them here because some fields require extra processing on // the parsed value. - if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ || - patternCharIndex == 15 /*HOUR1_FIELD*/ || - (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) || - patternCharIndex == 1) { + if (patternCharIndex == 4 /* HOUR_OF_DAY1_FIELD */ || + patternCharIndex == 15 /* HOUR1_FIELD */ || + (patternCharIndex == 2 /* MONTH_FIELD */ && count <= 2) || + patternCharIndex == 1 /* YEAR_FIELD */) { // It would be good to unify this with the obeyCount logic below, // but that's going to be difficult. if (obeyCount) { @@ -1560,6 +1608,15 @@ public class SimpleDateFormat extends DateFormat { } } else { value = number.intValue(); + + if (useFollowingMinusSignAsDelimiter && (value < 0) && + (((pos.index < text.length()) && + (text.charAt(pos.index) != minusSign)) || + ((pos.index == text.length()) && + (text.charAt(pos.index-1) == minusSign)))) { + value = -value; + pos.index--; + } } } @@ -1891,7 +1948,18 @@ public class SimpleDateFormat extends DateFormat { number = numberFormat.parse(text, pos); } if (number != null) { - calendar.set(field, number.intValue()); + value = number.intValue(); + + if (useFollowingMinusSignAsDelimiter && (value < 0) && + (((pos.index < text.length()) && + (text.charAt(pos.index) != minusSign)) || + ((pos.index == text.length()) && + (text.charAt(pos.index-1) == minusSign)))) { + value = -value; + pos.index--; + } + + calendar.set(field, value); return pos.index; } break parsing; @@ -2102,4 +2170,33 @@ public class SimpleDateFormat extends DateFormat { } } } + + /** + * Analyze the negative subpattern of DecimalFormat and set/update values + * as necessary. + */ + private void checkNegativeNumberExpression() { + if ((numberFormat instanceof DecimalFormat) && + !numberFormat.equals(originalNumberFormat)) { + String numberPattern = ((DecimalFormat)numberFormat).toPattern(); + if (!numberPattern.equals(originalNumberPattern)) { + hasFollowingMinusSign = false; + + int separatorIndex = numberPattern.indexOf(';'); + // If the negative subpattern is not absent, we have to analayze + // it in order to check if it has a following minus sign. + if (separatorIndex > -1) { + int minusIndex = numberPattern.indexOf('-', separatorIndex); + if ((minusIndex > numberPattern.lastIndexOf('0')) && + (minusIndex > numberPattern.lastIndexOf('#'))) { + hasFollowingMinusSign = true; + minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign(); + } + } + originalNumberPattern = numberPattern; + } + originalNumberFormat = numberFormat; + } + } + } diff --git a/jdk/test/java/text/Format/DateFormat/Bug4823811.java b/jdk/test/java/text/Format/DateFormat/Bug4823811.java new file mode 100644 index 00000000000..65b4aa26349 --- /dev/null +++ b/jdk/test/java/text/Format/DateFormat/Bug4823811.java @@ -0,0 +1,789 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/** + * @test + * @bug 4823811 + * @summary Confirm that text which includes numbers with a trailing minus sign is parsed correctly. + */ + +import java.text.*; +import java.util.*; + +public class Bug4823811 { + + private static Locale localeEG = new Locale("ar", "EG"); + private static Locale localeUS = Locale.US; + + private static String JuneInArabic = "\u064a\u0648\u0646\u064a\u0648"; + private static String JulyInArabic = "\u064a\u0648\u0644\u064a\u0648"; + private static String JuneInEnglish = "June"; + private static String JulyInEnglish = "July"; + + private static String BORDER = + "============================================================"; + + /* + * I don't use static import here intentionally so that this test program + * can be run on JDK 1.4.2. + */ + private static int ERA = Calendar.ERA; + private static int BC = GregorianCalendar.BC; +// private static int JAN = Calendar.JANUARY; +// private static int FEB = Calendar.FEBRUARY; +// private static int MAR = Calendar.MARCH; + private static int APR = Calendar.APRIL; + private static int MAY = Calendar.MAY; + private static int JUN = Calendar.JUNE; + private static int JUL = Calendar.JULY; +// private static int AUG = Calendar.AUGUST; +// private static int SEP = Calendar.SEPTEMBER; +// private static int OCT = Calendar.OCTOBER; +// private static int NOV = Calendar.NOVEMBER; +// private static int DEC = Calendar.DECEMBER; + + private static String[] patterns = { + "yyyy MMMM d H m s", + "yyyy MM dd hh mm ss", + + /* + * Because 1-based HOUR_OF_DAY, 1-based HOUR, MONTH, and YEAR fields + * are parsed using different code from the code for other numeric + * fields, I prepared YEAR-preceding patterns and SECOND-preceding + * patterns. + */ + "yyyy M d h m s", + " yyyy M d h m s", + "yyyy M d h m s ", + + "s m h d M yyyy", + " s m h d M yyyy", + "s m h d M yyyy ", + }; + + private static char originalMinusSign1 = ':'; + private static char originalMinusSign2 = '\uff0d'; // fullwidth minus + private static String[] delimiters = {"-", "/", ":", "/", "\uff0d", "/"}; + private static String[][] specialDelimiters = { + // for Arabic formatter and modified English formatter + {"--", "-/", "::", ":/", "\uff0d\uff0d", "\uff0d/"}, + + // for English formatter and modified Arabic formatter + {"--", "/-", "::", "/:", "\uff0d\uff0d", "/\uff0d"}, + }; + + /* + * Format: + * +-------------------------------------------------------------------+ + * | Input | Output | + * +---------------------+---------------------------------------------| + * | datesEG & datesUS | formattedDatesEG & formattedDatesUS | + * +-------------------------------------------------------------------+ + * + * Parse: + * +-------------------------------------------------------------------+ + * | Input | Output | + * |---------------------+---------------------------------------------| + * | datesToParse | datesEG & datesUS | + * +-------------------------------------------------------------------+ + */ + private static String[][] datesToParse = { + // "JUNE" and "JULY" are replaced with a localized month name later. + {"2008 JULY 20 3 12 83", + "2008 JULY 20 3 12 83", + "2008 JULY 20 3 12 83"}, + + {"2008 07 20 03 12 83", + "2008 07 20 03 12 83", + "2008 07 20 03 12 83"}, + + {"2008 7 20 3 12 83", + "2008 7 20 3 12 83", + "2008 7 20 3 12 83"}, + + {" 2008 7 20 3 12 83", + " 2008 7 20 3 12 83", + " 2008 7 20 3 12 83", + "2008 7 20 3 12 83"}, + + {"2008 7 20 3 12 83 ", + "2008 7 20 3 12 83 ", + "2008 7 20 3 12 83"}, + + {"83 12 3 20 7 2008", + "83 12 3 20 7 2008", + "83 12 3 20 7 2008"}, + + {" 83 12 3 20 7 2008", + " 83 12 3 20 7 2008", + " 83 12 3 20 7 2008", + "83 12 3 20 7 2008"}, + + {"83 12 3 20 7 2008 ", + "83 12 3 20 7 2008 ", + "83 12 3 20 7 2008"}, + }; + + // For formatting + private static String[][] formattedDatesEG = { + {"2008 JULY 20 3 13 23", + "2009 JULY 20 3 13 23", + null}, + + {"2008 07 20 03 13 23", + "2009 07 20 03 13 23", + "2007 05 20 03 13 23"}, + + {"2008 7 20 3 13 23", + "2009 6 10 3 13 23", + "2007 4 10 3 13 23"}, + + {" 2008 7 20 3 13 23", + null, + " 2009 7 20 3 13 23", + null}, + + {"2008 7 20 3 13 23 ", + "2008 7 20 3 10 37 ", + null}, + + {"23 13 3 20 7 2008", + "37 10 9 19 7 2008", + "23 49 8 19 7 2008"}, + + {" 23 13 3 20 7 2008", + null, + " 37 10 3 20 7 2008", + null}, + + {"23 13 3 20 7 2008 ", + "23 13 3 20 7 2009 ", + null}, + }; + + private static String[][] formattedDatesUS = { + {"2008 JULY 20 3 13 23", + null, + "2008 JUNE 10 3 13 23"}, + + {"2008 07 20 03 13 23", + "2007 05 20 03 13 23", + "2008 06 10 03 13 23"}, + + {"2008 7 20 3 13 23", + "2007 5 19 9 13 23", + "2008 6 9 9 13 23"}, + + {" 2008 7 20 3 13 23", + " 2009 7 20 3 13 23", + " 2007 5 20 3 13 23", + null}, + + {"2008 7 20 3 13 23 ", + "2008 7 20 3 13 23 ", + null}, + + {"23 13 3 20 7 2008", + "23 49 2 10 6 2008", + "23 13 9 9 6 2008"}, + + {" 23 13 3 20 7 2008", + " 37 10 3 20 7 2008", + " 23 49 2 20 7 2008", + null}, + + {"23 13 3 20 7 2008 ", + "23 13 3 20 7 2008 ", + null}, + }; + + private static GregorianCalendar[][] datesEG = { + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar(-2008, JUL, 20, 3, 12, 83), + null}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar(-2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2007, MAY, 20, 3, 12, 83)}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar(-2008, JUL, -20, 3, 12, 83), + new GregorianCalendar( 2007, APR, 10, 3, 12, 83)}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + null, + new GregorianCalendar(-2008, JUL, 20, 3, 12, 83), + null}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2008, JUL, 20, 3, 12, -83), + null}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2008, JUL, 20, -3, 12, -83), + new GregorianCalendar( 2008, JUL, 20, -3, -12, 83)}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + null, + new GregorianCalendar( 2008, JUL, 20, 3, 12, -83), + null}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar(-2008, JUL, 20, 3, 12, 83), + null}, + }; + + private static GregorianCalendar[][] datesUS = { + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + null, + new GregorianCalendar( 2008, JUN, 10, 3, 12, 83)}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2007, MAY, 20, 3, 12, 83), + new GregorianCalendar( 2008, JUN, 10, 3, 12, 83)}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2007, MAY, 20, -3, 12, 83), + new GregorianCalendar( 2008, JUL, -20, -3, 12, 83)}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar(-2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2007, MAY, 20, 3, 12, 83), + null}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + null}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2008, JUL, -20, 3, -12, 83), + new GregorianCalendar( 2008, JUL, -20, -3, 12, 83)}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2008, JUL, 20, 3, 12, -83), + new GregorianCalendar( 2008, JUL, 20, 3, -12, 83), + null}, + + {new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + new GregorianCalendar( 2008, JUL, 20, 3, 12, 83), + null}, + }; + + /* flags */ + private static boolean err = false; + private static boolean verbose = false; + + + public static void main(String[] args) { + if (args.length == 1 && args[0].equals("-v")) { + verbose = true; + } + + Locale defaultLocale = Locale.getDefault(); + TimeZone defaultTimeZone = TimeZone.getDefault(); + + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo")); + + try { + /* + * Test SimpleDateFormat.parse() and format() for original + * SimpleDateFormat instances + */ + testDateFormat1(); + + /* + * Test SimpleDateFormat.parse() and format() for modified + * SimpleDateFormat instances using an original minus sign, + * pattern, and diffenrent month names in DecimalFormat + */ + testDateFormat2(); + + /* + * Test SimpleDateFormat.parse() and format() for modified + * SimpleDateFormat instances using a fullwidth minus sign + */ + testDateFormat3(); + + /* + * Just to confirm that regressions aren't introduced in + * DecimalFormat. This cannot happen, though. Because I didn't + * change DecimalFormat at all. + */ + testNumberFormat(); + } + catch (Exception e) { + err = true; + System.err.println("Unexpected exception: " + e); + } + finally { + Locale.setDefault(defaultLocale); + TimeZone.setDefault(defaultTimeZone); + + if (err) { + System.err.println(BORDER + " Test failed."); + throw new RuntimeException("Date/Number formatting/parsing error."); + } else { + System.out.println(BORDER + " Test passed."); + } + } + } + + + // + // DateFormat test + // + private static void testDateFormat1() { + for (int i = 0; i < patterns.length; i++) { + System.out.println(BORDER); + for (int j = 0; j <= 1; j++) { + // Generate a pattern + String pattern = patterns[i].replaceAll(" ", delimiters[j]); + System.out.println("Pattern=\"" + pattern + "\""); + + System.out.println("*** DateFormat.format test in ar_EG"); + testDateFormatFormattingInRTL(pattern, i, j, null, localeEG, false); + + System.out.println("*** DateFormat.parse test in ar_EG"); + testDateFormatParsingInRTL(pattern, i, j, null, localeEG, false); + + System.out.println("*** DateFormat.format test in en_US"); + testDateFormatFormattingInLTR(pattern, i, j, null, localeUS, true); + + System.out.println("*** DateFormat.parse test in en_US"); + testDateFormatParsingInLTR(pattern, i, j, null, localeUS, true); + } + } + } + + private static void testDateFormat2() { + /* + * modified ar_EG Date&Time formatter : + * minus sign: ':' + * pattern: "#,##0.###" + * month names: In Arabic + * + * modified en_US Date&Time formatter : + * minus sign: ':' + * pattern: "#,##0.###;#,##0.###-" + * month names: In English + */ + DecimalFormat dfEG = (DecimalFormat)NumberFormat.getInstance(localeEG); + DecimalFormat dfUS = (DecimalFormat)NumberFormat.getInstance(localeUS); + + DecimalFormatSymbols dfsEG = dfEG.getDecimalFormatSymbols(); + DecimalFormatSymbols dfsUS = dfUS.getDecimalFormatSymbols(); + dfsEG.setMinusSign(originalMinusSign1); + dfsUS.setMinusSign(originalMinusSign1); + dfEG.setDecimalFormatSymbols(dfsUS); + dfUS.setDecimalFormatSymbols(dfsEG); + + String patternEG = dfEG.toPattern(); + String patternUS = dfUS.toPattern(); + + dfEG.applyPattern(patternUS); + dfUS.applyPattern(patternEG); + + for (int i = 0; i < patterns.length; i++) { + System.out.println(BORDER); + for (int j = 2; j <= 3; j++) { + // Generate a pattern + String pattern = patterns[i].replaceAll(" ", delimiters[j]); + System.out.println("Pattern=\"" + pattern + "\""); + + System.out.println("*** DateFormat.format test in modified en_US"); + testDateFormatFormattingInRTL(pattern, i, j, dfUS, localeUS, true); + + System.out.println("*** DateFormat.parse test in modified en_US"); + testDateFormatParsingInRTL(pattern, i, j, dfUS, localeUS, true); + + System.out.println("*** DateFormat.format test in modified ar_EG"); + testDateFormatFormattingInLTR(pattern, i, j, dfEG, localeEG, false); + + System.out.println("*** DateFormat.parse test in modified ar_EG"); + testDateFormatParsingInLTR(pattern, i, j, dfEG, localeEG, false); + } + } + } + + private static void testDateFormat3() { + /* + * modified ar_EG Date&Time formatter : + * minus sign: '\uff0d' // fullwidth minus + * pattern: "#,##0.###;#,##0.###-" + * month names: In Arabic + * + * modified en_US Date&Time formatter : + * minus sign: '\uff0d' // fullwidth minus + * pattern: "#,##0.###" + * month names: In English + */ + DecimalFormat dfEG = (DecimalFormat)NumberFormat.getInstance(localeEG); + DecimalFormat dfUS = (DecimalFormat)NumberFormat.getInstance(localeUS); + + DecimalFormatSymbols dfsEG = dfEG.getDecimalFormatSymbols(); + DecimalFormatSymbols dfsUS = dfUS.getDecimalFormatSymbols(); + dfsEG.setMinusSign(originalMinusSign2); + dfsUS.setMinusSign(originalMinusSign2); + dfEG.setDecimalFormatSymbols(dfsEG); + dfUS.setDecimalFormatSymbols(dfsUS); + + for (int i = 0; i < patterns.length; i++) { + System.out.println(BORDER); + for (int j = 4; j <= 5; j++) { + // Generate a pattern + String pattern = patterns[i].replaceAll(" ", delimiters[j]); + System.out.println("Pattern=\"" + pattern + "\""); + + System.out.println("*** DateFormat.format test in modified ar_EG"); + testDateFormatFormattingInRTL(pattern, i, j, dfEG, localeEG, false); + + System.out.println("*** DateFormat.parse test in modified ar_EG"); + testDateFormatParsingInRTL(pattern, i, j, dfEG, localeEG, false); + + System.out.println("*** DateFormat.format test in modified en_US"); + testDateFormatFormattingInLTR(pattern, i, j, dfUS, localeUS, true); + + System.out.println("*** DateFormat.parse test in modified en_US"); + testDateFormatParsingInLTR(pattern, i, j, dfUS, localeUS, true); + } + } + } + + private static void testDateFormatFormattingInRTL(String pattern, + int basePattern, + int delimiter, + NumberFormat nf, + Locale locale, + boolean useEnglishMonthName) { + Locale.setDefault(locale); + + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + if (nf != null) { + sdf.setNumberFormat(nf); + } + for (int i = 0; i < datesToParse[basePattern].length; i++) { + if (datesEG[basePattern][i] == null) { + continue; + } + + String expected = formattedDatesEG[basePattern][i] + .replaceAll("JUNE", (useEnglishMonthName ? + JuneInEnglish : JuneInArabic)) + .replaceAll("JULY", (useEnglishMonthName ? + JulyInEnglish : JulyInArabic)) + .replaceAll(" ", delimiters[delimiter]); + testDateFormatFormatting(sdf, pattern, datesEG[basePattern][i], + expected, locale.toString()); + } + } + + private static void testDateFormatFormattingInLTR(String pattern, + int basePattern, + int delimiter, + NumberFormat nf, + Locale locale, + boolean useEnglishMonthName) { + Locale.setDefault(locale); + + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + if (nf != null) { + sdf.setNumberFormat(nf); + } + for (int i = 0; i < datesToParse[basePattern].length; i++) { + if (datesUS[basePattern][i] == null) { + continue; + } + + String expected = formattedDatesUS[basePattern][i] + .replaceAll("JUNE", (useEnglishMonthName ? + JuneInEnglish : JuneInArabic)) + .replaceAll("JULY", (useEnglishMonthName ? + JulyInEnglish : JulyInArabic)) + .replaceAll(" ", delimiters[delimiter]); + testDateFormatFormatting(sdf, pattern, datesUS[basePattern][i], + expected, locale.toString()); + } + } + + private static void testDateFormatFormatting(SimpleDateFormat sdf, + String pattern, + GregorianCalendar givenGC, + String expected, + String locale) { + Date given = givenGC.getTime(); + String str = sdf.format(given); + if (expected.equals(str)) { + if (verbose) { + System.out.print(" Passed: SimpleDateFormat("); + System.out.print(locale + ", \"" + pattern + "\").format("); + System.out.println(given + ")"); + + System.out.print(" ---> \"" + str + "\" "); + System.out.println((givenGC.get(ERA) == BC) ? "(BC)" : "(AD)"); + } + } else { + err = true; + + System.err.print(" Failed: Unexpected SimpleDateFormat("); + System.out.print(locale + ", \"" + pattern + "\").format("); + System.out.println(given + ") result."); + + System.out.println(" Expected: \"" + expected + "\""); + + System.out.print(" Got: \"" + str + "\" "); + System.out.println((givenGC.get(ERA) == BC) ? "(BC)" : "(AD)"); + } + } + + private static void testDateFormatParsingInRTL(String pattern, + int basePattern, + int delimiter, + NumberFormat nf, + Locale locale, + boolean useEnglishMonthName) { + Locale.setDefault(locale); + + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + if (nf != null) { + sdf.setNumberFormat(nf); + } + for (int i = 0; i < datesToParse[basePattern].length; i++) { + String given = datesToParse[basePattern][i] + .replaceAll(" ", specialDelimiters[0][delimiter]) + .replaceAll(" ", delimiters[delimiter]); + + testDateFormatParsing(sdf, pattern, + given.replaceAll("JULY", (useEnglishMonthName ? + JulyInEnglish : JulyInArabic)), + datesEG[basePattern][i], locale.toString()); + } + } + + private static void testDateFormatParsingInLTR(String pattern, + int basePattern, + int delimiter, + NumberFormat nf, + Locale locale, + boolean useEnglishMonthName) { + Locale.setDefault(locale); + + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + if (nf != null) { + sdf.setNumberFormat(nf); + } + for (int i = 0; i < datesToParse[basePattern].length; i++) { + String given = datesToParse[basePattern][i] + .replaceAll(" ", specialDelimiters[1][delimiter]) + .replaceAll(" ", delimiters[delimiter]); + + testDateFormatParsing(sdf, pattern, + given.replaceAll("JULY", (useEnglishMonthName ? + JulyInEnglish : JulyInArabic)), + datesUS[basePattern][i], locale.toString()); + } + } + + private static void testDateFormatParsing(SimpleDateFormat sdf, + String pattern, + String given, + GregorianCalendar expectedGC, + String locale) { + try { + Date d = sdf.parse(given); + if (expectedGC == null) { + err = true; + System.err.print(" Failed: SimpleDateFormat(" + locale); + System.err.print(", \"" + pattern + "\").parse(\"" + given); + System.err.println("\") should have thrown ParseException"); + } else if (expectedGC.getTime().equals(d)) { + if (verbose) { + System.out.print(" Passed: SimpleDateFormat(" + locale); + System.out.print(", \"" + pattern + "\").parse(\"" + given); + System.out.println("\")"); + + System.out.print(" ---> " + d + " (" + d.getTime()); + System.out.println(")"); + } + } else { + err = true; + System.err.print(" Failed: SimpleDateFormat(" + locale); + System.err.print(", \"" + pattern + "\").parse(\"" + given); + System.err.println("\")"); + + System.err.print(" Expected: " + expectedGC.getTime()); + System.err.println(" (" + d.getTime() + ")"); + + System.err.print(" Got: " + d + " (" + d.getTime()); + System.err.println(")"); + + System.err.print(" Pattern: \""); + System.err.print(((DecimalFormat)sdf.getNumberFormat()).toPattern()); + System.err.println("\""); + } + } + catch (ParseException pe) { + if (expectedGC == null) { + if (verbose) { + System.out.print(" Passed: SimpleDateFormat(" + locale); + System.out.print(", \"" + pattern + "\").parse(\"" + given); + System.out.println("\")"); + + System.out.println(" threw ParseException as expected"); + } + } else { + err = true; + System.err.println(" Failed: Unexpected exception with"); + + System.err.print(" SimpleDateFormat(" + locale); + System.err.print(", \"" + pattern + "\").parse(\""); + System.err.println(given + "\"):"); + + System.err.println(" " + pe); + + System.err.print(" Pattern: \""); + System.err.print(((DecimalFormat)sdf.getNumberFormat()).toPattern()); + System.err.println("\""); + + System.err.print(" Month 0: "); + System.err.println(sdf.getDateFormatSymbols().getMonths()[0]); + } + } + } + + + // + // NumberFormat test + // + private static void testNumberFormat() { + NumberFormat nfEG = NumberFormat.getInstance(localeEG); + NumberFormat nfUS = NumberFormat.getInstance(localeUS); + + System.out.println("*** DecimalFormat.format test in ar_EG"); + testNumberFormatFormatting(nfEG, -123456789, "123,456,789-", "ar_EG"); + testNumberFormatFormatting(nfEG, -456, "456-", "ar_EG"); + + System.out.println("*** DecimalFormat.parse test in ar_EG"); + testNumberFormatParsing(nfEG, "123-", new Long(-123), "ar_EG"); + testNumberFormatParsing(nfEG, "123--", new Long(-123), "ar_EG"); + testNumberFormatParsingCheckException(nfEG, "-123", 0, "ar_EG"); + + System.out.println("*** DecimalFormat.format test in en_US"); + testNumberFormatFormatting(nfUS, -123456789, "-123,456,789", "en_US"); + testNumberFormatFormatting(nfUS, -456, "-456", "en_US"); + + System.out.println("*** DecimalFormat.parse test in en_US"); + testNumberFormatParsing(nfUS, "123-", new Long(123), "en_US"); + testNumberFormatParsing(nfUS, "-123", new Long(-123), "en_US"); + testNumberFormatParsingCheckException(nfUS, "--123", 0, "en_US"); + } + + private static void testNumberFormatFormatting(NumberFormat nf, + int given, + String expected, + String locale) { + String str = nf.format(given); + if (expected.equals(str)) { + if (verbose) { + System.out.print(" Passed: NumberFormat(" + locale); + System.out.println(").format(" + given + ")"); + + System.out.println(" ---> \"" + str + "\""); + } + } else { + err = true; + System.err.print(" Failed: Unexpected NumberFormat(" + locale); + System.err.println(").format(" + given + ") result."); + + System.err.println(" Expected: \"" + expected + "\""); + + System.err.println(" Got: \"" + str + "\""); + } + } + + private static void testNumberFormatParsing(NumberFormat nf, + String given, + Number expected, + String locale) { + try { + Number n = nf.parse(given); + if (n.equals(expected)) { + if (verbose) { + System.out.print(" Passed: NumberFormat(" + locale); + System.out.println(").parse(\"" + given + "\")"); + + System.out.println(" ---> " + n); + } + } else { + err = true; + System.err.print(" Failed: Unexpected NumberFormat(" + locale); + System.err.println(").parse(\"" + given + "\") result."); + + System.err.println(" Expected: " + expected); + + System.err.println(" Got: " + n); + } + } + catch (ParseException pe) { + err = true; + System.err.print(" Failed: Unexpected exception with NumberFormat("); + System.err.println(locale + ").parse(\"" + given + "\") :"); + + System.err.println(" " + pe); + } + } + + private static void testNumberFormatParsingCheckException(NumberFormat nf, + String given, + int expected, + String locale) { + try { + Number n = nf.parse(given); + err = true; + + System.err.print(" Failed: NumberFormat(" + locale); + System.err.println(").parse(\"" + given + "\")"); + + System.err.println(" should have thrown ParseException"); + } + catch (ParseException pe) { + int errorOffset = pe.getErrorOffset(); + if (errorOffset == expected) { + if (verbose) { + System.out.print(" Passed: NumberFormat(" + locale); + System.out.println(").parse(\"" + given + "\")"); + + System.out.print(" threw ParseException as expected, and its errorOffset was correct: "); + System.out.println(errorOffset); + } + } else { + err = true; + System.err.print(" Failed: NumberFormat(" + locale); + System.err.println(").parse(\"" + given + "\")"); + + System.err.print(" threw ParseException as expected, but its errorOffset was incorrect: "); + System.err.println(errorOffset); + } + } + } + +}