]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/i18n/smpdtfmt.cpp
ICU-66108.tar.gz
[apple/icu.git] / icuSources / i18n / smpdtfmt.cpp
index f278efd9c61a94f314e5ca21d8ca159e885fa8ea..825ddaad46b7ee27dcd0d0ad9636637361f977c6 100644 (file)
@@ -53,6 +53,8 @@
 #include "unicode/vtzone.h"
 #include "unicode/udisplaycontext.h"
 #include "unicode/brkiter.h"
+#include "unicode/rbnf.h"
+#include "unicode/dtptngen.h"
 #include "uresimp.h"
 #include "olsontz.h"
 #include "patternprops.h"
 #include "cstr.h"
 #include "dayperiodrules.h"
 #include "tznames_impl.h"   // ZONE_NAME_U16_MAX
+#include "number_utypes.h"
+#include "dtptngen_impl.h" // for datePatternHasNumericCore()
 
-#if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL)
+#define DEBUG_SYNTHETIC_TIMEFMTS 0
+
+#if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL) || DEBUG_SYNTHETIC_TIMEFMTS
 #include <stdio.h>
 #endif
 
@@ -271,7 +277,7 @@ static const int32_t gFieldRangeBiasLenient[] = {
 static const int32_t HEBREW_CAL_CUR_MILLENIUM_START_YEAR = 5000;
 static const int32_t HEBREW_CAL_CUR_MILLENIUM_END_YEAR = 6000;
 
-static UMutex LOCK = U_MUTEX_INITIALIZER;
+static UMutex LOCK;
 
 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleDateFormat)
 
@@ -285,9 +291,9 @@ SimpleDateFormat::NSOverride::~NSOverride() {
 void SimpleDateFormat::NSOverride::free() {
     NSOverride *cur = this;
     while (cur) {
-        NSOverride *next = cur->next;
+        NSOverride *next_temp = cur->next;
         delete cur;
-        cur = next;
+        cur = next_temp;
     }
 }
 
@@ -295,13 +301,12 @@ void SimpleDateFormat::NSOverride::free() {
 // to modify it so that it doesn't use thousands separators, doesn't always
 // show the decimal point, and recognizes integers only when parsing
 static void fixNumberFormatForDates(NumberFormat &nf) {
-    nf.setGroupingUsed(FALSE);
-    DecimalFormat* decfmt = dynamic_cast<DecimalFormat*>(&nf);
-    if (decfmt != NULL) {
-        decfmt->setDecimalSeparatorAlwaysShown(FALSE);
-    }
-    nf.setParseIntegerOnly(TRUE);
-    nf.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
+    // Use new group setter equivalent to
+    //    setGroupingUsed(FALSE);
+    //    setDecimalSeparatorAlwaysShown(FALSE);
+    //    setParseIntegerOnly(TRUE);
+    //    setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
+    nf.setDateSettings(); // Apple rdar://50064762
 }
 
 static const SharedNumberFormat *createSharedNumberFormat(
@@ -355,57 +360,6 @@ const NumberFormat *SimpleDateFormat::getNumberFormatByIndex(
     return &(**fSharedNumberFormatters[index]);
 }
 
-class SimpleDateFormatMutableNFNode {
- public:
-    const NumberFormat *key;
-    NumberFormat *value;
-    SimpleDateFormatMutableNFNode()
-            : key(NULL), value(NULL) { }
-    ~SimpleDateFormatMutableNFNode() {
-        delete value;
-    }
- private:
-    SimpleDateFormatMutableNFNode(const SimpleDateFormatMutableNFNode &);
-    SimpleDateFormatMutableNFNode &operator=(const SimpleDateFormatMutableNFNode &);
-};
-
-// Single threaded cache of non const NumberFormats. Designed to be stack
-// allocated and used for a single format call.
-class SimpleDateFormatMutableNFs : public UMemory {
- public:
-    SimpleDateFormatMutableNFs() {
-    }
-
-    // Returns a non-const clone of nf which can be safely modified.
-    // Subsequent calls with same nf will return the same non-const clone.
-    // This object maintains ownership of all returned non-const
-    // NumberFormat objects. On memory allocation error returns NULL.
-    // Caller must check for NULL return value.
-    NumberFormat *get(const NumberFormat *nf) {
-        if (nf == NULL) {
-            return NULL;
-        }
-        int32_t idx = 0;
-        while (nodes[idx].value) {
-            if (nf == nodes[idx].key) {
-                return nodes[idx].value;
-            }
-            ++idx;
-        }
-        U_ASSERT(idx < UDAT_FIELD_COUNT);
-        nodes[idx].key = nf;
-        nodes[idx].value = (NumberFormat *) nf->clone();
-        return nodes[idx].value;
-    }
- private:
-    // +1 extra for sentinel. If each field had its own NumberFormat, this
-    // cache would have to allocate UDAT_FIELD_COUNT mutable versions worst
-    // case.
-    SimpleDateFormatMutableNFNode nodes[UDAT_FIELD_COUNT + 1];
-    SimpleDateFormatMutableNFs(const SimpleDateFormatMutableNFs &);
-    SimpleDateFormatMutableNFs &operator=(const SimpleDateFormatMutableNFs &);
-};
-
 //----------------------------------------------------------------------
 
 SimpleDateFormat::~SimpleDateFormat()
@@ -417,6 +371,7 @@ SimpleDateFormat::~SimpleDateFormat()
     if (fTimeZoneFormat) {
         delete fTimeZoneFormat;
     }
+    freeFastNumberFormatters();
 
 #if !UCONFIG_NO_BREAK_ITERATION
     delete fCapitalizationBrkIter;
@@ -657,6 +612,7 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other)
     if (this == &other) {
         return *this;
     }
+    freeFastNumberFormatters(); // deletes refs to fNumberFormat's symbols
     DateFormat::operator=(other);
     fDateOverride = other.fDateOverride;
     fTimeOverride = other.fTimeOverride;
@@ -705,12 +661,15 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other)
         }
     }
 
+    UErrorCode localStatus = U_ZERO_ERROR;
+    initFastNumberFormatters(localStatus);
+
     return *this;
 }
 
 //----------------------------------------------------------------------
 
-Format*
+SimpleDateFormat*
 SimpleDateFormat::clone() const
 {
     return new SimpleDateFormat(*this);
@@ -744,6 +703,14 @@ SimpleDateFormat::operator==(const Format& other) const
 }
 
 //----------------------------------------------------------------------
