-
CSR
-
Resolution: Approved
-
P4
-
None
-
behavioral
-
low
-
Support for new String patterns should have no risk unless users are currently overriding any pattern related methods and implementing logic that exactly match the new String patterns proposed in this CSR which is unlikely.
-
Java API
-
SE
Summary
Allow MessageFormat to support String patterns for CompactNumberFormat
, ListFormat
, and DateTimeFormatter
.
Problem
Note: DateTimeFormatter is not a Format subclass, it does however, have the method toFormat() which converts itself to an internal Format subclass.
Background
java.text.MessageFormat
is an i18n class designed to support sub-formats. MessageFormat
provides setter methods, which allow a MessageFormat
to set sub-formats as long as they are instances of Format
.
Additionally, MessageFormat
provides a pattern syntax to allow MessageFormat
to be created via a String. There are a number of built-in patterns, for example, "number,integer" which uses a subformat equal to NumberFormat.getIntegerInstance(getLocale())
.
This CSR is focused on the FormatElement portion of the MessageFormat
pattern, which is composed of { ArgumentIndex , FormatType , FormatStyle }. Where "number" would be a FormatType and "integer" a FormatStyle.
Motivation
Although MessageFormat
supports CompactNumberFormat,
ListFormat
, and DateTimeFormatter
via the setter methods, it does not provide any support for the mentioned classes via the pattern syntax. This is not only inconsistent (as the other Format subclasses are supported), but causes a lack of functionality for the methods: MessageFormat::applyPattern
and MessageFormat::toPattern
regarding the mentioned subformat classes.
Although it is possible to create a DateTimeFormatter
and invoke toFormat()
to get a Format adapter class to use with MessageFormat
, it is not easily apparent, and the addition of these patterns help to provide cohesion between java.time and i18n.
Solution
CompactNumberFormat
Add two FormatStyle(s): "*compact_short*" and "*compact_long*" for the FormatType: "number".
ListFormat
Introduce a new FormatType: "list" with 3 associated FormatStyle(s): "(none)", "or", and "unit"
DateTimeFormatter
Introduce three new related FormatTypes: "dtf_time","dtf_date", and "dtf_datetime", which share the same FormatStyle(s) as the existing "time" and "date" FormatTypes. These types allow for the support of java.time.format.DateTimeFormatter via String patterns.
Additionally, provide support for all of the DateTimeFormatter static constants, by supporting the field names as FormatTypes. This allows for the standardized ISO formats (and RFC1123) to be used for formatting java.time objects.
The specification is re-organized and updated to not only make room for the new additions, but deliver information in a more concise manner.
Limitations
CompactNumberFormat
and ListFormat
cannot support subFormat patterns as the classes do not support a String pattern syntax. They can still be represented as a pre-defined type when MessageFormat::toPattern
is invoked.
DateTimeFormatter
in both cases cannot be represented when MessageFormat::toPattern
is invoked, as the class and its wrapper Format class both do not implement equals()
which is required when checking if a subformat is equal to a pattern.
These limitations exist regardless of the proposed improved pattern syntax.
The MessageFormat::toPattern will be updated with an implSpec tag to address these limitations.
Specification
As a result of this change, there was additional re-organization and updates to the existing class description. Those changes were independent of this fix, and omitted for brevity.
In the class description, update the FormatType and FormatStyle
- * <i>FormatType: one of </i>
- * number date time choice
+ * <i>FormatType:</i>
+ * number
+ * dtf_date
+ * dtf_time
+ * dtf_datetime
+ * <i>pre-defined DateTimeFormatter(s)</i>
+ * date
+ * time
+ * choice
+ * list
*
* <i>FormatStyle:</i>
* short
...
* integer
* currency
* percent
+ * compact_short
+ * compact_long
+ * or
+ * unit
* <i>SubformatPattern</i>
Clarify the case rules of allowed patterns
- * table shows how the values map to {@code Format} instances. Combinations not
- * shown in the table are illegal. A <i>SubformatPattern</i> must
+ * table shows how the values map to {@code Format} instances. These values
+ * are case-insensitive when passed to {@link #applyPattern(String)}. Combinations
+ * not shown in the table are illegal. A <i>SubformatPattern</i> must
* be a valid pattern string for the {@code Format} subclass used.
Add the number,compact styles
- * <th scope="row" style="font-weight:normal" rowspan=5>{@code number}
+ * <th scope="row" style="font-weight:normal" rowspan=7>{@code number}
* <th scope="row" style="font-weight:normal"><i>(none)</i>
...
+ * <th scope="row" style="font-weight:normal">{@code compact_short}
+ * <td>{@link NumberFormat#getCompactNumberInstance(Locale, NumberFormat.Style) NumberFormat.getCompactNumberInstance}{@code (getLocale(),} {@link NumberFormat.Style#SHORT})
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code compact_long}
+ * <td>{@link NumberFormat#getCompactNumberInstance(Locale, NumberFormat.Style) NumberFormat.getCompactNumberInstance}{@code (getLocale(),} {@link NumberFormat.Style#LONG})
+ * <tr>
Add the dtf_date, dtf_time, dtf_datetime and pre-defined DateTimeFormatter types
+ * <th scope="row" style="font-weight:normal" rowspan=6>{@code dtf_date}
+ * <th scope="row" style="font-weight:normal"><i>(none)</i>
+ * <td>{@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code short}
+ * <td>{@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#SHORT}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code medium}
+ * <td>{@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code long}
+ * <td>{@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#LONG}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code full}
+ * <td>{@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#FULL}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal"><i>SubformatPattern</i>
+ * <td>{@link DateTimeFormatter#ofPattern(String, Locale) DateTimeFormatter.ofPattern}{@code (subformatPattern, getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal" rowspan=6>{@code dtf_time}
+ * <th scope="row" style="font-weight:normal"><i>(none)</i>
+ * <td>{@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code short}
+ * <td>{@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#SHORT}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code medium}
+ * <td>{@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code long}
+ * <td>{@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#LONG}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code full}
+ * <td>{@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#FULL}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal"><i>SubformatPattern</i>
+ * <td>{@link DateTimeFormatter#ofPattern(String, Locale) DateTimeFormatter.ofPattern}{@code (subformatPattern, getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal" rowspan=6>{@code dtf_datetime}
+ * <th scope="row" style="font-weight:normal"><i>(none)</i>
+ * <td>{@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code short}
+ * <td>{@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#SHORT}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code medium}
+ * <td>{@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code long}
+ * <td>{@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#LONG}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code full}
+ * <td>{@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#FULL}{@code ).withLocale(getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal"><i>SubformatPattern</i>
+ * <td>{@link DateTimeFormatter#ofPattern(String, Locale) DateTimeFormatter.ofPattern}{@code (subformatPattern, getLocale())}
+ * <tr>
+ * <th scope="row" style="font-weight:normal" rowspan=1>{@code pre-defined DateTimeFormatter(s)}
+ * <th scope="row" style="font-weight:normal"><i>(none)</i>
+ * <td>The {@code pre-defined DateTimeFormatter(s)} are used as a {@code FormatType} :
+ * {@link DateTimeFormatter#BASIC_ISO_DATE BASIC_ISO_DATE},
+ * {@link DateTimeFormatter#ISO_LOCAL_DATE ISO_LOCAL_DATE},
+ * {@link DateTimeFormatter#ISO_OFFSET_DATE ISO_OFFSET_DATE},
+ * {@link DateTimeFormatter#ISO_DATE ISO_DATE},
+ * {@link DateTimeFormatter#ISO_LOCAL_TIME ISO_LOCAL_TIME},
+ * {@link DateTimeFormatter#ISO_OFFSET_TIME ISO_OFFSET_TIME},
+ * {@link DateTimeFormatter#ISO_TIME ISO_TIME},
+ * {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME ISO_LOCAL_DATE_TIME},
+ * {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO_OFFSET_DATE_TIME},
+ * {@link DateTimeFormatter#ISO_ZONED_DATE_TIME ISO_ZONED_DATE_TIME},
+ * {@link DateTimeFormatter#ISO_DATE_TIME ISO_DATE_TIME},
+ * {@link DateTimeFormatter#ISO_ORDINAL_DATE ISO_ORDINAL_DATE},
+ * {@link DateTimeFormatter#ISO_WEEK_DATE ISO_WEEK_DATE},
+ * {@link DateTimeFormatter#ISO_INSTANT ISO_INSTANT},
+ * {@link DateTimeFormatter#RFC_1123_DATE_TIME RFC_1123_DATE_TIME}
Add the list types
+ * <tr>
+ * <th scope="row" style="font-weight:normal" rowspan=3>{@code list}
+ * <th scope="row" style="font-weight:normal"><i>(none)</i>
+ * <td>{@link ListFormat#getInstance(Locale, ListFormat.Type, ListFormat.Style) ListFormat.getInstance}{@code (getLocale()}, {@link ListFormat.Type#STANDARD}, {@link ListFormat.Style#FULL})
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code or}
+ * <td>{@link ListFormat#getInstance(Locale, ListFormat.Type, ListFormat.Style) ListFormat.getInstance}{@code (getLocale()}, {@link ListFormat.Type#OR}, {@link ListFormat.Style#FULL})
+ * <tr>
+ * <th scope="row" style="font-weight:normal">{@code unit}
+ * <td>{@link ListFormat#getInstance(Locale, ListFormat.Type, ListFormat.Style) ListFormat.getInstance}{@code (getLocale()}, {@link ListFormat.Type#UNIT}, {@link ListFormat.Style#FULL}}
Add a section to clarify formatting java.time objects versus java.util.Date
+ * <h2>Formatting Date and Time</h2>
+ *
+ * MessageFormat provides patterns that support the date/time formatters in the
+ * {@link java.time.format} and {@link java.text} packages. Consider the following three examples,
+ * with a date of 11/16/2023:
+ *
+ * <p>1) a <i>date</i> {@code FormatType} with a <i>full</i> {@code FormatStyle},
+ * {@snippet lang=java :
+ * Object[] arg = {new GregorianCalendar(2023, Calendar.NOVEMBER, 16).getTime()};
+ * var fmt = new MessageFormat("The date was {0,date,full}");
+ * fmt.format(arg); // returns "The date was Thursday, November 16, 2023"
+ * }
+ *
+ * <p>2) a <i>dtf_date</i> {@code FormatType} with a <i>full</i> {@code FormatStyle},
+ * {@snippet lang=java :
+ * Object[] arg = {LocalDate.of(2023, 11, 16)};
+ * var fmt = new MessageFormat("The date was {0,dtf_date,full}");
+ * fmt.format(arg); // returns "The date was Thursday, November 16, 2023"
+ * }
+ *
+ * <p>3) an <i>ISO_LOCAL_DATE</i> {@code FormatType},
+ * {@snippet lang=java :
+ * Object[] arg = {LocalDate.of(2023, 11, 16)};
+ * var fmt = new MessageFormat("The date was {0,ISO_LOCAL_DATE}");
+ * fmt.format(arg); // returns "The date was 2023-11-16"
+ * }
+ *
Update the specification of toPattern()
/**
- * Returns a pattern representing the current state of the message format.
+ * {@return a String pattern adhering to the {@link ##patterns patterns section} that
+ * represents the current state of this {@code MessageFormat}}
+ *
* The string is constructed from internal information and therefore
* does not necessarily equal the previously applied pattern.
*
* @implSpec The implementation in {@link MessageFormat} returns a
* string that, when passed to a {@code MessageFormat()} constructor
* or {@link #applyPattern applyPattern()}, produces an instance that
- * is semantically equivalent to this instance.
- *
- * @return a pattern representing the current state of the message format
+ * is semantically equivalent to this instance. If a subformat cannot be
+ * converted to a String pattern, the {@code FormatType} and {@code FormatStyle}
+ * will be omitted from the {@code FormatElement}.
*/
public String toPattern() {
- csr of
-
JDK-8318761 MessageFormat pattern support for CompactNumberFormat, ListFormat, and DateTimeFormatter
- Resolved