JDK-8254629 : Day periods support
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.time
  • Priority: P4
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 16
  • Submitted: 2020-10-12
  • Updated: 2020-11-16
  • Resolved: 2020-11-16
Related Reports
CSR :  

Support `Day Periods` defined in CLDR.


Currently, the only day periods supported in the JDK are am/pm. In CLDR, there are other types of day periods, such as "midnight" or "noon" which have universally fixed interpretation, or  "in the morning" in which the period definition differs across locales. Since version 33 of CLDR, some locales have introduced these day periods, which JDK has yet to be able to support.


Introduce a new pattern character "`B`" that represents the `period-of-day`, which can be used in `java.time.DateTimeFormatter(Builder)` classes. A new method that corresponds to this pattern character will also be provided.


Add the following new pattern character definition in `java.time.format.DateTimeFormatter` class description:

    @@ -300,6 +300,7 @@
      *   <tr><th scope="row">F</th>       <td>day-of-week-in-month</td>        <td>number</td>            <td>3</td>
      *   <tr><th scope="row">a</th>       <td>am-pm-of-day</td>                <td>text</td>              <td>PM</td>
    + *   <tr><th scope="row">B</th>       <td>period-of-day</td>               <td>text</td>              <td>in the morning</td>
      *   <tr><th scope="row">h</th>       <td>clock-hour-of-am-pm (1-12)</td>  <td>number</td>            <td>12</td>
      *   <tr><th scope="row">K</th>       <td>hour-of-am-pm (0-11)</td>        <td>number</td>            <td>0</td>
      *   <tr><th scope="row">k</th>       <td>clock-hour-of-day (1-24)</td>    <td>number</td>            <td>24</td>

Similarly, add the following new pattern character definition in `java.time.format.DateTimeFormatterBuilder#appendPattern(pattern)` method:
@@ -1508,10 +1546,11 @@
      *   E       day-of-week                 text              Tue; Tuesday; T
      *   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
      *   F       day-of-week-in-month        number            3
      *   a       am-pm-of-day                text              PM
+     *   B       period-of-day               text              in the morning
      *   h       clock-hour-of-am-pm (1-12)  number            12
      *   K       hour-of-am-pm (0-11)        number            0
      *   k       clock-hour-of-day (1-24)    number            24
      *   H       hour-of-day (0-23)          number            0
@@ -1674,10 +1713,19 @@
      *    ZZZ     3      appendOffset("+HHMM","+0000")
      *    ZZZZ    4      appendLocalizedOffset(TextStyle.FULL)
      *    ZZZZZ   5      appendOffset("+HH:MM:ss","Z")
      * </pre>
      * <p>
+     * <b>Day periods</b>: Pattern letters to output a day period.
+     * <pre>
+     *  Pattern  Count  Equivalent builder methods
+     *  -------  -----  --------------------------
+     *    B       1      appendDayPeriodText(TextStyle.SHORT)
+     *    BBBB    4      appendDayPeriodText(TextStyle.FULL)
+     *    BBBBB   5      appendDayPeriodText(TextStyle.NARROW)
+     * </pre>
+     * <p>
      * <b>Modifiers</b>: Pattern letters that modify the rest of the pattern:
      * <pre>
      *  Pattern  Count  Equivalent builder methods
      *  -------  -----  --------------------------
      *    [       1      optionalStart()
Add a new method in `java.time.format.DateTimeFormatterBuilder` class:

     * Appends the day period text to the formatter.
     * <p>
     * This appends an instruction to format/parse the textual name of the day period
     * to the builder. Day periods are defined in LDML's
     * <a href="https://unicode.org/reports/tr35/tr35-dates.html#dayPeriods">"day periods"
     * </a> element.
     * <p>
     * During formatting, the day period is obtained from {@code HOUR_OF_DAY}, and
     * optionally {@code MINUTE_OF_HOUR} if exist. It will be mapped to a day period
     * type defined in LDML, such as "morning1" and then it will be translated into
     * text. Mapping to a day period type and its translation both depend on the
     * locale in the formatter.
     * <p>
     * During parsing, the text will be parsed into a day period type first. Then
     * the parsed day period is combined with other fields to make a {@code LocalTime} in
     * the resolving phase. If the {@code HOUR_OF_AMPM} field is present, it is combined
     * with the day period to make {@code HOUR_OF_DAY} taking into account any
     * {@code MINUTE_OF_HOUR} value. If {@code HOUR_OF_DAY} is present, it is validated
     * against the day period taking into account any {@code MINUTE_OF_HOUR} value. If a
     * day period is present without {@code HOUR_OF_DAY}, {@code MINUTE_OF_HOUR},
     * {@code SECOND_OF_MINUTE} and {@code NANO_OF_SECOND} then the midpoint of the
     * day period is set as the time in {@code SMART} and {@code LENIENT} mode.
     * For example, if the parsed day period type is "night1" and the period defined
     * for it in the formatter locale is from 21:00 to 06:00, then it results in
     * the {@code LocalTime} of 01:30.
     * If the resolved time conflicts with the day period, {@code DateTimeException} is
     * thrown in {@code STRICT} and {@code SMART} mode. In {@code LENIENT} mode, no
     * exception is thrown and the parsed day period is ignored.
     * <p>
     * The "midnight" type allows both "00:00" as the start-of-day and "24:00" as the
     * end-of-day, as long as they are valid with the resolved hour field.
     * @param style the text style to use, not null
     * @return this, for chaining, not null
     * @since 16
    public DateTimeFormatterBuilder appendDayPeriodText(TextStyle style) 

Add the following sentence to the field description of `java.time.temporal.ChronoField#AMPM_OF_DAY`:

    @@ -333,10 +333,9 @@
          * When parsing this field it behaves equivalent to the following:
          * The value is validated from 0 to 1 in strict and smart mode.
          * In lenient mode the value is not validated. It is combined with
    -     * {@code HOUR_OF_AMPM} to form {@code HOUR_OF_DAY} by multiplying
    -     * the {@code AMPM_OF_DAY} value by 12.
    +     * {@code HOUR_OF_AMPM} (if not present, it defaults to '6') to form
    +     * {@code HOUR_OF_DAY} by multiplying the {@code AMPM_OF_DAY} value
    +     * by 12.

Moving back to Approved.

Re-approving updated request.

Moving to Approved.

Thanks again for the review. I have modified the method spec, hopefully it will capture the idea. My intention was to honor the parsed hour-of-day/minute-of-hour field as they are, and if they are not available, midpoint of the day period will be set to those time fields.

This new approach looks good. I think the idea of the field is that "6 in the morning" can be parsed to 06:00 (not 18:00) which I'm not sure is captured by the proposed spec. It may be necessary to implement this and see what is or is not possible before finishing the spec.

Thank you for the suggestion. I will reconsider and confine the support within the formatter.

I do wonder if ChronoField.FLEXIBLE_PERIOD_OF_DAY is necessary. The value has no intrinsic meaning outside of formatting unless you are planning on adding extra constants, which I don't think you should. I'd suggest that this change could be handled via additional append methods on `DateTimeFormatterBuilder`, like ` appendLocalizedOffset()`