+static const UChar* timeSkeletons[4] = {
+    u"jmmsszzzz",   // kFull
+    u"jmmssz",      // kLong
+    u"jmmss",       // kMedium
+    u"jmm",         // kShort
+};
+
+enum { kBaseNameMax = ULOC_LANG_CAPACITY + ULOC_SCRIPT_CAPACITY + ULOC_COUNTRY_CAPACITY }; // includes separators and 0 term
 
 void SimpleDateFormat::construct(EStyle timeStyle,
                                  EStyle dateStyle,
@@ -758,18 +725,45 @@ void SimpleDateFormat::construct(EStyle timeStyle,
     if (U_FAILURE(status)) return;
 
     // Load date time patterns directly from resources.
+    // rdar://problem/26911014: If no resource bundle exists for the requested locale, we generally want to bias ourselves
+    // toward preserving the country (rather than the language, which is how things normally work) when retrieving certain
+    // date/time formatting patterns.  We want to do this for date patterns, but not for time and date+time patterns
+    // (this might change-- see rdar://problem/62242807 ).  We use countryBundle below for these patterns.  Note that
+    // countryBundle isn't _always_ what you get from calling ures_openWithCountryFallback() because date patterns are
+    // complicated and sometimes include language-based text.  The openResourceBundleForDatePatterns() function in
+    // dtptngen_impl.h handles the exceptions and goves us either the result of ures_openWithCountryFallback() or
+    // ures_open() depending on the actual patterns.
     const char* cType = fCalendar ? fCalendar->getType() : NULL;
+    UBool fallingBackByCountry = FALSE;
     LocalUResourceBundlePointer bundle(ures_open(NULL, locale.getBaseName(), &status));
+    LocalUResourceBundlePointer countryBundle(ures_openWithCountryFallback(NULL, locale.getBaseName(), &fallingBackByCountry, &status));
     if (U_FAILURE(status)) return;
+    
+    // If we're potentially falling back by country, check to see whether the language and country fallback locales
+    // have the same numbering system.  If they don't, fall back by language instead.  (Many date/time patterns have
+    // embedded assumptions about which numbering system they're being used with and don't behave well with other ones,
+    // especially if different writing directions are involved-- see rdar://69523017.)
+    if (fallingBackByCountry) {
+        int32_t dummy = -1;
+        const UChar* languageLocaleNumbers = ures_getStringByKeyWithFallback(bundle.getAlias(), "NumberElements/default", &dummy, &status);
+        const UChar* countryLocaleNumbers = ures_getStringByKeyWithFallback(countryBundle.getAlias(), "NumberElements/default", &dummy, &status);
+        if (U_FAILURE(status) || u_strcmp(languageLocaleNumbers, countryLocaleNumbers) != 0) {
+            fallingBackByCountry = FALSE;
+        }
+    }
 
     UBool cTypeIsGregorian = TRUE;
     LocalUResourceBundlePointer dateTimePatterns;
+    LocalUResourceBundlePointer countryDateTimePatterns;
     if (cType != NULL && uprv_strcmp(cType, "gregorian") != 0) {
         CharString resourcePath("calendar/", status);
         resourcePath.append(cType, status).append("/DateTimePatterns", status);
         dateTimePatterns.adoptInstead(
             ures_getByKeyWithFallback(bundle.getAlias(), resourcePath.data(),
                                       (UResourceBundle*)NULL, &status));
+        countryDateTimePatterns.adoptInstead(
+            ures_getByKeyWithFallback(countryBundle.getAlias(), resourcePath.data(),
+                                      (UResourceBundle*)NULL, &status));
         cTypeIsGregorian = FALSE;
     }
 
@@ -780,11 +774,13 @@ void SimpleDateFormat::construct(EStyle timeStyle,
             ures_getByKeyWithFallback(bundle.getAlias(),
                                       "calendar/gregorian/DateTimePatterns",
                                       (UResourceBundle*)NULL, &status));
+        countryDateTimePatterns.adoptInstead(
+            ures_getByKeyWithFallback(countryBundle.getAlias(),
+                                      "calendar/gregorian/DateTimePatterns",
+                                      (UResourceBundle*)NULL, &status));
     }
     if (U_FAILURE(status)) return;
 
-    LocalUResourceBundlePointer currentBundle;
-
     if (ures_getSize(dateTimePatterns.getAlias()) <= kDateTime)
     {
         status = U_INVALID_FORMAT_ERROR;
@@ -803,8 +799,6 @@ void SimpleDateFormat::construct(EStyle timeStyle,
         return;
     }
 
-    const UChar *resStr,*ovrStr;
-    int32_t resStrLen,ovrStrLen = 0;
     fDateOverride.setToBogus();
     fTimeOverride.setToBogus();
 
@@ -813,56 +807,17 @@ void SimpleDateFormat::construct(EStyle timeStyle,
     // and time pattern strings.
     if ((timeStyle != kNone) && (dateStyle != kNone))
     {
-        currentBundle.adoptInstead(
-                ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)timeStyle, NULL, &status));
-        if (U_FAILURE(status)) {
-           status = U_INVALID_FORMAT_ERROR;
-           return;
+        UnicodeString ovrStr;
+        UnicodeString tempus1 = getPatternForTimeStyle(timeStyle, locale, dateTimePatterns.getAlias(), ovrStr, status);
+        if (!ovrStr.isEmpty()) {
+            fTimeOverride = ovrStr;
         }
-        switch (ures_getType(currentBundle.getAlias())) {
-            case URES_STRING: {
-               resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status);
-               break;
-            }
-            case URES_ARRAY: {
-               resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status);
-               ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status);
-               fTimeOverride.setTo(TRUE, ovrStr, ovrStrLen);
-               break;
-            }
-            default: {
-               status = U_INVALID_FORMAT_ERROR;
-               return;
-            }
-        }
-
-        UnicodeString tempus1(TRUE, resStr, resStrLen);
-
-        currentBundle.adoptInstead(
-                ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)dateStyle, NULL, &status));
-        if (U_FAILURE(status)) {
-           status = U_INVALID_FORMAT_ERROR;
-           return;
-        }
-        switch (ures_getType(currentBundle.getAlias())) {
-            case URES_STRING: {
-               resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status);
-               break;
-            }
-            case URES_ARRAY: {
-               resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status);
-               ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status);
-               fDateOverride.setTo(TRUE, ovrStr, ovrStrLen);
-               break;
-            }
-            default: {
-               status = U_INVALID_FORMAT_ERROR;
-               return;
-            }
+        
+        UnicodeString tempus2 = getPatternForDateStyle(dateStyle, dateTimePatterns.getAlias(), countryDateTimePatterns.getAlias(), fallingBackByCountry, ovrStr, status);
+        if (!ovrStr.isEmpty()) {
+            fDateOverride = ovrStr;
         }
 
-        UnicodeString tempus2(TRUE, resStr, resStrLen);
-
         int32_t glueIndex = kDateTime;
         int32_t patternsSize = ures_getSize(dateTimePatterns.getAlias());
         if (patternsSize >= (kDateTimeOffset + kShort + 1)) {
@@ -870,72 +825,174 @@ void SimpleDateFormat::construct(EStyle timeStyle,
             glueIndex = (int32_t)(kDateTimeOffset + (dateStyle - kDateOffset));
         }
 
-        resStr = ures_getStringByIndex(dateTimePatterns.getAlias(), glueIndex, &resStrLen, &status);
-        SimpleFormatter(UnicodeString(TRUE, resStr, resStrLen), 2, 2, status).
+        UnicodeString resStr(ures_getUnicodeStringByIndex(dateTimePatterns.getAlias(), glueIndex, &status));
+        SimpleFormatter(resStr, 2, 2, status).
                 format(tempus1, tempus2, fPattern, status);
     }
     // if the pattern includes just time data or just date date, load the appropriate
     // pattern string from the resources
     // setTo() - see DateFormatSymbols::assignArray comments
     else if (timeStyle != kNone) {
-        currentBundle.adoptInstead(
-                ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)timeStyle, NULL, &status));
-        if (U_FAILURE(status)) {
-           status = U_INVALID_FORMAT_ERROR;
-           return;
+        UnicodeString ovrStr;
+        UnicodeString timePattern = getPatternForTimeStyle(timeStyle, locale, dateTimePatterns.getAlias(), ovrStr, status);
+        if (!ovrStr.isEmpty()) {
+            fDateOverride = ovrStr; // is this right?  This is what the original code had...
         }
-        switch (ures_getType(currentBundle.getAlias())) {
-            case URES_STRING: {
-               resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status);
-               break;
-            }
-            case URES_ARRAY: {
-               resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status);
-               ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status);
-               fDateOverride.setTo(TRUE, ovrStr, ovrStrLen);
-               break;
-            }
-            default: {
-               status = U_INVALID_FORMAT_ERROR;
-               return;
-            }
-        }
-        fPattern.setTo(TRUE, resStr, resStrLen);
+        fPattern = timePattern;
     }
     else if (dateStyle != kNone) {
-        currentBundle.adoptInstead(
-                ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)dateStyle, NULL, &status));
-        if (U_FAILURE(status)) {
-           status = U_INVALID_FORMAT_ERROR;
-           return;
-        }
-        switch (ures_getType(currentBundle.getAlias())) {
-            case URES_STRING: {
-               resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status);
-               break;
-            }
-            case URES_ARRAY: {
-               resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status);
-               ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status);
-               fDateOverride.setTo(TRUE, ovrStr, ovrStrLen);
-               break;
-            }
-            default: {
-               status = U_INVALID_FORMAT_ERROR;
-               return;
-            }
+        UnicodeString ovrStr;
+        UnicodeString datePattern = getPatternForDateStyle(dateStyle, dateTimePatterns.getAlias(), countryDateTimePatterns.getAlias(), fallingBackByCountry, ovrStr, status);
+        if (!ovrStr.isEmpty()) {
+            fDateOverride = ovrStr;
         }
