]> granicus.if.org Git - icu/commitdiff
ICU-20441 force Gannen use for ja@calendar=japanese non-numeric formats if no overrid...
authorPeter Edberg <pedberg@unicode.org>
Fri, 22 Feb 2019 00:04:04 +0000 (16:04 -0800)
committerpedberg-icu <42151464+pedberg-icu@users.noreply.github.com>
Fri, 22 Feb 2019 03:26:52 +0000 (19:26 -0800)
icu4c/source/i18n/smpdtfmt.cpp
icu4c/source/i18n/unicode/smpdtfmt.h
icu4c/source/test/cintltst/cdattst.c
icu4c/source/test/intltest/dtifmtts.cpp
icu4c/source/test/intltest/incaltst.cpp
icu4c/source/test/intltest/incaltst.h
icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/JapaneseTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java

index b691944689ce91a95874a3506d91741fa3124108..414dfffab8fda1498614a8d13d9a553fc38bc24f 100644 (file)
@@ -856,6 +856,17 @@ SimpleDateFormat::initialize(const Locale& locale,
 {
     if (U_FAILURE(status)) return;
 
+    parsePattern(); // Need this before initNumberFormatters(), to set fHasHanYearChar
+
+    // Simple-minded hack to force Gannen year numbering for ja@calendar=japanese
+    // if format is non-numeric (includes 年) and fDateOverride is not already specified.
+    // This does not update if applyPattern subsequently changes the pattern type.
+    if (fDateOverride.isBogus() && fHasHanYearChar &&
+            fCalendar != nullptr && uprv_strcmp(fCalendar->getType(),"japanese") == 0 &&
+            uprv_strcmp(fLocale.getLanguage(),"ja") == 0) {
+        fDateOverride.setTo(u"y=jpanyear", -1);
+    }
+
     // We don't need to check that the row count is >= 1, since all 2d arrays have at
     // least one row
     fNumberFormat = NumberFormat::createInstance(locale, status);
@@ -872,8 +883,6 @@ SimpleDateFormat::initialize(const Locale& locale,
     {
         status = U_MISSING_RESOURCE_ERROR;
     }
-
-    parsePattern();
 }
 
 /* Initialize the fields we use to disambiguate ambiguous years. Separate
@@ -4216,6 +4225,9 @@ void SimpleDateFormat::parsePattern() {
         if (ch == QUOTE) {
             inQuote = !inQuote;
         }
+        if (ch == 0x5E74) { // don't care whether this is inside quotes
+            fHasHanYearChar = TRUE;
+        }
         if (!inQuote) {
             if (ch == 0x6D) {  // 0x6D == 'm'
                 fHasMinute = TRUE;
index f1e7c96ee449aa6669f368f7220b10649b4719d4..a015c5be5c877a193deae7a955b6c1922878ec7c 100644 (file)
@@ -1599,6 +1599,7 @@ private:
 
     UBool                fHasMinute;
     UBool                fHasSecond;
+    UBool                fHasHanYearChar; // pattern contains the Han year character \u5E74
 
     /**
      * Sets fHasMinutes and fHasSeconds.
index 72dbb94a874d3d47fc73e33cfb5d5c520bdef92f..d5e3ba2ccd9f7065cbf076acaa7866d35a35d1bd 100644 (file)
@@ -42,6 +42,7 @@ static void TestContext(void);
 static void TestCalendarDateParse(void);
 static void TestParseErrorReturnValue(void);
 static void TestFormatForFields(void);
+static void TestForceGannenNumbering(void);
 
 void addDateForTest(TestNode** root);
 
@@ -61,6 +62,7 @@ void addDateForTest(TestNode** root)
     TESTCASE(TestOverrideNumberFormat);
     TESTCASE(TestParseErrorReturnValue);
     TESTCASE(TestFormatForFields);
+    TESTCASE(TestForceGannenNumbering);
 }
 /* Testing the DateFormat API */
 static void TestDateFormat()
@@ -1867,4 +1869,40 @@ static void TestFormatForFields(void) {
     }
 }
 
+static void TestForceGannenNumbering(void) {
+    UErrorCode status;
+    const char* locID = "ja_JP@calendar=japanese";
+    UDate refDate = 600336000000.0; // 1989 Jan 9 Monday = Heisei 1
+    const UChar* testSkeleton = u"yMMMd";
+
+    // Test Gannen year forcing
+    status = U_ZERO_ERROR;
+    UDateTimePatternGenerator* dtpgen = udatpg_open(locID, &status);
+    if (U_FAILURE(status)) {
+        log_data_err("Fail in udatpg_open locale %s: %s", locID, u_errorName(status));
+    } else {
+        UChar pattern[kUbufMax];
+        int32_t patlen = udatpg_getBestPattern(dtpgen, testSkeleton, -1, pattern, kUbufMax, &status);
+        if (U_FAILURE(status)) {
+            log_data_err("Fail in udatpg_getBestPattern locale %s: %s", locID, u_errorName(status));
+        } else  {
+            UDateFormat *testFmt = udat_open(UDAT_PATTERN, UDAT_PATTERN, locID, NULL, 0, pattern, patlen, &status);
+            if (U_FAILURE(status)) {
+                log_data_err("Fail in udat_open locale %s: %s", locID, u_errorName(status));
+            } else {
+                UChar testString[kUbufMax];
+                int32_t testStrLen = udat_format(testFmt, refDate, testString, kUbufMax, NULL, &status);
+                if (U_FAILURE(status)) {
+                    log_err("Fail in udat_format locale %s: %s", locID, u_errorName(status));
+                } else if (testStrLen < 3 || testString[2] != 0x5143) {
+                    char bbuf[kBbufMax];
+                    u_austrncpy(bbuf, testString, testStrLen);
+                    log_err("Formatting year 1 as Gannen, got%s but expected 3rd char to be 0x5143", bbuf);
+                }
+                udat_close(testFmt);
+            }
+        }
+        udatpg_close(dtpgen);
+    }
+}
 #endif /* #if !UCONFIG_NO_FORMATTING */
index a2e7019d3f429a31d90350ef67cc847e652ed961..4b38a3dc60ec75cee5a1d3edd5f238bcc7f914f4 100644 (file)
@@ -1062,9 +1062,9 @@ void DateIntervalFormatTest::testFormat() {
 
         "ja-u-ca-japanese", "H 31 03 15 09:00:00", "H 31 04 15 09:00:00", "GGGGGyMd", "H31/03/15\\uFF5E31/04/15",
 
-        "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GyMMMd", "\\u662D\\u548C64\\u5E741\\u67085\\u65E5\\uFF5E\\u5E73\\u62101\\u5E741\\u670815\\u65E5",
+        "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GyMMMd", "\\u662D\\u548C64\\u5E741\\u67085\\u65E5\\uFF5E\\u5E73\\u6210\\u5143\\u5E741\\u670815\\u65E5",
 
-        "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GGGGGyMd", "S64/1/5\\uFF5EH1/1/15", // The GGGGG/G forces inheritance from a different pattern, no padding
+        "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GGGGGyMd", "S64/1/5\\uFF5EH\\u5143/1/15", // The GGGGG/G forces inheritance from a different pattern, no padding
 
         "ja-u-ca-japanese", "H 31 04 15 09:00:00", JP_ERA_2019_NARROW " 1 05 15 09:00:00", "GGGGGyMd", "H31/4/15\\uFF5E" JP_ERA_2019_NARROW "1/5/15",
 
index bf0624e04a66c0214b6cea35e08cf68348d5ede6..3acb8af0b6f6132d830fb2b5b2d1c64e00889417 100644 (file)
 #include "string.h"
 #include "unicode/locid.h"
 #include "japancal.h"
+#include "unicode/localpointer.h"
+#include "unicode/datefmt.h"
+#include "unicode/smpdtfmt.h"
+#include "unicode/dtptngen.h"
 
 #if !UCONFIG_NO_FORMATTING
 
@@ -74,9 +78,10 @@ void IntlCalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &n
     CASE(4,TestBuddhistFormat);
     CASE(5,TestJapaneseFormat);
     CASE(6,TestJapanese3860);
-    CASE(7,TestPersian);
-    CASE(8,TestPersianFormat);
-    CASE(9,TestTaiwan);
+    CASE(7,TestForceGannenNumbering);
+    CASE(8,TestPersian);
+    CASE(9,TestPersianFormat);
+    CASE(10,TestTaiwan);
     default: name = ""; break;
     }
 }
