]> granicus.if.org Git - icu/commitdiff
ICU-21353 Implement DateTimePatternGenerator use of correct datetime pattern;
authorPeter Edberg <pedberg@unicode.org>
Tue, 11 Jan 2022 04:04:58 +0000 (20:04 -0800)
committerPeter Edberg <42151464+pedberg-icu@users.noreply.github.com>
Fri, 14 Jan 2022 03:16:37 +0000 (19:16 -0800)
includes new getter/setter API per TC discussion.

14 files changed:
icu4c/source/i18n/dtptngen.cpp
icu4c/source/i18n/udatpg.cpp
icu4c/source/i18n/unicode/dtptngen.h
icu4c/source/i18n/unicode/udatpg.h
icu4c/source/test/cintltst/cdateintervalformattest.c
icu4c/source/test/cintltst/udatpg_test.c
icu4c/source/test/intltest/dtfmttst.cpp
icu4c/source/test/intltest/dtptngts.cpp
icu4c/source/test/intltest/dtptngts.h
icu4c/source/test/intltest/tmsgfmt.cpp
icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateTimeGeneratorTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TestMessageFormat.java

index 987d83663acd94622e9a33bbace12522f2978365..10d4a3c8879a549f6806b8977c43ee77a05e1e5f 100644 (file)
@@ -393,10 +393,13 @@ DateTimePatternGenerator::operator=(const DateTimePatternGenerator& other) {
     *fp = *(other.fp);
     dtMatcher->copyFrom(other.dtMatcher->skeleton);
     *distanceInfo = *(other.distanceInfo);
-    dateTimeFormat = other.dateTimeFormat;
+    for (int32_t style = UDAT_FULL; style <= UDAT_SHORT; style++) {
+        dateTimeFormat[style] = other.dateTimeFormat[style];
+    }
     decimal = other.decimal;
-    // NUL-terminate for the C API.
-    dateTimeFormat.getTerminatedBuffer();
+    for (int32_t style = UDAT_FULL; style <= UDAT_SHORT; style++) {
+        dateTimeFormat[style].getTerminatedBuffer(); // NUL-terminate for the C API.
+    }
     decimal.getTerminatedBuffer();
     delete skipMatcher;
     if ( other.skipMatcher == nullptr ) {
@@ -430,7 +433,12 @@ DateTimePatternGenerator::operator==(const DateTimePatternGenerator& other) cons
         return true;
     }
     if ((pLocale==other.pLocale) && (patternMap->equals(*other.patternMap)) &&
-        (dateTimeFormat==other.dateTimeFormat) && (decimal==other.decimal)) {
+        (decimal==other.decimal)) {
+        for (int32_t style = UDAT_FULL; style <= UDAT_SHORT; style++) {
+            if (dateTimeFormat[style] != other.dateTimeFormat[style]) {
+                return false;
+            }
+        }
         for ( int32_t i=0 ; i<UDATPG_FIELD_COUNT; ++i ) {
             if (appendItemFormats[i] != other.appendItemFormats[i]) {
                 return false;
@@ -1199,7 +1207,21 @@ DateTimePatternGenerator::getBestPattern(const UnicodeString& patternForm, UDate
     }
     resultPattern.remove();
     status = U_ZERO_ERROR;
-    dtFormat=getDateTimeFormat();
+    // determine which dateTimeFormat to use
+    PtnSkeleton* reqSkeleton = dtMatcher->getSkeletonPtr();
+    UDateFormatStyle style = UDAT_SHORT;
+    int32_t monthFieldLen = reqSkeleton->baseOriginal.getFieldLength(UDATPG_MONTH_FIELD);
+    if (monthFieldLen == 4) {
+        if (reqSkeleton->baseOriginal.getFieldLength(UDATPG_WEEKDAY_FIELD) > 0) {
+            style = UDAT_FULL;
+        } else {
+            style = UDAT_LONG;
+        }
+    } else if (monthFieldLen == 3) {
+        style = UDAT_MEDIUM;
+    }
+    // and now use it to compose date and time
+    dtFormat=getDateTimeFormat(style, status);
     SimpleFormatter(dtFormat, 2, 2, status).format(timePattern, datePattern, resultPattern, status);
     return resultPattern;
 }
@@ -1335,14 +1357,45 @@ DateTimePatternGenerator::addCanonicalItems(UErrorCode& status) {
 
 void
 DateTimePatternGenerator::setDateTimeFormat(const UnicodeString& dtFormat) {
-    dateTimeFormat = dtFormat;
-    // NUL-terminate for the C API.
-    dateTimeFormat.getTerminatedBuffer();
+    UErrorCode status = U_ZERO_ERROR;
+    for (int32_t style = UDAT_FULL; style <= UDAT_SHORT; style++) {
+        setDateTimeFormat((UDateFormatStyle)style, dtFormat, status);
+    }
 }
 
 const UnicodeString&
 DateTimePatternGenerator::getDateTimeFormat() const {
-    return dateTimeFormat;
+    UErrorCode status = U_ZERO_ERROR;
+    return getDateTimeFormat(UDAT_MEDIUM, status);
+}
+
+void
+DateTimePatternGenerator::setDateTimeFormat(UDateFormatStyle style, const UnicodeString& dtFormat, UErrorCode& status) {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    if (style < UDAT_FULL || style > UDAT_SHORT) {
+        status = U_ILLEGAL_ARGUMENT_ERROR;
+        return;
+    }
+    dateTimeFormat[style] = dtFormat;
+    // Note for the following: getTerminatedBuffer() can re-allocate the UnicodeString
+    // buffer so we do this here before clients request a const ref to the UnicodeString
+    // or its buffer.
+    dateTimeFormat[style].getTerminatedBuffer(); // NUL-terminate for the C API.
+}
+
+const UnicodeString&
+DateTimePatternGenerator::getDateTimeFormat(UDateFormatStyle style, UErrorCode& status) const {
+    static const UnicodeString emptyString = UNICODE_STRING_SIMPLE("");
+    if (U_FAILURE(status)) {
+        return emptyString;
+    }
+    if (style < UDAT_FULL || style > UDAT_SHORT) {
+        status = U_ILLEGAL_ARGUMENT_ERROR;
+        return emptyString;
+    }
+    return dateTimeFormat[style];
 }
 
 void
@@ -1378,13 +1431,15 @@ DateTimePatternGenerator::setDateTimeFromCalendar(const Locale& locale, UErrorCo
     }
     if (U_FAILURE(status)) { return; }
 
-    if (ures_getSize(dateTimePatterns.getAlias()) <= DateFormat::kDateTime)
+    if (ures_getSize(dateTimePatterns.getAlias()) <= DateFormat::kDateTimeOffset + DateFormat::kShort)
     {
         status = U_INVALID_FORMAT_ERROR;
         return;
     }
-    resStr = ures_getStringByIndex(dateTimePatterns.getAlias(), (int32_t)DateFormat::kDateTime, &resStrLen, &status);
-    setDateTimeFormat(UnicodeString(TRUE, resStr, resStrLen));
+    for (int32_t style = UDAT_FULL; style <= UDAT_SHORT; style++) {
+        resStr = ures_getStringByIndex(dateTimePatterns.getAlias(), (int32_t)DateFormat::kDateTimeOffset + style, &resStrLen, &status);
+        setDateTimeFormat((UDateFormatStyle)style, UnicodeString(TRUE, resStr, resStrLen), status);
+    }
 }
 
 void
index 332636a93889f15c572dbe73cca58fca624209ed..9e61a12076803ad746e8635ab3778499c09cf870 100644 (file)
@@ -210,12 +210,47 @@ udatpg_setDateTimeFormat(const UDateTimePatternGenerator *dtpg,
 U_CAPI const UChar * U_EXPORT2
 udatpg_getDateTimeFormat(const UDateTimePatternGenerator *dtpg,
                          int32_t *pLength) {
-    const UnicodeString &result=((const DateTimePatternGenerator *)dtpg)->getDateTimeFormat();
-    if(pLength!=NULL) {
+    UErrorCode status = U_ZERO_ERROR;
+    return udatpg_getDateTimeFormatForStyle(dtpg, UDAT_MEDIUM, pLength, &status);
+}
+
+U_CAPI void U_EXPORT2
+udatpg_setDateTimeFormatForStyle(UDateTimePatternGenerator *udtpg,
+                        UDateFormatStyle style,
+                        const UChar *dateTimeFormat, int32_t length,
+                        UErrorCode *pErrorCode) {
+    if (U_FAILURE(*pErrorCode)) {
+        return;
+    } else if (dateTimeFormat==nullptr) {
+        *pErrorCode = U_ILLEGAL_ARGUMENT_ERROR;
+        return;
+    }
+    DateTimePatternGenerator *dtpg = reinterpret_cast<DateTimePatternGenerator *>(udtpg);
+    UnicodeString dtFormatString((UBool)(length<0), dateTimeFormat, length);
+    dtpg->setDateTimeFormat(style, dtFormatString, *pErrorCode);
+}
+
+U_CAPI const UChar* U_EXPORT2
+udatpg_getDateTimeFormatForStyle(const UDateTimePatternGenerator *udtpg,
+                        UDateFormatStyle style, int32_t *pLength,
+                        UErrorCode *pErrorCode) {
+    static const UChar emptyString[] = { (UChar)0 };
+    if (U_FAILURE(*pErrorCode)) {
+        if (pLength !=nullptr) {
+            *pLength = 0;
+        }
+        return emptyString;
+    }
+    const DateTimePatternGenerator *dtpg = reinterpret_cast<const DateTimePatternGenerator *>(udtpg);
+    const UnicodeString &result = dtpg->getDateTimeFormat(style, *pErrorCode);
+    if (pLength != nullptr) {
         *pLength=result.length();
     }
+    // Note: The UnicodeString for the dateTimeFormat string in the DateTimePatternGenerator
+    // was NUL-terminated what it was set, to avoid doing it here which could re-allocate
+    // the buffe and affect and cont references to the string or its buffer.
     return result.getBuffer();
-}
+ }
 
 U_CAPI void U_EXPORT2
 udatpg_setDecimal(UDateTimePatternGenerator *dtpg,
index 250a0e089fed6bb5fdf51d5b9d6d90606695bd4e..0b6390537be98ed18cdabeb0adaea25a58f36088 100644 (file)
@@ -311,6 +311,11 @@ public:
      * for those two skeletons, so the result is put together with this pattern,
      * resulting in "d-MMM h:mm".
      *
+     * There are four DateTimeFormats in a DateTimePatternGenerator object,
+     * corresponding to date styles UDAT_FULL..UDAT_SHORT. This method sets
+     * all of them to the specified pattern. To set them individually, see
+     * setDateTimeFormat(UDateFormatStyle style, ...).
+     *
      * @param dateTimeFormat
      *            message format pattern, here {1} will be replaced by the date
      *            pattern and {0} will be replaced by the time pattern.
@@ -320,11 +325,66 @@ public:
 
     /**
      * Getter corresponding to setDateTimeFormat.
+     *
+     * There are four DateTimeFormats in a DateTimePatternGenerator object,
+     * corresponding to date styles UDAT_FULL..UDAT_SHORT. This method gets
+     * the style for UDAT_MEDIUM (the default). To get them individually, see
+     * getDateTimeFormat(UDateFormatStyle style).
+     *
      * @return DateTimeFormat.
      * @stable ICU 3.8
      */
     const UnicodeString& getDateTimeFormat() const;
 
+#if !UCONFIG_NO_FORMATTING
+#ifndef U_HIDE_DRAFT_API
+    /**
+     * dateTimeFormats are message patterns used to compose combinations of date
+     * and time patterns. There are four length styles, corresponding to the
+     * inferred style of the date pattern; these are UDateFormatStyle values:
+     *  - UDAT_FULL (for date pattern with weekday and long month), else
+     *  - UDAT_LONG (for a date pattern with long month), else
+     *  - UDAT_MEDIUM (for a date pattern with abbreviated month), else
+     *  - UDAT_SHORT (for any other date pattern).
+     * For details on dateTimeFormats, see
+     * https://www.unicode.org/reports/tr35/tr35-dates.html#dateTimeFormats.
+     * The default pattern in the root locale for all styles is "{1} {0}".
+     *
+     * @param style
+     *              one of DateFormat.FULL..DateFormat.SHORT. Error if out of range.
+     * @param dateTimeFormat
+     *              the new dateTimeFormat to set for the the specified style
+     * @param status
+     *              in/out parameter; if no failure status is already set,
+     *              it will be set according to result of the function (e.g.
+     *              U_ILLEGAL_ARGUMENT_ERROR for style out of range).
+     * @draft ICU 71
+     */
+    void setDateTimeFormat(UDateFormatStyle style, const UnicodeString& dateTimeFormat,
+                            UErrorCode& status);
+
+    /**
+     * Getter corresponding to setDateTimeFormat.
+     *
+     * @param style
+     *              one of UDAT_FULL..UDAT_SHORT. Error if out of range.
+     * @param status
+     *              in/out parameter; if no failure status is already set,
+     *              it will be set according to result of the function (e.g.
+     *              U_ILLEGAL_ARGUMENT_ERROR for style out of range).
+     * @return
+     *              the current dateTimeFormat for the the specified style, or
+     *              empty string in case of error. The UnicodeString reference,
+     *              or the contents of the string, may no longer be valid if
+     *              setDateTimeFormat is called, or the DateTimePatternGenerator
+     *              object is deleted.
+     * @draft ICU 71
+     */
+    const UnicodeString& getDateTimeFormat(UDateFormatStyle style,
+                            UErrorCode& status) const;
+#endif /* U_HIDE_DRAFT_API */
+#endif /* #if !UCONFIG_NO_FORMATTING */
+
     /**
      * Return the best pattern matching the input skeleton. It is guaranteed to
      * have all of the fields in the skeleton.
@@ -556,7 +616,7 @@ private:
     UnicodeString appendItemFormats[UDATPG_FIELD_COUNT];
     // TODO(ticket:13619): [3] -> UDATPG_WIDTH_COUNT
     UnicodeString fieldDisplayNames[UDATPG_FIELD_COUNT][3];
-    UnicodeString dateTimeFormat;
+    UnicodeString dateTimeFormat[4];
     UnicodeString decimal;
     DateTimeMatcher *skipMatcher;
     Hashtable *fAvailableFormatKeyHash;
index efe4357bfeecd28d55231ee5a753f0c5ddd9e831..684a905e42602e6e3b539fe4fe4cb7f2cbf0284f 100644 (file)
@@ -492,6 +492,11 @@ udatpg_getFieldDisplayName(const UDateTimePatternGenerator *dtpg,
  * for those two skeletons, so the result is put together with this pattern,
  * resulting in "d-MMM h:mm".
  *
+ * There are four DateTimeFormats in a UDateTimePatternGenerator object,
+ * corresponding to date styles UDAT_FULL..UDAT_SHORT. This method sets
+ * all of them to the specified pattern. To set them individually, see
+ * udatpg_setDateTimeFormatForStyle.
+ *
  * @param dtpg a pointer to UDateTimePatternGenerator.
  * @param dtFormat
  *            message format pattern, here {1} will be replaced by the date
@@ -505,6 +510,12 @@ udatpg_setDateTimeFormat(const UDateTimePatternGenerator *dtpg,
 
 /**
  * Getter corresponding to setDateTimeFormat.
+ *
+ * There are four DateTimeFormats in a UDateTimePatternGenerator object,
+ * corresponding to date styles UDAT_FULL..UDAT_SHORT. This method gets
+ * the style for UDAT_MEDIUM (the default). To get them individually, see
+ * udatpg_getDateTimeFormatForStyle.
+ *
  * @param dtpg   a pointer to UDateTimePatternGenerator.
  * @param pLength A pointer that will receive the length of the format
  * @return dateTimeFormat.
@@ -514,6 +525,70 @@ U_CAPI const UChar * U_EXPORT2
 udatpg_getDateTimeFormat(const UDateTimePatternGenerator *dtpg,
                          int32_t *pLength);
 
+#if !UCONFIG_NO_FORMATTING
+#ifndef U_HIDE_DRAFT_API
+/**
+ * dateTimeFormats are message patterns used to compose combinations of date
+ * and time patterns. There are four length styles, corresponding to the
+ * inferred style of the date pattern; these are UDateFormatStyle values:
+ *  - UDAT_FULL (for date pattern with weekday and long month), else
+ *  - UDAT_LONG (for a date pattern with long month), else
+ *  - UDAT_MEDIUM (for a date pattern with abbreviated month), else
+ *  - UDAT_SHORT (for any other date pattern).
+ * For details on dateTimeFormats, see
+ * https://www.unicode.org/reports/tr35/tr35-dates.html#dateTimeFormats.
+ * The default pattern in the root locale for all styles is "{1} {0}".
+ *
+ * @param udtpg
+ *              a pointer to the UDateTimePatternGenerator
+ * @param style
+ *              one of UDAT_FULL..UDAT_SHORT. Error if out of range.
+ * @param dateTimeFormat
+ *              the new dateTimeFormat to set for the the specified style
+ * @param length
+ *              the length of dateTimeFormat, or -1 if unknown and pattern
+ *              is null-terminated
+ * @param pErrorCode
+ *              a pointer to the UErrorCode (in/out parameter); if no failure
+ *              status is already set, it will be set according to result of the
+ *              function (e.g. U_ILLEGAL_ARGUMENT_ERROR for style out of range).
+ * @draft ICU 71
+ */
+U_CAPI void U_EXPORT2
+udatpg_setDateTimeFormatForStyle(UDateTimePatternGenerator *udtpg,
+                        UDateFormatStyle style,
+                        const UChar *dateTimeFormat, int32_t length,
+                        UErrorCode *pErrorCode);
+
+/**
+ * Getter corresponding to udatpg_setDateTimeFormatForStyle.
+ *
+ * @param udtpg
+ *              a pointer to the UDateTimePatternGenerator
+ * @param style
+ *              one of UDAT_FULL..UDAT_SHORT. Error if out of range.
+ * @param pLength
+ *              a pointer that will receive the length of the format. May be NULL
+ *              if length is not desired.
+ * @param pErrorCode
+ *              a pointer to the UErrorCode (in/out parameter); if no failure
+ *              status is already set, it will be set according to result of the
+ *              function (e.g. U_ILLEGAL_ARGUMENT_ERROR for style out of range).
+ * @return
+ *              pointer to the current dateTimeFormat (0 terminated) for the specified
+ *              style, or empty string in case of error. The pointer and its contents
+ *              may no longer be valid if udatpg_setDateTimeFormat is called, or
+ *              udatpg_setDateTimeFormatForStyle for the same style is called, or the
+ *              UDateTimePatternGenerator object is closed.
+ * @draft ICU 71
+ */
+U_CAPI const UChar* U_EXPORT2
+udatpg_getDateTimeFormatForStyle(const UDateTimePatternGenerator *udtpg,
+                        UDateFormatStyle style, int32_t *pLength,
+                        UErrorCode *pErrorCode);
+#endif /* U_HIDE_DRAFT_API */
+#endif /* #if !UCONFIG_NO_FORMATTING */
+
 /**
  * The decimal value is used in formatting fractions of seconds. If the
  * skeleton contains fractional seconds, then this is used with the
index 513989ac6bbfbf359e529e19e464cbf37ef3225e..8f157d2f5d491cf9e9f5d0af85fb827c5e0449c9 100644 (file)
@@ -368,23 +368,23 @@ static void TestFormatToResult() {
 
     {
         const char* message = "Field position test 1";
-        const UChar* expectedString = u"27. September 2010, 15:00 – 2. März 2011, 18:30";
+        const UChar* expectedString = u"27. September 2010 um 15:00 – 2. März 2011 um 18:30";
         udtitvfmt_formatToResult(fmt, Date201009270800, Date201103021030, fdi, &ec);
         assertSuccess("Formatting", &ec);
         static const UFieldPositionWithCategory expectedFieldPositions[] = {
             // category, field, begin index, end index
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 25},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 27},
             {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
             {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 13},
             {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 14, 18},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 20, 22},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 23, 25},
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 28, 47},
-            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 28, 29},
-            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 31, 35},
-            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 36, 40},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 42, 44},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 45, 47}};
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 22, 24},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 25, 27},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 30, 51},
+            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 30, 31},
+            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 33, 37},
+            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 38, 42},
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 46, 48},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 49, 51}};
         checkMixedFormattedValue(
             message,
             udtitvfmt_resultAsValue(fdi, &ec),
@@ -438,23 +438,23 @@ static void TestFormatCalendarToResult() {
 
     {
         const char* message = "Field position test 1";
-        const UChar* expectedString = u"27. September 2010, 15:00 – 2. März 2011, 18:30";
+        const UChar* expectedString = u"27. September 2010 um 15:00 – 2. März 2011 um 18:30";
         udtitvfmt_formatCalendarToResult(fmt, ucal1, ucal2, fdi, &ec);
         assertSuccess("Formatting", &ec);
         static const UFieldPositionWithCategory expectedFieldPositions[] = {
             // category, field, begin index, end index
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 25},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 27},
             {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
             {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 13},
             {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 14, 18},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 20, 22},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 23, 25},
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 28, 47},
-            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 28, 29},
-            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 31, 35},
-            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 36, 40},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 42, 44},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 45, 47}};
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 22, 24},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 25, 27},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 30, 51},
+            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 30, 31},
+            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 33, 37},
+            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 38, 42},
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 46, 48},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 49, 51}};
         checkMixedFormattedValue(
             message,
             udtitvfmt_resultAsValue(fdi, &ec),
@@ -493,23 +493,23 @@ static void TestFormatCalendarToResult() {
         ucal_setMillis(ucal5, Date158210160000, &ec);
         //                                        1         2         3         4
         //                              012345678901234567890123456789012345678901234567890
-        const UChar* expectedString = u"4. Oktober 1582, 00:00 – 16. Oktober 1582, 00:00";
+        const UChar* expectedString = u"4. Oktober 1582 um 00:00 – 16. Oktober 1582 um 00:00";
         udtitvfmt_formatCalendarToResult(fmt, ucal4, ucal5, fdi, &ec);
         assertSuccess("Formatting", &ec);
         static const UFieldPositionWithCategory expectedFieldPositions[] = {
             // category, field, begin index, end index
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 22},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 24},
             {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 1},
             {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 3, 10},
             {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 11, 15},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 17, 19},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 20, 22},
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 25, 48},
-            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 25, 27},
-            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 29, 36},
-            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 37, 41},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 43, 45},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 46, 48}};
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 19, 21},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 22, 24},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 27, 52},
+            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 27, 29},
+            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 31, 38},
+            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 39, 43},
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 47, 49},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 50, 52}};
         checkMixedFormattedValue(
             message,
             udtitvfmt_resultAsValue(fdi, &ec),
@@ -527,23 +527,23 @@ static void TestFormatCalendarToResult() {
         const char* message = "Field position test 4";
         //                                        1         2         3         4
         //                              012345678901234567890123456789012345678901234567890
-        const UChar* expectedString = u"14. Oktober 1582, 00:00 – 16. Oktober 1582, 00:00";
+        const UChar* expectedString = u"14. Oktober 1582 um 00:00 – 16. Oktober 1582 um 00:00";
         udtitvfmt_formatCalendarToResult(fmt, ucal4, ucal5, fdi, &ec);
         assertSuccess("Formatting", &ec);
         static const UFieldPositionWithCategory expectedFieldPositions[] = {
             // category, field, begin index, end index
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 23},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 25},
             {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
             {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 11},
             {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 12, 16},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 18, 20},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 21, 23},
-            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 26, 49},
-            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 26, 28},
-            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 30, 37},
-            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 38, 42},
-            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 44, 46},
-            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 47, 49}};
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 20, 22},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 23, 25},
+            {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 28, 53},
+            {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 28, 30},
+            {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 32, 39},
+            {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 40, 44},
+            {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 48, 50},
+            {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 51, 53}};
         checkMixedFormattedValue(
             message,
             udtitvfmt_resultAsValue(fdi, &ec),
index 5f15577b26452696c644f8549e17c7fba36c6eb8..66cfb41d47f8048e8f3e17ab9cdc30cfbd34c861 100644 (file)
@@ -46,6 +46,7 @@ static void TestGetFieldDisplayNames(void);
 static void TestGetDefaultHourCycle(void);
 static void TestGetDefaultHourCycleOnEmptyInstance(void);
 static void TestEras(void);
+static void TestDateTimePatterns(void);
 
 void addDateTimePatternGeneratorTest(TestNode** root) {
     TESTCASE(TestOpenClose);
@@ -56,6 +57,7 @@ void addDateTimePatternGeneratorTest(TestNode** root) {
     TESTCASE(TestGetDefaultHourCycle);
     TESTCASE(TestGetDefaultHourCycleOnEmptyInstance);
     TESTCASE(TestEras);
+    TESTCASE(TestDateTimePatterns);
 }
 
 /*
@@ -614,4 +616,178 @@ static void TestEras(void) {
     }
 }
 
+enum { kNumDateTimePatterns = 4 };
+
+typedef struct {
+    const char* localeID;
+    const UChar* expectPat[kNumDateTimePatterns];
+} DTPLocaleAndResults;
+
+static void doDTPatternTest(UDateTimePatternGenerator* udtpg,
+                            const UChar** skeletons,
+                            DTPLocaleAndResults* localeAndResultsPtr);
+
+static void TestDateTimePatterns(void) {
+    const UChar* skeletons[kNumDateTimePatterns] = {
+        u"yMMMMEEEEdjmm", // full date, short time
+        u"yMMMMdjmm",     // long date, short time
+        u"yMMMdjmm",      // medium date, short time
+        u"yMdjmm"         // short date, short time
+    };
+    // The following tests some locales in which there are differences between the
+    // DateTimePatterns of various length styles.
+    DTPLocaleAndResults localeAndResults[] = {
+        { "en", { u"EEEE, MMMM d, y 'at' h:mm a", // long != medium
+                  u"MMMM d, y 'at' h:mm a",
+                  u"MMM d, y, h:mm a",
+                  u"M/d/y, h:mm a" } },
+        { "fr", { u"EEEE d MMMM y 'à' HH:mm", // medium != short
+                  u"d MMMM y 'à' HH:mm",
+                  u"d MMM y, HH:mm",
+                  u"dd/MM/y HH:mm" } },
+        { "ha", { u"EEEE d MMMM, y HH:mm", // full != long
+                  u"d MMMM, y 'da' HH:mm",
+                  u"d MMM, y, HH:mm",
+                  u"y-MM-dd, HH:mm" } },
+        { NULL, { NULL, NULL, NULL, NULL } } // terminator
+    };
+
+    const UChar* enDTPatterns[kNumDateTimePatterns] = {
+        u"{1} 'at' {0}",
+        u"{1} 'at' {0}",
+        u"{1}, {0}",
+        u"{1}, {0}"
+    };
+    const UChar* modDTPatterns[kNumDateTimePatterns] = {
+        u"{1} _0_ {0}",
+        u"{1} _1_ {0}",
+        u"{1} _2_ {0}",
+        u"{1} _3_ {0}"
+    };
+    DTPLocaleAndResults enModResults = { "en", { u"EEEE, MMMM d, y _0_ h:mm a",
+                                                 u"MMMM d, y _1_ h:mm a",
+                                                 u"MMM d, y _2_ h:mm a",
+                                                 u"M/d/y _3_ h:mm a" }
+    };
+
+    // Test various locales with standard data
+    UErrorCode status;
+    UDateTimePatternGenerator* udtpg;
+    DTPLocaleAndResults* localeAndResultsPtr = localeAndResults;
+    for (; localeAndResultsPtr->localeID != NULL; localeAndResultsPtr++) {
+        status = U_ZERO_ERROR;
+        udtpg = udatpg_open(localeAndResultsPtr->localeID, &status);
+        if (U_FAILURE(status)) {
+            log_data_err("FAIL: udatpg_open for locale %s: %s", localeAndResultsPtr->localeID, myErrorName(status));
+        } else {
+            doDTPatternTest(udtpg, skeletons, localeAndResultsPtr);
+            udatpg_close(udtpg);
+        }
+    }
+    // Test getting and modifying date-time combining patterns
+    status = U_ZERO_ERROR;
+    udtpg = udatpg_open("en", &status);
+    if (U_FAILURE(status)) {
+        log_data_err("FAIL: udatpg_open #2 for locale en: %s", myErrorName(status));
+    } else {
+        char bExpect[64];
+        char bGet[64];
+        const UChar* uGet;
+        int32_t uGetLen, uExpectLen;
+
+        // Test error: style out of range
+        status = U_ZERO_ERROR;
+        uGet = udatpg_getDateTimeFormatForStyle(udtpg, UDAT_NONE, &uGetLen, &status);
+        if (status != U_ILLEGAL_ARGUMENT_ERROR || uGetLen != 0 || uGet==NULL || *uGet!= 0) {
+            if (uGet==NULL) {
+                log_err("FAIL: udatpg_getDateTimeFormatForStyle with invalid style, expected U_ILLEGAL_ARGUMENT_ERROR "
+                        "and ptr to empty string but got %s, len %d, ptr = NULL\n", myErrorName(status), uGetLen);
+            } else {
+                log_err("FAIL: udatpg_getDateTimeFormatForStyle with invalid style, expected U_ILLEGAL_ARGUMENT_ERROR "
+                        "and ptr to empty string but got %s, len %d, *ptr = %04X\n", myErrorName(status), uGetLen, *uGet);
+            }
+        }
+
+        // Test normal getting and setting
+        for (int32_t patStyle = 0; patStyle < kNumDateTimePatterns; patStyle++) {
+            status = U_ZERO_ERROR;
+            uExpectLen = u_strlen(enDTPatterns[patStyle]);
+            uGet = udatpg_getDateTimeFormatForStyle(udtpg, patStyle, &uGetLen, &status);
+            if (U_FAILURE(status)) {
+                log_err("FAIL udatpg_getDateTimeFormatForStyle %d (en before mod), get %s\n", patStyle, myErrorName(status));
+            } else if (uGetLen != uExpectLen || u_strncmp(uGet, enDTPatterns[patStyle], uExpectLen) != 0) {
+                u_austrcpy(bExpect, enDTPatterns[patStyle]);
+                u_austrcpy(bGet, uGet);
+                log_err("ERROR udatpg_getDateTimeFormatForStyle %d (en before mod), expect %d:\"%s\", get %d:\"%s\"\n",
+                        patStyle, uExpectLen, bExpect, uGetLen, bGet);
+            }
+            status = U_ZERO_ERROR;
+            udatpg_setDateTimeFormatForStyle(udtpg, patStyle, modDTPatterns[patStyle], -1, &status);
+            if (U_FAILURE(status)) {
+                log_err("FAIL udatpg_setDateTimeFormatForStyle %d (en), get %s\n", patStyle, myErrorName(status));
+            } else {
+                uExpectLen = u_strlen(modDTPatterns[patStyle]);
+                uGet = udatpg_getDateTimeFormatForStyle(udtpg, patStyle, &uGetLen, &status);
+                if (U_FAILURE(status)) {
+                    log_err("FAIL udatpg_getDateTimeFormatForStyle %d (en after  mod), get %s\n", patStyle, myErrorName(status));
+                } else if (uGetLen != uExpectLen || u_strncmp(uGet, modDTPatterns[patStyle], uExpectLen) != 0) {
+                    u_austrcpy(bExpect, modDTPatterns[patStyle]);
+                    u_austrcpy(bGet, uGet);
+                    log_err("ERROR udatpg_getDateTimeFormatForStyle %d (en after  mod), expect %d:\"%s\", get %d:\"%s\"\n",
+                            patStyle, uExpectLen, bExpect, uGetLen, bGet);
+                }
+            }
+        }
+        // Test result of setting
+        doDTPatternTest(udtpg, skeletons, &enModResults);
+        // Test old get/set functions
+        uExpectLen = u_strlen(modDTPatterns[UDAT_MEDIUM]);
+        uGet = udatpg_getDateTimeFormat(udtpg, &uGetLen);
+        if (uGetLen != uExpectLen || u_strncmp(uGet, modDTPatterns[UDAT_MEDIUM], uExpectLen) != 0) {
+            u_austrcpy(bExpect, modDTPatterns[UDAT_MEDIUM]);
+            u_austrcpy(bGet, uGet);
+            log_err("ERROR udatpg_getDateTimeFormat (en after  mod), expect %d:\"%s\", get %d:\"%s\"\n",
+                    uExpectLen, bExpect, uGetLen, bGet);
+        }
+        udatpg_setDateTimeFormat(udtpg, modDTPatterns[UDAT_SHORT], -1); // set all dateTimePatterns to the short format
+        uExpectLen = u_strlen(modDTPatterns[UDAT_SHORT]);
+        u_austrcpy(bExpect, modDTPatterns[UDAT_SHORT]);
+        for (int32_t patStyle = 0; patStyle < kNumDateTimePatterns; patStyle++) {
+            status = U_ZERO_ERROR;
+            uGet = udatpg_getDateTimeFormatForStyle(udtpg, patStyle, &uGetLen, &status);
+            if (U_FAILURE(status)) {
+                log_err("FAIL udatpg_getDateTimeFormatForStyle %d (en after second mod), get %s\n", patStyle, myErrorName(status));
+            } else if (uGetLen != uExpectLen || u_strncmp(uGet, modDTPatterns[UDAT_SHORT], uExpectLen) != 0) {
+                u_austrcpy(bGet, uGet);
+                log_err("ERROR udatpg_getDateTimeFormatForStyle %d (en after second mod), expect %d:\"%s\", get %d:\"%s\"\n",
+                        patStyle, uExpectLen, bExpect, uGetLen, bGet);
+            }
+        }
+
+        udatpg_close(udtpg);
+    }
+}
+
+static void doDTPatternTest(UDateTimePatternGenerator* udtpg,
+                            const UChar** skeletons,
+                            DTPLocaleAndResults* localeAndResultsPtr) {
+    for (int32_t patStyle = 0; patStyle < kNumDateTimePatterns; patStyle++) {
+        UChar uGet[64];
+        int32_t uGetLen, uExpectLen;
+        UErrorCode status = U_ZERO_ERROR;
+        uExpectLen = u_strlen(localeAndResultsPtr->expectPat[patStyle]);
+        uGetLen = udatpg_getBestPattern(udtpg, skeletons[patStyle], -1, uGet, 64, &status);
+        if (U_FAILURE(status)) {
+            log_err("FAIL udatpg_getBestPattern locale %s style %d: %s\n", localeAndResultsPtr->localeID, patStyle, myErrorName(status));
+        } else if (uGetLen != uExpectLen || u_strncmp(uGet, localeAndResultsPtr->expectPat[patStyle], uExpectLen) != 0) {
+            char bExpect[64];
+            char bGet[64];
+            u_austrcpy(bExpect, localeAndResultsPtr->expectPat[patStyle]);
+            u_austrcpy(bGet, uGet);
+            log_err("ERROR udatpg_getBestPattern locale %s style %d, expect %d:\"%s\", get %d:\"%s\"\n",
+                    localeAndResultsPtr->localeID, patStyle, uExpectLen, bExpect, uGetLen, bGet);
+        }
+    }
+}
+
 #endif
index 16f3002a95ef5b1b494ff3d53033e422d0a4d731..920ec75c8b940e3b2d032a12233104ca00ef9161 100644 (file)
@@ -190,7 +190,7 @@ void DateFormatTest::TestPatterns() {
         {UDAT_ABBR_UTC_TZ, "ZZZZ", "en", "ZZZZ"},
 
         {UDAT_YEAR_NUM_MONTH_DAY UDAT_ABBR_UTC_TZ, "yMdZZZZ", "en", "M/d/y, ZZZZ"},
-        {UDAT_MONTH_DAY UDAT_LOCATION_TZ, "MMMMdVVVV", "en", "MMMM d, VVVV"}
+        {UDAT_MONTH_DAY UDAT_LOCATION_TZ, "MMMMdVVVV", "en", "MMMM d 'at' VVVV"}
     };
 
     IcuTestErrorCode errorCode(*this, "TestPatterns()");
index 6fe8af05b60a22639d2b7d452bbb64447565adca..dde9d4dd3acf6fbb15317ffaa7a6f536861960a0 100644 (file)
@@ -46,6 +46,7 @@ void IntlTestDateTimePatternGeneratorAPI::runIndexedTest( int32_t index, UBool e
         TESTCASE(10, testGetDefaultHourCycle_OnEmptyInstance);
         TESTCASE(11, test_jConsistencyOddLocales);
         TESTCASE(12, testBestPattern);
+        TESTCASE(13, testDateTimePatterns);
         default: name = ""; break;
     }
 }
@@ -530,7 +531,7 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
     format->applyPattern(gen->getBestPattern(UnicodeString("MMMMdHmm"), status));
     dateReturned.remove();
     dateReturned = format->format(sampleDate, dateReturned, status);
-    expectedResult=UnicodeString("14. von Oktober, 08:58", -1, US_INV);
+    expectedResult=UnicodeString("14. von Oktober um 08:58", -1, US_INV);
     if ( dateReturned != expectedResult ) {
         errln(UnicodeString("ERROR: Simple test addPattern failed!: d\'. von\' MMMM   Got: ") + dateReturned + UnicodeString(" Expected: ") + expectedResult);
     }
@@ -1631,4 +1632,150 @@ void IntlTestDateTimePatternGeneratorAPI::testBestPattern() {
     }
 }
 
+void IntlTestDateTimePatternGeneratorAPI::testDateTimePatterns() {
+    UnicodeString skeletons[kNumDateTimePatterns] = {
+        UnicodeString("yMMMMEEEEdjmm"), // full date, short time
+        UnicodeString("yMMMMdjmm"),     // long date, short time
+        UnicodeString("yMMMdjmm"),      // medium date, short time
+        UnicodeString("yMdjmm")         // short date, short time
+    };
+    // The following tests some locales in which there are differences between the
+    // DateTimePatterns of various length styles.
+    DTPLocaleAndResults localeAndResults[] = {
+        { "en", { UnicodeString(u"EEEE, MMMM d, y 'at' h:mm a"), // long != medium
+                  UnicodeString(u"MMMM d, y 'at' h:mm a"),
+                  UnicodeString(u"MMM d, y, h:mm a"),
+                  UnicodeString(u"M/d/y, h:mm a") } },
+        { "fr", { UnicodeString(u"EEEE d MMMM y 'à' HH:mm"), // medium != short
+                  UnicodeString(u"d MMMM y 'à' HH:mm"),
+                  UnicodeString(u"d MMM y, HH:mm"),
+                  UnicodeString(u"dd/MM/y HH:mm") } },
+        { "ha", { UnicodeString(u"EEEE d MMMM, y HH:mm"), // full != long
+                  UnicodeString(u"d MMMM, y 'da' HH:mm"),
+                  UnicodeString(u"d MMM, y, HH:mm"),
+                  UnicodeString(u"y-MM-dd, HH:mm") } },
+        { nullptr, { UnicodeString(""), UnicodeString(""), // terminator
+                    UnicodeString(""), UnicodeString("") } },
+    };
+
+    UnicodeString enDTPatterns[kNumDateTimePatterns] = {
+        UnicodeString(u"{1} 'at' {0}"),
+        UnicodeString(u"{1} 'at' {0}"),
+        UnicodeString(u"{1}, {0}"),
+        UnicodeString(u"{1}, {0}")
+    };
+    UnicodeString modDTPatterns[kNumDateTimePatterns] = {
+        UnicodeString(u"{1} _0_ {0}"),
+        UnicodeString(u"{1} _1_ {0}"),
+        UnicodeString(u"{1} _2_ {0}"),
+        UnicodeString(u"{1} _3_ {0}")
+    };
+    DTPLocaleAndResults enModResults = { "en", { UnicodeString(u"EEEE, MMMM d, y _0_ h:mm a"),
+                                                 UnicodeString(u"MMMM d, y _1_ h:mm a"),
+                                                 UnicodeString(u"MMM d, y _2_ h:mm a"),
+                                                 UnicodeString(u"M/d/y _3_ h:mm a") }
+    };
+
+    // Test various locales with standard data
+    UErrorCode status;
+    LocalPointer<DateTimePatternGenerator> dtpg;
+    DTPLocaleAndResults* localeAndResultsPtr = localeAndResults;
+    for (; localeAndResultsPtr->localeID != nullptr; localeAndResultsPtr++) {
+        status = U_ZERO_ERROR;
+        Locale locale(localeAndResultsPtr->localeID);
+        dtpg.adoptInstead(DateTimePatternGenerator::createInstance(locale, status));
+        if (U_FAILURE(status)) {
+            dataerrln("FAIL: DateTimePatternGenerator::createInstance for locale %s: %s",
+                        localeAndResultsPtr->localeID, u_errorName(status));
+        } else {
+            doDTPatternTest(dtpg.getAlias(), skeletons, localeAndResultsPtr);
+        }
+    }
+    // Test getting and modifying date-time combining patterns
+    status = U_ZERO_ERROR;
+    dtpg.adoptInstead(DateTimePatternGenerator::createInstance(Locale::getEnglish(), status));
+    if (U_FAILURE(status)) {
+        dataerrln("FAIL: DateTimePatternGenerator::createInstance #2 for locale en: %s", u_errorName(status));
+    } else {
+        char bExpect[64];
+        char bGet[64];
+        // Test style out of range
+        status = U_ZERO_ERROR;
+        const UnicodeString& dtFormat0 = dtpg->getDateTimeFormat(UDAT_NONE, status);
+        int32_t dtFormat0Len = dtFormat0.length();
+        if (status != U_ILLEGAL_ARGUMENT_ERROR || dtFormat0Len != 0) {
+            errln("ERROR: getDateTimeFormat with invalid style, expected U_ILLEGAL_ARGUMENT_ERROR and lero-length string, "
+                  "got %s with length %d", u_errorName(status), dtFormat0Len);
+        }
+        // Test normal getting and setting
+        for (int32_t patStyle = 0; patStyle < kNumDateTimePatterns; patStyle++) {
+            status = U_ZERO_ERROR;
+            const UnicodeString& dtFormat1 = dtpg->getDateTimeFormat((UDateFormatStyle)patStyle, status);
+            if (U_FAILURE(status)) {
+                errln("FAIL: getDateTimeFormat for en before mod, style %d, get %s", patStyle, u_errorName(status));
+            } else  if (dtFormat1 != enDTPatterns[patStyle]) {
+                enDTPatterns[patStyle].extract(0, enDTPatterns[patStyle].length(), bExpect, 64);
+                dtFormat1.extract(0, dtFormat1.length(), bGet, 64);
+                errln("ERROR: getDateTimeFormat for en before mod, style %d, expect \"%s\", get \"%s\"",
+                        patStyle, bExpect, bGet);
+            }
+            status = U_ZERO_ERROR;
+            dtpg->setDateTimeFormat((UDateFormatStyle)patStyle, modDTPatterns[patStyle], status);
+            if (U_FAILURE(status)) {
+                errln("FAIL: setDateTimeFormat for en, style %d, get %s", patStyle, u_errorName(status));
+            } else {
+                const UnicodeString& dtFormat2 = dtpg->getDateTimeFormat((UDateFormatStyle)patStyle, status);
+                if (U_FAILURE(status)) {
+                    errln("FAIL: getDateTimeFormat for en after  mod, style %d, get %s", patStyle, u_errorName(status));
+                } else if (dtFormat2 != modDTPatterns[patStyle]) {
+                    modDTPatterns[patStyle].extract(0, modDTPatterns[patStyle].length(), bExpect, 64);
+                    dtFormat2.extract(0, dtFormat2.length(), bGet, 64);
+                    errln("ERROR: getDateTimeFormat for en after mod, style %d, expect \"%s\", get \"%s\"",
+                            patStyle, bExpect, bGet);
+                }
+            }
+        }
+        // Test result of setting
+        doDTPatternTest(dtpg.getAlias(), skeletons, &enModResults);
+        // Test old get/set functions
+        const UnicodeString& dtFormat3 = dtpg->getDateTimeFormat();
+        if (dtFormat3 != modDTPatterns[UDAT_MEDIUM]) {
+            modDTPatterns[UDAT_MEDIUM].extract(0, modDTPatterns[UDAT_MEDIUM].length(), bExpect, 64);
+            dtFormat3.extract(0, dtFormat3.length(), bGet, 64);
+            errln("ERROR: old getDateTimeFormat for en after mod, expect \"%s\", get \"%s\"", bExpect, bGet);
+        }
+        dtpg->setDateTimeFormat(modDTPatterns[UDAT_SHORT]); // set all dateTimePatterns to the short format
+        modDTPatterns[UDAT_SHORT].extract(0, modDTPatterns[UDAT_SHORT].length(), bExpect, 64);
+        for (int32_t patStyle = 0; patStyle < kNumDateTimePatterns; patStyle++) {
+            status = U_ZERO_ERROR;
+            const UnicodeString& dtFormat4 = dtpg->getDateTimeFormat((UDateFormatStyle)patStyle, status);
+            if (U_FAILURE(status)) {
+                errln("FAIL: getDateTimeFormat for en after second mod, style %d, get %s", patStyle, u_errorName(status));
+            } else if (dtFormat4 != modDTPatterns[UDAT_SHORT]) {
+                dtFormat4.extract(0, dtFormat4.length(), bGet, 64);
+                errln("ERROR: getDateTimeFormat for en after second mod, style %d, expect \"%s\", get \"%s\"",
+                        patStyle, bExpect, bGet);
+            }
+        }
+    }
+}
+
+void IntlTestDateTimePatternGeneratorAPI::doDTPatternTest(DateTimePatternGenerator* dtpg, UnicodeString* skeletons, DTPLocaleAndResults* localeAndResultsPtr) {
+    for (int32_t patStyle = 0; patStyle < kNumDateTimePatterns; patStyle++) {
+        UErrorCode status = U_ZERO_ERROR;
+        UnicodeString getPat = dtpg->getBestPattern(skeletons[patStyle], UDATPG_MATCH_NO_OPTIONS, status);
+        if (U_FAILURE(status)) {
+            errln("FAIL: DateTimePatternGenerator::getBestPattern locale %s, style %d: %s",
+                        localeAndResultsPtr->localeID, patStyle, u_errorName(status));
+        } else if (getPat != localeAndResultsPtr->expectPat[patStyle]) {
+            char bExpect[64];
+            char bGet[64];
+            localeAndResultsPtr->expectPat[patStyle].extract(0, localeAndResultsPtr->expectPat[patStyle].length(), bExpect, 64);
+            getPat.extract(0, getPat.length(), bGet, 64);
+            errln("ERROR: DateTimePatternGenerator::getBestPattern locale %s, style %d, expect \"%s\", get \"%s\"",
+                        localeAndResultsPtr->localeID, patStyle, bExpect, bGet);
+        }
+    }
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
index 4650fd7fcf6764599b4446818d0203cb7e845792..ec700603eb13e5a4349b890e84c9935dded951ac 100644 (file)
@@ -13,6 +13,8 @@
 
 #if !UCONFIG_NO_FORMATTING
 
+#include "unicode/dtptngen.h"
+#include "unicode/ustring.h"
 #include "intltest.h"
 
 /**
@@ -38,6 +40,14 @@ private:
     void testGetDefaultHourCycle_OnEmptyInstance();
     void test_jConsistencyOddLocales();
     void testBestPattern();
+    void testDateTimePatterns();
+
+    enum { kNumDateTimePatterns = 4 };
+    typedef struct {
+        const char* localeID;
+        const UnicodeString expectPat[kNumDateTimePatterns];
+    } DTPLocaleAndResults;
+    void doDTPatternTest(DateTimePatternGenerator* dtpg, UnicodeString* skeletons, DTPLocaleAndResults* localeAndResultsPtr);
 };
 
 #endif /* #if !UCONFIG_NO_FORMATTING */
index e55aa8fbe3750345b3035c3f41758dc125dea8df..66f498222170fc6fdad9afa0bf557bce58ed22b0 100644 (file)
@@ -2051,7 +2051,7 @@ void TestMessageFormat::TestMessageFormatDateSkeleton() {
     UDate date = LocaleTest::date(2021-1900, UCAL_NOVEMBER, 23, 16, 42, 55);
 
     doTheRealDateTimeSkeletonTesting(date, u"{0,date,::MMMMd}", "en", u"November 23", status);
-    doTheRealDateTimeSkeletonTesting(date, u"{0,date,::yMMMMdjm}", "en", u"November 23, 2021, 4:42 PM", status);
+    doTheRealDateTimeSkeletonTesting(date, u"{0,date,::yMMMMdjm}", "en", u"November 23, 2021 at 4:42 PM", status);
     doTheRealDateTimeSkeletonTesting(date, u"{0,date,   ::   yMMMMd   }", "en", u"November 23, 2021", status);
     doTheRealDateTimeSkeletonTesting(date, u"{0,date,::yMMMMd}", "fr", u"23 novembre 2021", status);
     doTheRealDateTimeSkeletonTesting(date, u"Expiration: {0,date,::yMMM}!", "en", u"Expiration: Nov 2021!", status);
@@ -2065,7 +2065,7 @@ void TestMessageFormat::TestMessageFormatTimeSkeleton() {
     UDate date = LocaleTest::date(2021-1900, UCAL_NOVEMBER, 23, 16, 42, 55);
 
     doTheRealDateTimeSkeletonTesting(date, u"{0,time,::MMMMd}", "en", u"November 23", status);
-    doTheRealDateTimeSkeletonTesting(date, u"{0,time,::yMMMMdjm}", "en", u"November 23, 2021, 4:42 PM", status);
+    doTheRealDateTimeSkeletonTesting(date, u"{0,time,::yMMMMdjm}", "en", u"November 23, 2021 at 4:42 PM", status);
     doTheRealDateTimeSkeletonTesting(date, u"{0,time,   ::   yMMMMd   }", "en", u"November 23, 2021", status);
     doTheRealDateTimeSkeletonTesting(date, u"{0,time,::yMMMMd}", "fr", u"23 novembre 2021", status);
     doTheRealDateTimeSkeletonTesting(date, u"Expiration: {0,time,::yMMM}!", "en", u"Expiration: Nov 2021!", status);
index 1fa92146c9e0ae3a45e155ba421ce6048b3b4901..e60c1e40f277b8469b6af0fcff980fe793a7b63a 100644 (file)
@@ -313,8 +313,11 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
     }
 
     private void setDateTimeFromCalendar(ULocale uLocale) {
-        String dateTimeFormat = Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM);
-        setDateTimeFormat(dateTimeFormat);
+        Calendar cal = Calendar.getInstance(uLocale);
+        for (int style = DateFormat.FULL; style <= DateFormat.SHORT; style++) {
+            String dateTimeFormat = Calendar.getDateTimePattern(cal, uLocale, style);
+            setDateTimeFormat(style, dateTimeFormat);
+        }
     }
 
     private void setDecimalSymbols(ULocale uLocale) {
@@ -650,8 +653,26 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
 
         if (datePattern == null) return timePattern == null ? "" : timePattern;
         if (timePattern == null) return datePattern;
+        // determine which dateTimeFormat to use
+        String canonicalSkeleton = current.toCanonicalString(); // month fields use M, weekday fields use E
+        int style = DateFormat.SHORT;
+        int monthFieldLen = 0;
+        int monthFieldOffset = canonicalSkeleton.indexOf('M');
+        if (monthFieldOffset >= 0) {
+            monthFieldLen = 1 + canonicalSkeleton.lastIndexOf('M') - monthFieldOffset;
+        }
+        if (monthFieldLen == 4) {
+            if (canonicalSkeleton.indexOf('E') >= 0) {
+                style = DateFormat.FULL;
+            } else {
+                style = DateFormat.LONG;
+            }
+        } else if (monthFieldLen == 3) {
+            style = DateFormat.MEDIUM;
+        }
+        // and now use it to compose date and time
         return SimpleFormatterImpl.formatRawPattern(
-                getDateTimeFormat(), 2, 2, timePattern, datePattern);
+                getDateTimeFormat(style), 2, 2, timePattern, datePattern);
     }
 
     /*
@@ -998,23 +1019,79 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
      * for those two skeletons, so the result is put together with this pattern,
      * resulting in "d-MMM h:mm".
      *
+     * There are four DateTimeFormats in a DateTimePatternGenerator object,
+     * corresponding to date styles DateFormat.FULL..DateFormat.SHORT. This method sets
+     * all of them to the specified pattern. To set them individually, see
+     * setDateTimeFormat(int style, ...).
+     *
      * @param dateTimeFormat message format pattern, where {1} will be replaced by the date
      *            pattern and {0} will be replaced by the time pattern.
      * @stable ICU 3.6
      */
     public void setDateTimeFormat(String dateTimeFormat) {
         checkFrozen();
-        this.dateTimeFormat = dateTimeFormat;
+        for (int style = DateFormat.FULL; style <= DateFormat.SHORT; style++) {
+            setDateTimeFormat(style, dateTimeFormat);
+        }
     }
 
     /**
      * Getter corresponding to setDateTimeFormat.
      *
+     * There are four DateTimeFormats in a DateTimePatternGenerator object,
+     * corresponding to date styles DateFormat.FULL..DateFormat.SHORT. This method gets
+     * the style for DateFormat.MEDIUM (the default). To get them individually, see
+     * getDateTimeFormat(int style).
+     *
      * @return pattern
      * @stable ICU 3.6
      */
     public String getDateTimeFormat() {
-        return dateTimeFormat;
+        return getDateTimeFormat(DateFormat.MEDIUM);
+    }
+
+    /**
+     * dateTimeFormats are message patterns used to compose combinations of date
+     * and time patterns. There are four length styles, corresponding to the
+     * inferred style of the date pattern:
+     *  - DateFormat.FULL (for date pattern with weekday and long month), else
+     *  - DateFormat.LONG (for a date pattern with long month), else
+     *  - DateFormat.MEDIUM (for a date pattern with abbreviated month), else
+     *  - DateFormat.SHORT (for any other date pattern).
+     * For details on dateTimeFormats, see
+     * https://www.unicode.org/reports/tr35/tr35-dates.html#dateTimeFormats.
+     * The default pattern in the root locale for all styles is "{1} {0}".
+     *
+     * @param style
+     *              one of DateFormat.FULL..DateFormat.SHORT. An exception will
+     *              be thrown if out of range.
+     * @param dateTimeFormat
+     *              the new dateTimeFormat to set for the specified style
+     * @draft ICU 71
+     */
+    public void setDateTimeFormat(int style, String dateTimeFormat) {
+        if (style < DateFormat.FULL || style > DateFormat.SHORT) {
+            throw new IllegalArgumentException("Illegal style here: " + style);
+        }
+        checkFrozen();
+        this.dateTimeFormats[style] = dateTimeFormat;
+    }
+
+    /**
+     * Getter corresponding to setDateTimeFormat.
+     *
+     * @param style
+     *              one of DateFormat.FULL..DateFormat.SHORT. An exception will
+     *              be thrown if out of range.
+     * @return
+     *              the current dateTimeFormat for the specified style.
+     * @draft ICU 71
+     */
+    public String getDateTimeFormat(int style) {
+        if (style < DateFormat.FULL || style > DateFormat.SHORT) {
+            throw new IllegalArgumentException("Illegal style here: " + style);
+        }
+        return dateTimeFormats[style];
     }
 
     /**
@@ -1470,6 +1547,7 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
             DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone());
             result.skeleton2pattern = (TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>) skeleton2pattern.clone();
             result.basePattern_pattern = (TreeMap<String, PatternWithSkeletonFlag>) basePattern_pattern.clone();
+            result.dateTimeFormats = dateTimeFormats.clone();
             result.appendItemFormats = appendItemFormats.clone();
             result.fieldDisplayNames = fieldDisplayNames.clone();
             result.current = new DateTimeMatcher();
@@ -1971,7 +2049,14 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
     private TreeMap<DateTimeMatcher, PatternWithSkeletonFlag> skeleton2pattern = new TreeMap<>(); // items are in priority order
     private TreeMap<String, PatternWithSkeletonFlag> basePattern_pattern = new TreeMap<>(); // items are in priority order
     private String decimal = "?";
-    private String dateTimeFormat = "{1} {0}";
+    // For the following, need fallback patterns in case an empty instance 
+    // of DateTimePatterngenerator is used for formatting.
+    private String[] dateTimeFormats = {
+        "{1} {0}",
+        "{1} {0}",
+        "{1} {0}",
+        "{1} {0}"
+    };
     private String[] appendItemFormats = new String[TYPE_LIMIT];
     private String[][] fieldDisplayNames = new String[TYPE_LIMIT][DisplayWidth.COUNT];
     private char defaultHourFormatChar = 'H';
index 6b9b71142c7c02d9de30c729a9c01c27092b7715..3f25d642f010f5b5917ef9e502a3baf44d8d32af 100644 (file)
@@ -132,7 +132,7 @@ public class DateFormatTest extends TestFmwk {
                 {}, // marker for starting combinations
 
                 {DateFormat.YEAR_NUM_MONTH_DAY + DateFormat.ABBR_UTC_TZ, "yMdZZZZ", "en", "M/d/y, ZZZZ"},
-                {DateFormat.MONTH_DAY + DateFormat.LOCATION_TZ, "MMMMdVVVV", "en", "MMMM d, VVVV"},
+                {DateFormat.MONTH_DAY + DateFormat.LOCATION_TZ, "MMMMdVVVV", "en", "MMMM d 'at' VVVV"},
         };
         Date testDate = new Date(2012-1900, 6, 1, 14, 58, 59); // just for verbose log
 
index b12aadf0bf4441a2db19a87fa0529a8a9c207cf9..85566f034589b378572724aa35fdd0aea9044e7c 100644 (file)
@@ -163,7 +163,7 @@ public class DateTimeGeneratorTest extends TestFmwk {
         gen.addPattern("d'. von' MMMM", true, returnInfo);
         // the returnInfo is mostly useful for debugging problem cases
         format.applyPattern(gen.getBestPattern("MMMMdHmm"));
-        assertEquals("modified format: MMMdHmm", "14. von Oktober, 08:58", format.format(sampleDate));
+        assertEquals("modified format: MMMMdHmm", "14. von Oktober um 08:58", format.format(sampleDate));
 
         // get a pattern and modify it
         format = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locale);
@@ -1880,4 +1880,125 @@ public class DateTimeGeneratorTest extends TestFmwk {
             }
         }
     }
+
+    private final static int NUM_DATE_TIME_PATTERNS = 4;
+
+    private final class DTPLocaleAndResults {
+        public String localeID;
+        public String[] expectPat;
+        // Simple constructor
+        public DTPLocaleAndResults(String locID, String[] exPat) {
+            localeID = locID;
+            expectPat = exPat;
+        }
+    }
+
+    @Test
+    public void testDateTimePatterns() {
+        String[] skeletons = {
+            "yMMMMEEEEdjmm", // full date, short time
+            "yMMMMdjmm",     // long date, short time
+            "yMMMdjmm",      // medium date, short time
+            "yMdjmm",        // short date, short time
+        };
+        // The following tests some locales in which there are differences between the
+        // DateTimePatterns of various length styles.
+        final DTPLocaleAndResults[] localeAndResults = {
+            new DTPLocaleAndResults( "en", new String[]{ // long != medium
+                                           "EEEE, MMMM d, y 'at' h:mm a",
+                                           "MMMM d, y 'at' h:mm a",
+                                           "MMM d, y, h:mm a",
+                                           "M/d/y, h:mm a" } ),
+            new DTPLocaleAndResults( "fr", new String[]{ // medium != short
+                                           "EEEE d MMMM y 'à' HH:mm",
+                                           "d MMMM y 'à' HH:mm",
+                                           "d MMM y, HH:mm",
+                                           "dd/MM/y HH:mm" } ),
+            new DTPLocaleAndResults( "ha", new String[]{ // full != long
+                                           "EEEE d MMMM, y HH:mm",
+                                           "d MMMM, y 'da' HH:mm",
+                                           "d MMM, y, HH:mm",
+                                           "y-MM-dd, HH:mm" } ),
+        };
+
+        String[] enDTPatterns = {
+            "{1} 'at' {0}",
+            "{1} 'at' {0}",
+            "{1}, {0}",
+            "{1}, {0}",
+        };
+        String[] modDTPatterns = {
+            "{1} _0_ {0}",
+            "{1} _1_ {0}",
+            "{1} _2_ {0}",
+            "{1} _3_ {0}",
+        };
+        final DTPLocaleAndResults enModResults =
+            new DTPLocaleAndResults( "en", new String[]{
+                                            "EEEE, MMMM d, y _0_ h:mm a",
+                                            "MMMM d, y _1_ h:mm a",
+                                            "MMM d, y _2_ h:mm a",
+                                            "M/d/y _3_ h:mm a" } );
+
+        // Test various locales with standard data
+        DateTimePatternGenerator dtpg;
+        for (DTPLocaleAndResults localeAndResultItem: localeAndResults) {
+            dtpg = DateTimePatternGenerator.getInstance(new Locale(localeAndResultItem.localeID));
+            doDTPatternTest(dtpg, skeletons, localeAndResultItem);
+        }
+        // Test getting and modifying date-time combining patterns
+        dtpg = DateTimePatternGenerator.getInstance(ULocale.ENGLISH);
+        // Test style out of range
+        String dtFormat0 = "";
+        boolean gotException = false;
+        try {
+            dtFormat0 = dtpg.getDateTimeFormat(DateFormat.NONE);
+        } catch(IllegalArgumentException e) {
+            gotException = true;
+        }
+        if (!gotException) {
+            errln("ERROR: getDateTimeFormat with invalid style, expected IllegalArgumentException but got format \""
+                    + dtFormat0 + "\"");
+        }
+        // Test normal getting and setting
+        for (int patStyle = 0; patStyle < NUM_DATE_TIME_PATTERNS; patStyle++) {
+            String dtFormat1 = dtpg.getDateTimeFormat(patStyle);
+            if (!dtFormat1.equals(enDTPatterns[patStyle])) {
+                errln("ERROR: getDateTimeFormat for en before mod, style " + patStyle +
+                    ", expect \"" + enDTPatterns[patStyle] + "\", get \"" + dtFormat1 + "\"");
+            }
+            dtpg.setDateTimeFormat(patStyle, modDTPatterns[patStyle]);
+            String dtFormat2 = dtpg.getDateTimeFormat(patStyle);
+            if (!dtFormat2.equals(modDTPatterns[patStyle])) {
+                errln("ERROR: getDateTimeFormat for en after  mod, style " + patStyle +
+                    ", expect \"" + modDTPatterns[patStyle] + "\", get \"" + dtFormat2 + "\"");
+            }
+        }
+        // Test result of setting
+        doDTPatternTest(dtpg, skeletons, enModResults);
+        // Test old get/set functions
+        String dtFormat3 = dtpg.getDateTimeFormat();
+        if (!dtFormat3.equals(modDTPatterns[DateFormat.MEDIUM])) {
+            errln("ERROR: old getDateTimeFormat for en before mod, expect \"" +
+                    modDTPatterns[DateFormat.MEDIUM] + "\", get \"" + dtFormat3 + "\"");
+        }
+        dtpg.setDateTimeFormat(modDTPatterns[DateFormat.SHORT]); // set all dateTimePatterns to the short format
+        for (int patStyle = 0; patStyle < NUM_DATE_TIME_PATTERNS; patStyle++) {
+            String dtFormat4 = dtpg.getDateTimeFormat(patStyle);
+            if (!dtFormat4.equals(modDTPatterns[DateFormat.SHORT])) {
+                errln("ERROR: getDateTimeFormat for en after second mod, style " + patStyle +
+                    ", expect \"" + modDTPatterns[DateFormat.SHORT] + "\", get \"" + dtFormat4 + "\"");
+            }
+        }
+    }
+
+    private void doDTPatternTest(DateTimePatternGenerator dtpg, String[] skeletons, DTPLocaleAndResults localeAndResultItem) {
+        for (int patStyle = 0; patStyle < NUM_DATE_TIME_PATTERNS; patStyle++) {
+            String getPat = dtpg.getBestPattern(skeletons[patStyle]);
+            if (!getPat.equals(localeAndResultItem.expectPat[patStyle])) {
+                errln("ERROR: getBestPattern locale " + localeAndResultItem.localeID + ", style " + patStyle +
+                    ", expect \"" + localeAndResultItem.expectPat[patStyle] + "\", get \"" + getPat + "\"");
+            }
+        }
+    }
 }
index 7b94ebd180a074c2f1295f099e39196464c48925..60e1f051e7ffe4e6bf7ad2aa66d73e53847f40f0 100644 (file)
@@ -2140,7 +2140,7 @@ public class TestMessageFormat extends TestFmwk {
         Date date = new GregorianCalendar(2021, Calendar.NOVEMBER, 23, 16, 42, 55).getTime();
 
         doTheRealDateTimeSkeletonTesting(date, "{0,date,::MMMMd}", ULocale.ENGLISH, "November 23");
-        doTheRealDateTimeSkeletonTesting(date, "{0,date,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021, 4:42 PM");
+        doTheRealDateTimeSkeletonTesting(date, "{0,date,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021 at 4:42 PM");
         doTheRealDateTimeSkeletonTesting(date, "{0,date,   ::   yMMMMd   }", ULocale.ENGLISH, "November 23, 2021");
         doTheRealDateTimeSkeletonTesting(date, "{0,date,::yMMMMd}", ULocale.FRENCH, "23 novembre 2021");
         doTheRealDateTimeSkeletonTesting(date, "Expiration: {0,date,::yMMM}!", ULocale.ENGLISH, "Expiration: Nov 2021!");
@@ -2152,7 +2152,7 @@ public class TestMessageFormat extends TestFmwk {
         Date date = new GregorianCalendar(2021, Calendar.NOVEMBER, 23, 16, 42, 55).getTime();
 
         doTheRealDateTimeSkeletonTesting(date, "{0,time,::MMMMd}", ULocale.ENGLISH, "November 23");
-        doTheRealDateTimeSkeletonTesting(date, "{0,time,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021, 4:42 PM");
+        doTheRealDateTimeSkeletonTesting(date, "{0,time,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021 at 4:42 PM");
         doTheRealDateTimeSkeletonTesting(date, "{0,time,   ::   yMMMMd   }", ULocale.ENGLISH, "November 23, 2021");
         doTheRealDateTimeSkeletonTesting(date, "{0,time,::yMMMMd}", ULocale.FRENCH, "23 novembre 2021");
         doTheRealDateTimeSkeletonTesting(date, "Expiration: {0,time,::yMMM}!", ULocale.ENGLISH, "Expiration: Nov 2021!");