changeset 637:1b0b3a777a6c

4823811: [Fmt-Da] SimpleDateFormat patterns don't allow embedding of some literal punctuation Reviewed-by: okutsu
author peytoia
date Mon, 08 Sep 2008 14:31:08 +0900
parents 9b8e20a3c5f0
children 21346d9b372a
files src/share/classes/java/text/SimpleDateFormat.java test/java/text/Format/DateFormat/Bug4823811.java
diffstat 2 files changed, 895 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/text/SimpleDateFormat.java	Mon Sep 08 13:31:45 2008 +0900
+++ b/src/share/classes/java/text/SimpleDateFormat.java	Mon Sep 08 14:31:08 2008 +0900
@@ -374,6 +374,24 @@
     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.
      */
     transient private char[] compiledPattern;
@@ -1226,6 +1244,8 @@
      */
     public Date parse(String text, ParsePosition pos)
     {
+        checkNegativeNumberExpression();
+
         int start = pos.index;
         int oldStart = start;
         int textLength = text.length();
@@ -1271,14 +1291,42 @@
                 // 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 @@
      */
     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 @@
             // 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 @@
                     }
                 } 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 @@
                     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 @@
             }
         }
     }
+
+    /**
+     * 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;
+        }
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/text/Format/DateFormat/Bug4823811.java	Mon Sep 08 14:31:08 2008 +0900
@@ -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);
+            }
+        }
+    }
+
+}