@@ -636,20 +641,20 @@ void IntlCalendarTest::TestJapanese3860()
         // Test parse with missing era (should default to current era, heisei)
         // Test parse with incomplete information
         logln("Testing parse w/ missing era...");
-        SimpleDateFormat *fmt = new SimpleDateFormat(UnicodeString("y.M.d"), Locale("ja_JP@calendar=japanese"), status);
+        SimpleDateFormat *fmt = new SimpleDateFormat(UnicodeString("y/M/d"), Locale("ja_JP@calendar=japanese"), status);
         CHECK(status, "creating date format instance");
         if(!fmt) { 
             errln("Couldn't create en_US instance");
         } else {
             UErrorCode s2 = U_ZERO_ERROR;
             cal2->clear();
-            UnicodeString samplestr("1.1.9");
+            UnicodeString samplestr("1/5/9");
             logln(UnicodeString() + "Test Year: " + samplestr);
             aDate = fmt->parse(samplestr, s2);
             ParsePosition pp=0;
             fmt->parse(samplestr, *cal2, pp);
-            CHECK(s2, "parsing the 1.1.9 string");
-            logln("*cal2 after 119 parse:");
+            CHECK(s2, "parsing the 1/5/9 string");
+            logln("*cal2 after 159 parse:");
             str.remove();
             fmt2->format(aDate, str);
             logln(UnicodeString() + "as Gregorian Calendar: " + str);
@@ -660,7 +665,7 @@ void IntlCalendarTest::TestJapanese3860()
             int32_t expectYear = 1;
             int32_t expectEra = JapaneseCalendar::getCurrentEra();
             if((gotYear!=1) || (gotEra != expectEra)) {
-                errln(UnicodeString("parse "+samplestr+" of 'y.m.d' as Japanese Calendar, expected year ") + expectYear + 
+                errln(UnicodeString("parse "+samplestr+" of 'y/M/d' as Japanese Calendar, expected year ") + expectYear + 
                     UnicodeString(" and era ") + expectEra +", but got year " + gotYear + " and era " + gotEra + " (Gregorian:" + str +")");
             } else {            
                 logln(UnicodeString() + " year: " + gotYear + ", era: " + gotEra);
@@ -714,8 +719,50 @@ void IntlCalendarTest::TestJapanese3860()
     delete fmt2;
 }
 
+void IntlCalendarTest::TestForceGannenNumbering()
+{
+    UErrorCode status;
+    const char* locID = "ja_JP@calendar=japanese";
+    Locale loc(locID);
+    UDate refDate = 600336000000.0; // 1989 Jan 9 Monday = Heisei 1
+    UnicodeString testSkeleton("yMMMd");
 
+    // Test Gannen year forcing
+    status = U_ZERO_ERROR;
+    LocalPointer<DateFormat> testFmt1(DateFormat::createInstanceForSkeleton(testSkeleton, loc, status));
+    if (U_FAILURE(status)) {
+        dataerrln("Fail in DateFormat::createInstanceForSkeleton locale %s: %s", locID, u_errorName(status));
+    } else {
+        UnicodeString testString1;
+        testString1 = testFmt1->format(refDate, testString1);
+        if (testString1.length() < 3 || testString1.charAt(2) != 0x5143) {
+            errln(UnicodeString("Formatting year 1 as Gannen, got " + testString1 + " but expected 3rd char to be 0x5143"));
+        }
+    }
 
+    // Test disabling of Gannen year forcing
+    status = U_ZERO_ERROR;
+    LocalPointer<DateTimePatternGenerator> dtpgen(DateTimePatternGenerator::createInstance(loc, status));
+    if (U_FAILURE(status)) {
+        dataerrln("Fail in DateTimePatternGenerator::createInstance locale %s: %s", locID, u_errorName(status));
+    } else {
+        UnicodeString pattern = dtpgen->getBestPattern(testSkeleton, status);
+        if (U_FAILURE(status)) {
+            dataerrln("Fail in DateTimePatternGenerator::getBestPattern locale %s: %s", locID, u_errorName(status));
+        } else  {
+            LocalPointer<SimpleDateFormat> testFmt2(new SimpleDateFormat(pattern, UnicodeString(""), loc, status));
+            if (U_FAILURE(status)) {
+                dataerrln("Fail in new SimpleDateFormat locale %s: %s", locID, u_errorName(status));
+            } else {
+                UnicodeString testString2;
+                testString2 = testFmt2->format(refDate, testString2);
+                if (testString2.length() < 3 || testString2.charAt(2) != 0x0031) {
+                    errln(UnicodeString("Formatting year 1 with Gannen disabled, got " + testString2 + " but expected 3rd char to be 1"));
+                }
+            }
+        }
+    }
+}
 
 /**
  * Verify the Persian Calendar.
index 628b6e4cd40c0af6d54a5c60403405a774becee5..2d42bcc817f3180e78c5e1267c90eb77335aaa7b 100644 (file)
@@ -34,6 +34,7 @@ public:
     void TestJapanese(void);
     void TestJapaneseFormat(void);
     void TestJapanese3860(void);
+    void TestForceGannenNumbering(void);
     
     void TestPersian(void);
     void TestPersianFormat(void);
index c14c23881199a98de6b2f857292844d77652d9bc..737a127744a84de9810a422b2c1dd8d12ac1b9cd 100644 (file)
@@ -939,6 +939,11 @@ public class SimpleDateFormat extends DateFormat {
      */
     private transient boolean hasSecond;
 
+    /**
+     * DateFormat pattern contains the Han year character \u5E74=年, => non-numeric E Asian format.
+     */
+    private transient boolean hasHanYearChar;
+
     /*
      *  Capitalization setting, introduced in ICU 50
      *  Special serialization, see writeObject & readObject below
@@ -1136,11 +1141,20 @@ public class SimpleDateFormat extends DateFormat {
         setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
         initLocalZeroPaddingNumberFormat();
 
+        parsePattern(); // Need this before initNumberFormatters(), to set hasHanYearChar
+
+        // Simple-minded hack to force Gannen year numbering for ja@calendar=japanese
+        // if format is non-numeric (includes 年) and overrides are not already specified.
+        // This does not update if applyPattern subsequently changes the pattern type.
+        if (override == null && hasHanYearChar &&
+                calendar != null && calendar.getType().equals("japanese") &&
+                locale != null && locale.getLanguage().equals("ja")) {
+            override = "y=jpanyear";
+        }
+
         if (override != null) {
            initNumberFormatters(locale);
         }
-
-        parsePattern();
     }
 
     /**
@@ -4555,6 +4569,7 @@ public class SimpleDateFormat extends DateFormat {
     private void parsePattern() {
         hasMinute = false;
         hasSecond = false;
+        hasHanYearChar = false;
 
         boolean inQuote = false;
         for (int i = 0; i < pattern.length(); ++i) {
@@ -4562,6 +4577,9 @@ public class SimpleDateFormat extends DateFormat {
             if (ch == '\'') {
                 inQuote = !inQuote;
             }
+            if (ch == '\u5E74') { // don't care whether this is inside quotes
+                hasHanYearChar = true;
+            }
             if (!inQuote) {
                 if (ch == 'm') {
                     hasMinute = true;
index 590a84751a83ee23d77e37427356ad853fda7419..7b9739abefb501faa28f8ec48d47abf8acba533f 100644 (file)
@@ -19,6 +19,7 @@ import org.junit.runners.JUnit4;
 
 import com.ibm.icu.impl.LocaleUtility;
 import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.text.DateTimePatternGenerator;
 import com.ibm.icu.text.SimpleDateFormat;
 import com.ibm.icu.util.Calendar;
 import com.ibm.icu.util.JapaneseCalendar;
@@ -150,13 +151,17 @@ public class JapaneseTest extends CalendarTestFmwk {
     @Test
     public void Test3860()
     {
+        final String jCalShortPattern = "y/M/d"; // Note: just 'y' doesn't work here.
+        final String jCalGannenDate = "1/5/9"; // A date in the above format after the accession date for Heisei era (Heisei year 1 Jan 8)
+                                               // or the new era in Gregorian 2019 (new era year 1 May 1). If before the accession date,
+                                               // the year will be in the previous era.
         ULocale loc = new ULocale("ja_JP@calendar=japanese");
         Calendar cal = new JapaneseCalendar(loc);
         DateFormat enjformat = cal.getDateTimeFormat(DateFormat.FULL,DateFormat.FULL,new ULocale("en_JP@calendar=japanese"));
-        DateFormat format = cal.getDateTimeFormat(DateFormat.SHORT,DateFormat.SHORT,loc); // SHORT => no jpanyear since we apply "y.M.d" anyway
-        ((SimpleDateFormat)format).applyPattern("y.M.d");  // Note: just 'y' doesn't work here.
+        DateFormat format = cal.getDateTimeFormat(DateFormat.SHORT,DateFormat.SHORT,loc); // SHORT => no jpanyear since we will apply a short pattern
+        ((SimpleDateFormat)format).applyPattern(jCalShortPattern);
         ParsePosition pos = new ParsePosition(0);
-        Date aDate = format.parse("1.5.9", pos); // after accession (Heisei or next). Before accession in the same year would format with the previous era.
+        Date aDate = format.parse(jCalGannenDate, pos);
         String inEn = enjformat.format(aDate);
 
         cal.clear();
@@ -207,7 +212,7 @@ public class JapaneseTest extends CalendarTestFmwk {
 
         // Tests for formats with gannen numbering Gy年
         pos.setIndex(0);
-        aDate = format.parse("1.5.9", pos); // reset
+        aDate = format.parse(jCalGannenDate, pos); // reset
         DateFormat fmtWithGannen = DateFormat.getDateInstance(cal, DateFormat.MEDIUM, loc);
         String aString = fmtWithGannen.format(aDate);
         if (aString.charAt(2) != '\u5143') { // 元
@@ -227,6 +232,36 @@ public class JapaneseTest extends CalendarTestFmwk {
         }
     }
 
+    @Test
+    public void TestForceGannenNumbering() {
+        final String jCalShortPattern = "y/M/d"; // Note: just 'y' doesn't work here.
+        final String jCalGannenDate = "1/5/9"; // A date in the above format after the accession date for Heisei era (Heisei year 1 Jan 8)
+                                               // or the new era in Gregorian 2019 (new era year 1 May 1). If before the accession date,
+                                               // the year will be in the previous era.
+        ULocale loc = new ULocale("ja_JP@calendar=japanese");
+        DateFormat refFmt = DateFormat.getDateInstance(DateFormat.SHORT, loc);
+        ((SimpleDateFormat)refFmt).applyPattern(jCalShortPattern);
+        ParsePosition pos = new ParsePosition(0);
+        Date refDate = refFmt.parse(jCalGannenDate, pos);
+        final String testSkeleton = "yMMMd";
+
+        // Test Gannen year forcing
+        DateFormat testFmt1 = DateFormat.getInstanceForSkeleton(testSkeleton, loc);
+        String testString1 = testFmt1.format(refDate);
+        if (testString1.length() < 3 || testString1.charAt(2) != '\u5143') { // 元
+            errln("Formatting year 1 as Gannen, got " + testString1 + " but expected 3rd char to be \u5143");
+        }
+
+        // Test disabling of Gannen year forcing
+        DateTimePatternGenerator dtpgen = DateTimePatternGenerator.getInstance(loc);
+        String pattern = dtpgen.getBestPattern(testSkeleton);
+        SimpleDateFormat testFmt2 = new SimpleDateFormat(pattern, "", loc); // empty override string to disable Gannen year numbering
+        String testString2 = testFmt2.format(refDate);
+        if (testString2.length() < 3 || testString2.charAt(2) != '1') {
+            errln("Formatting year 1 with Gannen disabled, got " + testString2 + " but expected 3rd char to be 1");
+        }
+    }
+
     @Test
     public void Test5345parse() {
         // Test parse with incomplete information
index 7c7a8a45be3c8e36f743cecdaaba458e9b8ba17c..8fd76a414c7624b1561edc678d8e4e81cfa1261c 100644 (file)
@@ -710,11 +710,11 @@ public class DateIntervalFormatTest extends TestFmwk {
 
                 "ja-u-ca-japanese", "H 31 03 15 09:00:00", "H 31 04 15 09:00:00", "GGGGGyMd", "H31/03/15\uFF5E31/04/15",
 
-                "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GyMMMd", "\u662D\u548C64\u5E741\u67085\u65E5\uFF5E\u5E73\u62101\u5E741\u670815\u65E5",
+                "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GyMMMd", "\u662D\u548C64\u5E741\u67085\u65E5\uFF5E\u5E73\u6210\u5143\u5E741\u670815\u65E5",
 
-                "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GGGGGyMd", "S64\u5E741\u67085\u65E5\uFF5EH1\u5E741\u670815\u65E5", // The GGGGG/G forces inheritance from a different pattern, non-numeric-only
+                "ja-u-ca-japanese", "S 64 01 05 09:00:00", "H 1 01 15 09:00:00",  "GGGGGyMd", "S64\u5E741\u67085\u65E5\uFF5EH\u5143\u5E741\u670815\u65E5", // The GGGGG/G forces inheritance from a different pattern, non-numeric-only
 
-                "ja-u-ca-japanese", "H 31 04 15 09:00:00", DateFormat.JP_ERA_2019_NARROW+" 1 05 15 09:00:00", "GGGGGyMd", "H31\u5E744\u670815\u65E5\uFF5E"+DateFormat.JP_ERA_2019_NARROW+"1\u5E745\u670815\u65E5",
+                "ja-u-ca-japanese", "H 31 04 15 09:00:00", DateFormat.JP_ERA_2019_NARROW+" 1 05 15 09:00:00", "GGGGGyMd", "H31\u5E744\u670815\u65E5\uFF5E"+DateFormat.JP_ERA_2019_NARROW+"\u5143\u5E745\u670815\u65E5",
 
         };
         expect(DATA, DATA.length);