-        fPattern.setTo(TRUE, resStr, resStrLen);
+        fPattern = datePattern;
     }
 
     // and if it includes _neither_, that's an error
     else
         status = U_INVALID_FORMAT_ERROR;
 
-    // finally, finish initializing by creating a Calendar and a NumberFormat
     initialize(locale, status);
 }
 
+UnicodeString
+SimpleDateFormat::getPatternForTimeStyle(EStyle timeStyle,
+                                         const Locale& locale,
+                                         UResourceBundle* dateTimePatterns,
+                                         UnicodeString& ovrStr,
+                                         UErrorCode& status) {
+    UnicodeString timePattern;
+    if (timeStyle >= kFull && timeStyle <= kShort) {
+        const char* baseLoc = locale.getBaseName();
+        if (baseLoc!=NULL && baseLoc[0]!=0 && uprv_strcmp(baseLoc,"und")!=0) {
+            UErrorCode useStatus = U_ZERO_ERROR;
+            const char* validLoc = getLocaleID(ULOC_VALID_LOCALE, useStatus);
+            if (U_SUCCESS(useStatus) && uprv_strcmp(validLoc,baseLoc)!=0) {
+                char minLoc[kBaseNameMax];
+                uloc_minimizeSubtags(baseLoc, minLoc, kBaseNameMax, &useStatus);
+                minLoc[kBaseNameMax-1] = 0; // ensure zero term
+                const char* actualLoc = getLocaleID(ULOC_ACTUAL_LOCALE, useStatus);
+                if (U_SUCCESS(useStatus) && uprv_strcmp(actualLoc,minLoc)!=0) {
+                    // The standard time formats may have the wrong time cycle, because:
+                    // * the valid locale is not the same as the base locale, or
+                    // * the actual locale the patterns are coming from is not the same
+                    //   as the minimized locale.
+                    // We could *also* check whether they do actually have a mismatch with
+                    // the time cycle preferences for the region, but that is a lot more
+                    // work for little or no additional benefit, since just going ahead
+                    // and always synthesizing the time format as per the following should
+                    // create a locale-appropriate pattern with cycle that matches the
+                    // region preferences anyway (for completely unsupported languages,
+                    // this will use root patterns for the appropriate cycle for the
+                    // likely subtags resion).
+                    LocalPointer<DateTimePatternGenerator> dtpg(DateTimePatternGenerator::createInstance(locale, useStatus, TRUE));
+                    if (U_SUCCESS(useStatus)) {
+                        UnicodeString timeSkeleton(TRUE, timeSkeletons[timeStyle], -1);
+                        timePattern = dtpg->getBestPattern(timeSkeleton, useStatus);
+#if DEBUG_SYNTHETIC_TIMEFMTS
+                        if (timePattern.length() != 0) {
+                            char bbuf[32];
+                            timePattern.extract(0,timePattern.length(),bbuf,32);
+                            printf("\n## for locale %s, validLoc %s, minLoc %s, actualLoc %s, synth timePat %s\n", locale.getName(), validLoc, minLoc, actualLoc, bbuf);
+                        }
+#endif
+                    }
+                }
+            }
+        }
+    }
+    
+    if (timePattern.isEmpty()) {
+        timePattern = getPatternString((int32_t)timeStyle, dateTimePatterns, ovrStr, status);
+    }
+    return timePattern;
+}
+
+UnicodeString
+SimpleDateFormat::getPatternForDateStyle(EStyle dateStyle,
+                                         UResourceBundle* languageDateTimePatterns,
+                                         UResourceBundle* countryDateTimePatterns,
+                                         UBool& fallingBackByCountry,
+                                         UnicodeString& ovrStr,
+                                         UErrorCode& status) {
+    UnicodeString languageOverride;
+    UnicodeString languagePattern = getPatternString((int32_t)dateStyle, languageDateTimePatterns, languageOverride, status);
+    
+    // by default, we should fetch the pattern from the language resource
+    UnicodeString result = languagePattern;
+    ovrStr = languageOverride;
+    
+    // but IF the country resource is actually different from the lanuguage resource AND the caller is asking
+    // for a medium or short date format AND that format in the country resource is all-numeric, return the
+    // pattern from the country resource instead
+    if (fallingBackByCountry) {
+        fallingBackByCountry = FALSE;
+        if ((dateStyle == kDateOffset + kMedium || dateStyle == kDateOffset + kShort)) {
+            UnicodeString countryOverride;
+            UnicodeString countryPattern = getPatternString((int32_t)dateStyle, countryDateTimePatterns, countryOverride, status);
+            UBool stripRTLmarks = uloc_isRightToLeft(ures_getLocaleByType(countryDateTimePatterns, ULOC_ACTUAL_LOCALE, &status)) && !uloc_isRightToLeft(ures_getLocaleByType(languageDateTimePatterns, ULOC_ACTUAL_LOCALE, &status));
+
+            if (U_SUCCESS(status)) {
+                if (datePatternHasNumericCore(countryPattern)) {
+                    ovrStr = countryOverride;
+                    result = countryPattern;
+                    fallingBackByCountry = TRUE;
+                    if (stripRTLmarks) {
+                        // the date formats for RTL languages often include Unicode right-to-left marks to get the format
+                        // to lay out correctly.  If we got the pattern from a RTL locale and the requested language is
+                        // not a RTL language, we need to strip those out (same comment below)
+                        result.findAndReplace(UnicodeString(u'\u200f'), UnicodeString());
+                    }
+                } else if (dateStyle == kDateOffset + kMedium && datePatternHasNumericCore(languagePattern)) {
+                    // if the user asked for the MEDIUM format, it's NOT all-numeric in the country resource, but it IS
+                    // all-numeric in the language resource, return the SHORT format from the country resource
+                    countryPattern = getPatternString(kDateOffset + kShort, countryDateTimePatterns, countryOverride, status);
+                    if (datePatternHasNumericCore(countryPattern)) {
+                        ovrStr = countryOverride;
+                        result = countryPattern;
+                        fallingBackByCountry = TRUE;
+                        if (stripRTLmarks) {
+                            result.findAndReplace(UnicodeString(u'\u200f'), UnicodeString());
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return result;
+}
+
+UnicodeString
+SimpleDateFormat::getPatternString(int32_t index,
+                                   UResourceBundle* dateTimePatterns,
+                                   UnicodeString& ovrStr,
+                                   UErrorCode& status) {
+    UnicodeString resStr;
+    LocalUResourceBundlePointer currentBundle(ures_getByIndex(dateTimePatterns, index, NULL, &status));
+    ovrStr.remove();
+    if (U_FAILURE(status)) {
+       status = U_INVALID_FORMAT_ERROR;
+       return resStr;
+    }
+    switch (ures_getType(currentBundle.getAlias())) {
+        case URES_STRING: {
+           resStr = ures_getUnicodeString(currentBundle.getAlias(), &status);
+           break;
+        }
+        case URES_ARRAY: {
+           resStr = ures_getUnicodeStringByIndex(currentBundle.getAlias(), 0, &status);
+           ovrStr = ures_getUnicodeStringByIndex(currentBundle.getAlias(), 1, &status);
+           break;
+        }
+        default: {
+           status = U_INVALID_FORMAT_ERROR;
+           return resStr;
+        }
+    }
+    return resStr;
+}
+
 //----------------------------------------------------------------------
 
 Calendar*
@@ -953,6 +1010,8 @@ SimpleDateFormat::initialize(const Locale& locale,
 {
     if (U_FAILURE(status)) return;
 
+    parsePattern(); // Need this before initNumberFormatters(), to set fHasHanYearChar
+
     // If the locale has @[....]numbers=hanidays we want to *delete* that (so it
     // it is not used for every field) and then set fDateOverride to "d=hanidays"
     // (as with std formats for zh@calendar=chinese) to use hanidays for d field.
@@ -967,6 +1026,14 @@ SimpleDateFormat::initialize(const Locale& locale,
             fDateOverride.setTo(hanidaysOverride,-1);
         }
     }
+    // Simple-minded hack to force Gannen year numbering for ja@calendar=japanese
+    // if format is non-numeric (includes 年) and fDateOverride is not already specified.
+    // Now this does get updated 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
@@ -976,15 +1043,14 @@ SimpleDateFormat::initialize(const Locale& locale,
         fixNumberFormatForDates(*fNumberFormat);
         //fNumberFormat->setLenient(TRUE); // Java uses a custom DateNumberFormat to format/parse
 
-        initNumberFormatters(locale,status);
+        initNumberFormatters(locale, status);
+        initFastNumberFormatters(status);
 
     }
     else if (U_SUCCESS(status))
     {
         status = U_MISSING_RESOURCE_ERROR;
     }
-
-    parsePattern();
 }
 
 /* Initialize the fields we use to disambiguate ambiguous years. Separate
@@ -1091,11 +1157,6 @@ SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo,
     int32_t fieldNum = 0;
     UDisplayContext capitalizationContext = getContext(UDISPCTX_TYPE_CAPITALIZATION, status);
 
-    // Create temporary cache of mutable number format objects. This way
-    // subFormat won't have to clone the const NumberFormat for each field.
-    // if several fields share the same NumberFormat, which will almost
-    // always be the case, this is a big save.
-    SimpleDateFormatMutableNFs mutableNFs;
     // loop through the pattern string character by character
     for (int32_t i = 0; i < fPattern.length() && U_SUCCESS(status); ++i) {
         UChar ch = fPattern[i];
@@ -1103,7 +1164,7 @@ SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo,
         // Use subFormat() to format a repeated pattern character
         // when a different pattern or non-pattern character is seen
         if (ch != prevCh && count > 0) {
-            subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, mutableNFs, status);
+            subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, status);
             count = 0;
         }
         if (ch == QUOTE) {
@@ -1130,7 +1191,7 @@ SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo,
 
     // Format the last item in the pattern, if any
     if (count > 0) {
-        subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, mutableNFs, status);
+        subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, status);
     }
 
     if (calClone != NULL) {
@@ -1325,6 +1386,49 @@ _appendSymbolWithMonthPattern(UnicodeString& dst, int32_t value, const UnicodeSt
 }
 
 //----------------------------------------------------------------------
+
+static number::LocalizedNumberFormatter*
+createFastFormatter(const DecimalFormat* df, int32_t minInt, int32_t maxInt, UErrorCode& status) {
+    const number::LocalizedNumberFormatter* lnfBase = df->toNumberFormatter(status);
+    if (U_FAILURE(status)) {
+        return nullptr;
+    }
+    return lnfBase->integerWidth(
+        number::IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt)
+    ).clone().orphan();
+}
+
+void SimpleDateFormat::initFastNumberFormatters(UErrorCode& status) {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    auto* df = dynamic_cast<DecimalFormat*>(fNumberFormat);
+    if (df == nullptr) {
+        return;
+    }
+    df->setDFSShallowCopy(TRUE);
+    fFastNumberFormatters[SMPDTFMT_NF_1x10] = createFastFormatter(df, 1, 10, status);
+    fFastNumberFormatters[SMPDTFMT_NF_2x10] = createFastFormatter(df, 2, 10, status);
+    fFastNumberFormatters[SMPDTFMT_NF_3x10] = createFastFormatter(df, 3, 10, status);
+    fFastNumberFormatters[SMPDTFMT_NF_4x10] = createFastFormatter(df, 4, 10, status);
+    fFastNumberFormatters[SMPDTFMT_NF_2x2] = createFastFormatter(df, 2, 2, status);
+    df->setDFSShallowCopy(FALSE);
+}
+
+void SimpleDateFormat::freeFastNumberFormatters() {
+    delete fFastNumberFormatters[SMPDTFMT_NF_1x10];
+    delete fFastNumberFormatters[SMPDTFMT_NF_2x10];
+    delete fFastNumberFormatters[SMPDTFMT_NF_3x10];
+    delete fFastNumberFormatters[SMPDTFMT_NF_4x10];
+    delete fFastNumberFormatters[SMPDTFMT_NF_2x2];
+    fFastNumberFormatters[SMPDTFMT_NF_1x10] = nullptr;
+    fFastNumberFormatters[SMPDTFMT_NF_2x10] = nullptr;
+    fFastNumberFormatters[SMPDTFMT_NF_3x10] = nullptr;
+    fFastNumberFormatters[SMPDTFMT_NF_4x10] = nullptr;
+    fFastNumberFormatters[SMPDTFMT_NF_2x2] = nullptr;
+}
+
+
 void
 SimpleDateFormat::initNumberFormatters(const Locale &locale,UErrorCode &status) {
     if (U_FAILURE(status)) {
@@ -1383,15 +1487,15 @@ SimpleDateFormat::processOverrideString(const Locale &locale, const UnicodeStrin
 
         int32_t nsNameHash = nsName.hashCode();
         // See if the numbering system is in the override list, if not, then add it.
-        NSOverride *cur = overrideList;
+        NSOverride *curr = overrideList;
         const SharedNumberFormat *snf = NULL;
         UBool found = FALSE;
-        while ( cur && !found ) {
-            if ( cur->hash == nsNameHash ) {
-                snf = cur->snf;
+        while ( curr && !found ) {
+            if ( curr->hash == nsNameHash ) {
+                snf = curr->snf;
                 found = TRUE;
             }
-            cur = cur->next;
+            curr = curr->next;
         }
 
         if (!found) {
@@ -1474,7 +1578,6 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
                             int32_t fieldNum,
                             FieldPositionHandler& handler,
                             Calendar& cal,
-                            SimpleDateFormatMutableNFs &mutableNFs,
                             UErrorCode& status) const
 {
     if (U_FAILURE(status)) {
@@ -1487,7 +1590,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
     UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(ch);
     const int32_t maxIntCount = 10;
     int32_t beginOffset = appendTo.length();
-    NumberFormat *currentNumberFormat;
+    const NumberFormat *currentNumberFormat;
     DateFormatSymbols::ECapitalizationContextUsageType capContextUsageType = DateFormatSymbols::kCapContextUsageOther;
 
     UBool isHebrewCalendar = (uprv_strcmp(cal.getType(),"hebrew") == 0);
@@ -1512,9 +1615,9 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
         return;
     }
 
-    currentNumberFormat = mutableNFs.get(getNumberFormatByIndex(patternCharIndex));
+    currentNumberFormat = getNumberFormatByIndex(patternCharIndex);
     if (currentNumberFormat == NULL) {
-        status = U_MEMORY_ALLOCATION_ERROR;
+        status = U_INTERNAL_PROGRAM_ERROR;
         return;
     }
     UnicodeString hebr("hebr", 4, US_INV);
@@ -1634,18 +1737,15 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
     case UDAT_FRACTIONAL_SECOND_FIELD:
         // Fractional seconds left-justify
         {
-            currentNumberFormat->setMinimumIntegerDigits((count > 3) ? 3 : count);
-            currentNumberFormat->setMaximumIntegerDigits(maxIntCount);
+            int32_t minDigits = (count > 3) ? 3 : count;
             if (count == 1) {
                 value /= 100;
             } else if (count == 2) {
                 value /= 10;
             }
-            FieldPosition p(FieldPosition::DONT_CARE);
-            currentNumberFormat->format(value, appendTo, p);
+            zeroPaddingNumber(currentNumberFormat, appendTo, value, minDigits, maxIntCount);
             if (count > 3) {
-                currentNumberFormat->setMinimumIntegerDigits(count - 3);
-                currentNumberFormat->format((int32_t)0, appendTo, p);
+                zeroPaddingNumber(currentNumberFormat, appendTo, 0, count - 3, maxIntCount);
             }
         }
         break;
@@ -1763,104 +1863,105 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
             UnicodeString zoneString(zsbuf, 0, UPRV_LENGTHOF(zsbuf));
             const TimeZone& tz = cal.getTimeZone();
             UDate date = cal.getTime(status);
+            const TimeZoneFormat *tzfmt = tzFormat(status);
             if (U_SUCCESS(status)) {
                 if (patternCharIndex == UDAT_TIMEZONE_FIELD) {
                     if (count < 4) {
                         // "z", "zz", "zzz"
-                        tzFormat()->format(UTZFMT_STYLE_SPECIFIC_SHORT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_SPECIFIC_SHORT, tz, date, zoneString);
                         capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneShort;
                     } else {
                         // "zzzz" or longer
-                        tzFormat()->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, date, zoneString);
                         capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneLong;
                     }
                 }
                 else if (patternCharIndex == UDAT_TIMEZONE_RFC_FIELD) {
                     if (count < 4) {
                         // "Z"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString);
                     } else if (count == 5) {
                         // "ZZZZZ"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString);
                     } else {
                         // "ZZ", "ZZZ", "ZZZZ"
-                        tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString);
                     }
                 }
                 else if (patternCharIndex == UDAT_TIMEZONE_GENERIC_FIELD) {
                     if (count == 1) {
                         // "v"
-                        tzFormat()->format(UTZFMT_STYLE_GENERIC_SHORT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_GENERIC_SHORT, tz, date, zoneString);
                         capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneShort;
                     } else if (count == 4) {
                         // "vvvv"
-                        tzFormat()->format(UTZFMT_STYLE_GENERIC_LONG, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_GENERIC_LONG, tz, date, zoneString);
                         capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneLong;
                     }
                 }
                 else if (patternCharIndex == UDAT_TIMEZONE_SPECIAL_FIELD) {
                     if (count == 1) {
                         // "V"
-                        tzFormat()->format(UTZFMT_STYLE_ZONE_ID_SHORT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ZONE_ID_SHORT, tz, date, zoneString);
                     } else if (count == 2) {
                         // "VV"
-                        tzFormat()->format(UTZFMT_STYLE_ZONE_ID, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ZONE_ID, tz, date, zoneString);
                     } else if (count == 3) {
                         // "VVV"
-                        tzFormat()->format(UTZFMT_STYLE_EXEMPLAR_LOCATION, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_EXEMPLAR_LOCATION, tz, date, zoneString);
                     } else if (count == 4) {
                         // "VVVV"
-                        tzFormat()->format(UTZFMT_STYLE_GENERIC_LOCATION, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_GENERIC_LOCATION, tz, date, zoneString);
                         capContextUsageType = DateFormatSymbols::kCapContextUsageZoneLong;
                     }
                 }
                 else if (patternCharIndex == UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD) {
                     if (count == 1) {
                         // "O"
-                        tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT_SHORT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_LOCALIZED_GMT_SHORT, tz, date, zoneString);
                     } else if (count == 4) {
                         // "OOOO"
-                        tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString);
                     }
                 }
                 else if (patternCharIndex == UDAT_TIMEZONE_ISO_FIELD) {
                     if (count == 1) {
                         // "X"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_SHORT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_BASIC_SHORT, tz, date, zoneString);
                     } else if (count == 2) {
                         // "XX"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FIXED, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_BASIC_FIXED, tz, date, zoneString);
                     } else if (count == 3) {
                         // "XXX"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FIXED, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_FIXED, tz, date, zoneString);
                     } else if (count == 4) {
                         // "XXXX"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FULL, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_BASIC_FULL, tz, date, zoneString);
                     } else if (count == 5) {
                         // "XXXXX"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString);
                     }
                 }
                 else if (patternCharIndex == UDAT_TIMEZONE_ISO_LOCAL_FIELD) {
                     if (count == 1) {
                         // "x"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT, tz, date, zoneString);
                     } else if (count == 2) {
                         // "xx"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED, tz, date, zoneString);
                     } else if (count == 3) {
                         // "xxx"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED, tz, date, zoneString);
                     } else if (count == 4) {
                         // "xxxx"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString);
                     } else if (count == 5) {
                         // "xxxxx"
-                        tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL, tz, date, zoneString);
+                        tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL, tz, date, zoneString);
                     }
                 }
                 else {
-                    U_ASSERT(FALSE);
+                    UPRV_UNREACHABLE;
                 }
             }
             appendTo += zoneString;
@@ -1906,14 +2007,14 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
             // Stealing am/pm value to use as our array index.
             // It works out: am/midnight are both 0, pm/noon are both 1,
             // 12 am is 12 midnight, and 12 pm is 12 noon.
-            int32_t value = cal.get(UCAL_AM_PM, status);
+            int32_t val = cal.get(UCAL_AM_PM, status);
 
             if (count <= 3) {
-                toAppend = &fSymbols->fAbbreviatedDayPeriods[value];
+                toAppend = &fSymbols->fAbbreviatedDayPeriods[val];
             } else if (count == 4 || count > 5) {
-                toAppend = &fSymbols->fWideDayPeriods[value];
+                toAppend = &fSymbols->fWideDayPeriods[val];
             } else { // count == 5
-                toAppend = &fSymbols->fNarrowDayPeriods[value];
+                toAppend = &fSymbols->fNarrowDayPeriods[val];
             }
         }
 
@@ -1923,7 +2024,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
         if (toAppend == NULL || toAppend->isBogus()) {
             // Reformat with identical arguments except ch, now changed to 'a'.
             subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum,
-                      handler, cal, mutableNFs, status);
+                      handler, cal, status);
         } else {
             appendTo += *toAppend;
         }
@@ -1944,7 +2045,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
             // Data doesn't exist for the locale we're looking for.
             // Falling back to am/pm.
             subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum,
-                      handler, cal, mutableNFs, status);
+                      handler, cal, status);
             break;
         }
 
@@ -2015,7 +2116,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
             periodType == DayPeriodRules::DAYPERIOD_PM ||
             toAppend->isBogus()) {
             subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum,
-                      handler, cal, mutableNFs, status);
+                      handler, cal, status);
         }
         else {
             appendTo += *toAppend;
@@ -2032,7 +2133,8 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
     }
 #if !UCONFIG_NO_BREAK_ITERATION
     // if first field, check to see whether we need to and are able to titlecase it
-    if (fieldNum == 0 && u_islower(appendTo.char32At(beginOffset)) && fCapitalizationBrkIter != NULL) {
+    if (fieldNum == 0 && fCapitalizationBrkIter != NULL && appendTo.length() > beginOffset &&
+            u_islower(appendTo.char32At(beginOffset))) {
         UBool titlecase = FALSE;
         switch (capitalizationContext) {
             case UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE:
@@ -2049,9 +2151,11 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
                 break;
         }
         if (titlecase) {
+            BreakIterator* const mutableCapitalizationBrkIter = fCapitalizationBrkIter->clone();
             UnicodeString firstField(appendTo, beginOffset);
-            firstField.toTitle(fCapitalizationBrkIter, fLocale, U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT);
+            firstField.toTitle(mutableCapitalizationBrkIter, fLocale, U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT);
             appendTo.replaceBetween(beginOffset, appendTo.length(), firstField);
+            delete mutableCapitalizationBrkIter;
         }
     }
 #endif
@@ -2063,6 +2167,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo,
 
 void SimpleDateFormat::adoptNumberFormat(NumberFormat *formatToAdopt) {
     fixNumberFormatForDates(*formatToAdopt);
+    freeFastNumberFormatters(); // deletes refs to fNumberFormat's symbols
     delete fNumberFormat;
     fNumberFormat = formatToAdopt;
 
@@ -2072,6 +2177,10 @@ void SimpleDateFormat::adoptNumberFormat(NumberFormat *formatToAdopt) {
         freeSharedNumberFormatters(fSharedNumberFormatters);
         fSharedNumberFormatters = NULL;
     }
+
+    // Also re-compute the fast formatters.
+    UErrorCode localStatus = U_ZERO_ERROR;
+    initFastNumberFormatters(localStatus);
 }
 
 void SimpleDateFormat::adoptNumberFormat(const UnicodeString& fields, NumberFormat *formatToAdopt, UErrorCode &status){
@@ -2123,16 +2232,58 @@ SimpleDateFormat::getNumberFormatForField(UChar field) const {
 //----------------------------------------------------------------------
 void
 SimpleDateFormat::zeroPaddingNumber(
-        NumberFormat *currentNumberFormat,
+        const NumberFormat *currentNumberFormat,
         UnicodeString &appendTo,
         int32_t value, int32_t minDigits, int32_t maxDigits) const
 {
-    if (currentNumberFormat!=NULL) {
+    const number::LocalizedNumberFormatter* fastFormatter = nullptr;
+    // NOTE: This uses the heuristic that these five min/max int settings account for the vast majority
+    // of SimpleDateFormat number formatting cases at the time of writing (ICU 62).
+    if (currentNumberFormat == fNumberFormat) {
+        if (maxDigits == 10) {
+            if (minDigits == 1) {
+                fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_1x10];
+            } else if (minDigits == 2) {
+                fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_2x10];
+            } else if (minDigits == 3) {
+                fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_3x10];
+            } else if (minDigits == 4) {
+                fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_4x10];
+            }
+        } else if (maxDigits == 2) {
+            if (minDigits == 2) {
+                fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_2x2];
+            }
+        }
+    }
+    if (fastFormatter != nullptr) {
+        // Can use fast path
+        number::impl::UFormattedNumberData result;
+        result.quantity.setToInt(value);
+        UErrorCode localStatus = U_ZERO_ERROR;
+        fastFormatter->formatImpl(&result, localStatus);
+        if (U_FAILURE(localStatus)) {
+            return;
+        }
+        appendTo.append(result.getStringRef().toTempUnicodeString());
+        return;
+    }
+
+    // Check for RBNF (no clone necessary)
+    auto* rbnf = dynamic_cast<const RuleBasedNumberFormat*>(currentNumberFormat);
+    if (rbnf != nullptr) {
         FieldPosition pos(FieldPosition::DONT_CARE);
+        rbnf->format(value, appendTo, pos);  // 3rd arg is there to speed up processing
+        return;
+    }
 
-        currentNumberFormat->setMinimumIntegerDigits(minDigits);
-        currentNumberFormat->setMaximumIntegerDigits(maxDigits);
-        currentNumberFormat->format(value, appendTo, pos);  // 3rd arg is there to speed up processing
+    // Fall back to slow path (clone and mutate the NumberFormat)
+    if (currentNumberFormat != nullptr) {
+        FieldPosition pos(FieldPosition::DONT_CARE);
+        LocalPointer<NumberFormat> nf(currentNumberFormat->clone());
+        nf->setMinimumIntegerDigits(minDigits);
+        nf->setMaximumIntegerDigits(maxDigits);
+        nf->format(value, appendTo, pos);  // 3rd arg is there to speed up processing
     }
 }
 
@@ -2199,7 +2350,6 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition&
     int32_t saveHebrewMonth = -1;
     int32_t count = 0;
     UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
-    SimpleDateFormatMutableNFs mutableNFs;
 
     // For parsing abutting numeric fields. 'abutPat' is the
     // offset into 'pattern' of the first of 2 or more abutting
@@ -2293,7 +2443,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition&
                 }
 
                 pos = subParse(text, pos, ch, count,
-                               TRUE, FALSE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, mutableNFs);
+                               TRUE, FALSE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType);
 
                 // If the parse fails anywhere in the run, back up to the
                 // start of the run and retry.
@@ -2308,7 +2458,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition&
             // fields.
             else if (ch != 0x6C) { // pattern char 'l' (SMALL LETTER L) just gets ignored
                 int32_t s = subParse(text, pos, ch, count,
-                               FALSE, TRUE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, mutableNFs, &dayPeriodInt);
+                               FALSE, TRUE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, &dayPeriodInt);
 
                 if (s == -pos-1) {
                     // era not present, in special cases allow this to continue
@@ -2317,10 +2467,10 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition&
 
                     if (i+1 < fPattern.length()) {
                         // move to next pattern character
-                        UChar ch = fPattern.charAt(i+1);
+                        UChar c = fPattern.charAt(i+1);
 
                         // check for whitespace
-                        if (PatternProps::isWhiteSpace(ch)) {
+                        if (PatternProps::isWhiteSpace(c)) {
                             i++;
                             // Advance over run in pattern
                             while ((i+1)<fPattern.length() &&
@@ -2955,7 +3105,7 @@ SimpleDateFormat::set2DigitYearStart(UDate d, UErrorCode& status)
  */
 int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UChar ch, int32_t count,
                            UBool obeyCount, UBool allowNegative, UBool ambiguousYear[], int32_t& saveHebrewMonth, Calendar& cal,
-                           int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, SimpleDateFormatMutableNFs &mutableNFs,
+                           int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType,
                            int32_t *dayPeriod) const
 {
     Formattable number;
@@ -2965,7 +3115,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
     UErrorCode status = U_ZERO_ERROR;
     ParsePosition pos(0);
     UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(ch);
-    NumberFormat *currentNumberFormat;
+    const NumberFormat *currentNumberFormat;
     UnicodeString temp;
     int32_t tzParseOptions = (isLenient())? UTZFMT_PARSE_OPTION_ALL_STYLES: UTZFMT_PARSE_OPTION_NONE;
     UBool gotNumber = FALSE;
@@ -2978,7 +3128,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
         return -start;
     }
 
-    currentNumberFormat = mutableNFs.get(getNumberFormatByIndex(patternCharIndex));
+    currentNumberFormat = getNumberFormatByIndex(patternCharIndex);
     if (currentNumberFormat == NULL) {
         return -start;
     }
@@ -3233,8 +3383,8 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
             if (!strcmp(cal.getType(),"hebrew")) {
                 HebrewCalendar *hc = (HebrewCalendar*)&cal;
                 if (cal.isSet(UCAL_YEAR)) {
-                   UErrorCode status = U_ZERO_ERROR;
-                   if (!hc->isLeapYear(hc->get(UCAL_YEAR,status)) && value >= 6) {
+                   UErrorCode monthStatus = U_ZERO_ERROR;
+                   if (!hc->isLeapYear(hc->get(UCAL_YEAR, monthStatus)) && value >= 6) {
                        cal.set(UCAL_MONTH, value);
                    } else {
                        cal.set(UCAL_MONTH, value - 1);
@@ -3496,31 +3646,41 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
     case UDAT_TIMEZONE_FIELD: // 'z'
         {
             UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_SPECIFIC_SHORT : UTZFMT_STYLE_SPECIFIC_LONG;
-            TimeZone *tz  = tzFormat()->parse(style, text, pos, tzParseOptions, tzTimeType);
-            if (tz != NULL) {
-                cal.adoptTimeZone(tz);
-                return pos.getIndex();
+            const TimeZoneFormat *tzfmt = tzFormat(status);
+            if (U_SUCCESS(status)) {
+                TimeZone *tz = tzfmt->parse(style, text, pos, tzParseOptions, tzTimeType);
+                if (tz != NULL) {
+                    cal.adoptTimeZone(tz);
+                    return pos.getIndex();
+                }
             }
-        }
+            return -start;
+    }
         break;
     case UDAT_TIMEZONE_RFC_FIELD: // 'Z'
         {
             UTimeZoneFormatStyle style = (count < 4) ?
                 UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL : ((count == 5) ? UTZFMT_STYLE_ISO_EXTENDED_FULL: UTZFMT_STYLE_LOCALIZED_GMT);
-            TimeZone *tz  = tzFormat()->parse(style, text, pos, tzTimeType);
-            if (tz != NULL) {
-                cal.adoptTimeZone(tz);
-                return pos.getIndex();
+            const TimeZoneFormat *tzfmt = tzFormat(status);
+            if (U_SUCCESS(status)) {
+                TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType);
+                if (tz != NULL) {
+                    cal.adoptTimeZone(tz);
+                    return pos.getIndex();
+                }
             }
             return -start;
         }
     case UDAT_TIMEZONE_GENERIC_FIELD: // 'v'
         {
             UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_GENERIC_SHORT : UTZFMT_STYLE_GENERIC_LONG;
-            TimeZone *tz  = tzFormat()->parse(style, text, pos, tzParseOptions, tzTimeType);
-            if (tz != NULL) {
-                cal.adoptTimeZone(tz);
-                return pos.getIndex();
+            const TimeZoneFormat *tzfmt = tzFormat(status);
+            if (U_SUCCESS(status)) {
+                TimeZone *tz = tzfmt->parse(style, text, pos, tzParseOptions, tzTimeType);
+                if (tz != NULL) {
+                    cal.adoptTimeZone(tz);
+                    return pos.getIndex();
+                }
             }
             return -start;
         }
@@ -3541,20 +3701,26 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
                 style = UTZFMT_STYLE_GENERIC_LOCATION;
                 break;
             }
-            TimeZone *tz  = tzFormat()->parse(style, text, pos, tzTimeType);
-            if (tz != NULL) {
-                cal.adoptTimeZone(tz);
-                return pos.getIndex();
+            const TimeZoneFormat *tzfmt = tzFormat(status);
+            if (U_SUCCESS(status)) {
+                TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType);
+                if (tz != NULL) {
+                    cal.adoptTimeZone(tz);
+                    return pos.getIndex();
+                }
             }
             return -start;
         }
     case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: // 'O'
         {
             UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_LOCALIZED_GMT_SHORT : UTZFMT_STYLE_LOCALIZED_GMT;
-            TimeZone *tz  = tzFormat()->parse(style, text, pos, tzTimeType);
-            if (tz != NULL) {
-                cal.adoptTimeZone(tz);
-                return pos.getIndex();
+            const TimeZoneFormat *tzfmt = tzFormat(status);
+            if (U_SUCCESS(status)) {
+                TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType);
+                if (tz != NULL) {
+                    cal.adoptTimeZone(tz);
+                    return pos.getIndex();
+                }
             }
             return -start;
         }
@@ -3578,10 +3744,13 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
                 style = UTZFMT_STYLE_ISO_EXTENDED_FULL;
                 break;
             }
-            TimeZone *tz  = tzFormat()->parse(style, text, pos, tzTimeType);
-            if (tz != NULL) {
-                cal.adoptTimeZone(tz);
-                return pos.getIndex();
+            const TimeZoneFormat *tzfmt = tzFormat(status);
+            if (U_SUCCESS(status)) {
+                TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType);
+                if (tz != NULL) {
+                    cal.adoptTimeZone(tz);
+                    return pos.getIndex();
+                }
             }
             return -start;
         }
@@ -3605,10 +3774,13 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
                 style = UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL;
                 break;
             }
-            TimeZone *tz  = tzFormat()->parse(style, text, pos, tzTimeType);
-            if (tz != NULL) {
-                cal.adoptTimeZone(tz);
-                return pos.getIndex();
+            const TimeZoneFormat *tzfmt = tzFormat(status);
+            if (U_SUCCESS(status)) {
+                TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType);
+                if (tz != NULL) {
+                    cal.adoptTimeZone(tz);
+                    return pos.getIndex();
+                }
             }
             return -start;
         }
@@ -3620,21 +3792,21 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
             static const UChar alt_sep = DateFormatSymbols::ALTERNATE_TIME_SEPARATOR;
 
             // Try matching a time separator.
-            int32_t count = 1;
+            int32_t count_sep = 1;
             UnicodeString data[3];
             fSymbols->getTimeSeparatorString(data[0]);
 
             // Add the default, if different from the locale.
             if (data[0].compare(&def_sep, 1) != 0) {
-                data[count++].setTo(def_sep);
+                data[count_sep++].setTo(def_sep);
             }
 
             // If lenient, add also the alternate, if different from the locale.
             if (isLenient() && data[0].compare(&alt_sep, 1) != 0) {
-                data[count++].setTo(alt_sep);
+                data[count_sep++].setTo(alt_sep);
             }
 
-            return matchString(text, start, UCAL_FIELD_COUNT /* => nothing to set */, data, count, NULL, cal);
+            return matchString(text, start, UCAL_FIELD_COUNT /* => nothing to set */, data, count_sep, NULL, cal);
         }
 
     case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
@@ -3642,7 +3814,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
         U_ASSERT(dayPeriod != NULL);
         int32_t ampmStart = subParse(text, start, 0x61, count,
                            obeyCount, allowNegative, ambiguousYear, saveHebrewMonth, cal,
-                           patLoc, numericLeapMonthFormatter, tzTimeType, mutableNFs);
+                           patLoc, numericLeapMonthFormatter, tzTimeType);
 
         if (ampmStart > 0) {
             return ampmStart;
@@ -3723,7 +3895,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
     }
     parseInt(*src, number, pos, allowNegative,currentNumberFormat);
     if (pos.getIndex() != parseStart) {
-        int32_t value = number.getLong();
+        int32_t val = number.getLong();
 
         // Don't need suffix processing here (as in number processing at the beginning of the function);
         // the new fields being handled as numeric values (month, weekdays, quarters) should not have suffixes.
@@ -3731,7 +3903,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
         // Check the range of the value
         if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) {
             int32_t bias = gFieldRangeBias[patternCharIndex];
-            if (bias >= 0 && (value > cal.getMaximum(field) + bias || value < cal.getMinimum(field) + bias)) {
+            if (bias >= 0 && (val > cal.getMaximum(field) + bias || val < cal.getMinimum(field) + bias)) {
                 return -start;
             }
         } else {
@@ -3750,35 +3922,35 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC
             if (!strcmp(cal.getType(),"hebrew")) {
                 HebrewCalendar *hc = (HebrewCalendar*)&cal;
                 if (cal.isSet(UCAL_YEAR)) {
-                   UErrorCode status = U_ZERO_ERROR;
-                   if (!hc->isLeapYear(hc->get(UCAL_YEAR,status)) && value >= 6) {
-                       cal.set(UCAL_MONTH, value);
+                   UErrorCode monthStatus = U_ZERO_ERROR;
+                   if (!hc->isLeapYear(hc->get(UCAL_YEAR, monthStatus)) && val >= 6) {
+                       cal.set(UCAL_MONTH, val);
                    } else {
-                       cal.set(UCAL_MONTH, value - 1);
+                       cal.set(UCAL_MONTH, val - 1);
                    }
                 } else {
-                    saveHebrewMonth = value;
+                    saveHebrewMonth = val;
                 }
             } else {
-                cal.set(UCAL_MONTH, value - 1);
+                cal.set(UCAL_MONTH, val - 1);
             }
             break;
         case UDAT_STANDALONE_MONTH_FIELD:
-            cal.set(UCAL_MONTH, value - 1);
+            cal.set(UCAL_MONTH, val - 1);
             break;
         case UDAT_DOW_LOCAL_FIELD:
         case UDAT_STANDALONE_DAY_FIELD:
-            cal.set(UCAL_DOW_LOCAL, value);
+            cal.set(UCAL_DOW_LOCAL, val);
             break;
         case UDAT_QUARTER_FIELD:
         case UDAT_STANDALONE_QUARTER_FIELD:
-             cal.set(UCAL_MONTH, (value - 1) * 3);
+             cal.set(UCAL_MONTH, (val - 1) * 3);
              break;
         case UDAT_RELATED_YEAR_FIELD:
-            cal.setRelatedYear(value);
+            cal.setRelatedYear(val);
             break;
         default:
-            cal.set(field, value);
+            cal.set(field, val);
             break;
         }
         return pos.getIndex();
@@ -3794,7 +3966,7 @@ void SimpleDateFormat::parseInt(const UnicodeString& text,
                                 Formattable& number,
                                 ParsePosition& pos,
                                 UBool allowNegative,
-                                NumberFormat *fmt) const {
+                                const NumberFormat *fmt) const {
     parseInt(text, number, -1, pos, allowNegative,fmt);
 }
 
@@ -3806,18 +3978,21 @@ void SimpleDateFormat::parseInt(const UnicodeString& text,
                                 int32_t maxDigits,
                                 ParsePosition& pos,
                                 UBool allowNegative,
-                                NumberFormat *fmt) const {
+                                const NumberFormat *fmt) const {
     UnicodeString oldPrefix;
-    DecimalFormat* df = NULL;
-    if (!allowNegative && (df = dynamic_cast<DecimalFormat*>(fmt)) != NULL) {
-        df->getNegativePrefix(oldPrefix);
+    auto* fmtAsDF = dynamic_cast<const DecimalFormat*>(fmt);
+    LocalPointer<DecimalFormat> df;
+    if (!allowNegative && fmtAsDF != nullptr) {
+        df.adoptInstead(fmtAsDF->clone());
+        if (df.isNull()) {
+            // Memory allocation error
+            return;
+        }
         df->setNegativePrefix(UnicodeString(TRUE, SUPPRESS_NEGATIVE_PREFIX, -1));
+        fmt = df.getAlias();
     }
     int32_t oldPos = pos.getIndex();
     fmt->parse(text, number, pos);
-    if (df != NULL) {
-        df->setNegativePrefix(oldPrefix);
-    }
 
     if (maxDigits > 0) {
         // adjust the result to fit into
@@ -3923,6 +4098,42 @@ SimpleDateFormat::applyPattern(const UnicodeString& pattern)
 {
     fPattern = pattern;
     parsePattern();
+
+    // Hack to update use of Gannen year numbering for ja@calendar=japanese -
+    // use only if format is non-numeric (includes 年) and no other fDateOverride.
+    if (fCalendar != nullptr && uprv_strcmp(fCalendar->getType(),"japanese") == 0 &&
+            uprv_strcmp(fLocale.getLanguage(),"ja") == 0) {
+        if (fDateOverride==UnicodeString(u"y=jpanyear") && !fHasHanYearChar) {
+            // Gannen numbering is set but new pattern should not use it, unset;
+            // use procedure from adoptNumberFormat to clear overrides
+            if (fSharedNumberFormatters) {
+                freeSharedNumberFormatters(fSharedNumberFormatters);
+                fSharedNumberFormatters = NULL;
+            }
+            fDateOverride.setToBogus(); // record status
+        } else if (fDateOverride.isBogus() && fHasHanYearChar) {
+            // No current override (=> no Gannen numbering) but new pattern needs it;
+            // use procedures from initNUmberFormatters / adoptNumberFormat
+            umtx_lock(&LOCK);
+            if (fSharedNumberFormatters == NULL) {
+                fSharedNumberFormatters = allocSharedNumberFormatters();
+            }
+            umtx_unlock(&LOCK);
+            if (fSharedNumberFormatters != NULL) {
+                Locale ovrLoc(fLocale.getLanguage(),fLocale.getCountry(),fLocale.getVariant(),"numbers=jpanyear");
+                UErrorCode status = U_ZERO_ERROR;
+                const SharedNumberFormat *snf = createSharedNumberFormat(ovrLoc, status);
+                if (U_SUCCESS(status)) {
+                    // Now that we have an appropriate number formatter, fill in the
+                    // appropriate slot in the number formatters table.
+                    UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(u'y');
+                    SharedObject::copyPtr(snf, fSharedNumberFormatters[patternCharIndex]);
+                    snf->deleteIfZeroRefCount();
+                    fDateOverride.setTo(u"y=jpanyear", -1); // record status
+                }
+            }
+        }
+    }
 }
 
 //----------------------------------------------------------------------
@@ -3964,7 +4175,13 @@ SimpleDateFormat::setDateFormatSymbols(const DateFormatSymbols& newFormatSymbols
 //----------------------------------------------------------------------
 const TimeZoneFormat*
 SimpleDateFormat::getTimeZoneFormat(void) const {
-    return (const TimeZoneFormat*)tzFormat();
+    // TimeZoneFormat initialization might fail when out of memory.
+    // If we always initialize TimeZoneFormat instance, we can return
+    // such status there. For now, this implementation lazily instantiates
+    // a TimeZoneFormat for performance optimization reasons, but cannot
+    // propagate such error (probably just out of memory case) to the caller.
+    UErrorCode status = U_ZERO_ERROR;
+    return (const TimeZoneFormat*)tzFormat(status);
 }
 
 //----------------------------------------------------------------------
@@ -3995,6 +4212,7 @@ void SimpleDateFormat::adoptCalendar(Calendar* calendarToAdopt)
     calLocale.setKeywordValue("calendar", calendarToAdopt->getType(), status);
     newSymbols = DateFormatSymbols::createForLocale(calLocale, status);
     if (U_FAILURE(status)) {
+        delete calendarToAdopt;
         return;
     }
   }
@@ -4020,7 +4238,7 @@ SimpleDateFormat::setContext(UDisplayContext value, UErrorCode& status)
     if (U_SUCCESS(status)) {
         if ( fCapitalizationBrkIter == NULL && (value==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
                 value==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU || value==UDISPCTX_CAPITALIZATION_FOR_STANDALONE) ) {
-            UErrorCode status = U_ZERO_ERROR;
+            status = U_ZERO_ERROR;
             fCapitalizationBrkIter = BreakIterator::createSentenceInstance(fLocale, status);
             if (U_FAILURE(status)) {
                 delete fCapitalizationBrkIter;
@@ -4235,12 +4453,11 @@ SimpleDateFormat::skipUWhiteSpace(const UnicodeString& text, int32_t pos) const
 
 // Lazy TimeZoneFormat instantiation, semantically const.
 TimeZoneFormat *
-SimpleDateFormat::tzFormat() const {
+SimpleDateFormat::tzFormat(UErrorCode &status) const {
     if (fTimeZoneFormat == NULL) {
         umtx_lock(&LOCK);
         {
             if (fTimeZoneFormat == NULL) {
-                UErrorCode status = U_ZERO_ERROR;
                 TimeZoneFormat *tzfmt = TimeZoneFormat::createInstance(fLocale, status);
                 if (U_FAILURE(status)) {
                     return NULL;
@@ -4257,6 +4474,7 @@ SimpleDateFormat::tzFormat() const {
 void SimpleDateFormat::parsePattern() {
     fHasMinute = FALSE;
     fHasSecond = FALSE;
+    fHasHanYearChar = FALSE;
 
     int len = fPattern.length();
     UBool inQuote = FALSE;
@@ -4265,6 +4483,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;