javaandroidsimpledateformat

Outputting the day period based on locale


I want to format a date, which should be localized and includes the "day period".

"Day period" meaning not am/pm but "afternoon", "early morning" etc.

A formatted date based on german locale with the 12 hour setting enabled should look like 03.08.18, 9:01 abends, "abends" meaning evening.

The same example should be 3/8/18 9:01 de la noche in spanish.


Why i think it should be possible:

A: Because the WhatsApp Android app formats dates like this

B: Because the Java 10 source code includes the following (From the german translation file):

<dayPeriods>
<dayPeriodContext type="format">
    <dayPeriodWidth type="wide">
        <dayPeriod type="afternoon">nachmittags</dayPeriod>
        <dayPeriod type="am">vorm.</dayPeriod>
        <dayPeriod type="earlyMorning">morgens</dayPeriod>
        <dayPeriod type="evening">abends</dayPeriod>
        <dayPeriod type="morning">vormittags</dayPeriod>
        <dayPeriod type="night">nachts</dayPeriod>
        <dayPeriod type="noon">Mittag</dayPeriod>
        <dayPeriod type="pm">nachm.</dayPeriod>
    </dayPeriodWidth>
</dayPeriodContext>
...
</dayPeriods>


I tried: (current is the locale)

DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, current);

String formattedDate = df.format(System.currentTimeMillis());

Output: 08.12.18 7:24 nachm or 8/12/18 7:54 p. m.


The following code has the same output:

DateFormat abc = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, current);

abc.format(System.currentTimeMillis());


I also tried the DateTimeFormatterBuilder (With all of the AMPM_OF_DAY versions):

DateTimeFormatter test = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT);

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
                .append(test)
                .appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
                .appendText(ChronoField.AMPM_OF_DAY, TextStyle.NARROW)
                .appendText(ChronoField.AMPM_OF_DAY, TextStyle.FULL)
                .appendText(ChronoField.AMPM_OF_DAY, TextStyle.FULL_STANDALONE)
                .toFormatter(current);

ZonedDateTime date = ZonedDateTime.now();
date.format(fmt);

Output: 8 dic. 2018 19:54p. m.p. m.p. m.11


Is there any way i can output the date this way?

Thanks


Solution

  • There are only two libraries which support day periods, namely ICU4J (partially incorporated in Android) and my lib Time4A. Other formatters like java.text.DateFormat or java.time.format.DateTimeFormatter have no support.

    Example in Time4A for the German locale (here with a bridge to the old-fashioned type java.util.Date):

     ChronoFormatter<java.util.Date> f =
          ChronoFormatter.ofPattern(
              "dd.MM.uuuu, h:mm BBBB",
              PatternType.CLDR,
              Locale.GERMAN,
              Moment.axis(TemporalType.JAVA_UTIL_DATE))
          .withStdTimezone();
     System.out.println(f.format(new java.util.Date()));
    

    example output => "11.12.2018, 6:03 morgens"

    It uses the special pattern symbol "B" defined by LDML-syntax of CLDR-data (managed by Unicode consortium). The day partition rules are also taken from CLDR data and look for German like:

    # day-period-rules
    T0000=night1
    T0500=morning1
    T1000=morning2
    T1200=afternoon1
    T1300=afternoon2
    T1800=evening1
    
    # day-period-translations
    P(a)_morning1=morgens
    P(a)_morning2=vormittags
    P(a)_afternoon1=mittags
    P(a)_afternoon2=nachmittags
    P(a)_evening1=abends
    P(a)_night1=nachts
    

    If you don't agree with these data then you might construct your own customized formatter via the builder pattern.

    Note about ICU4J: I have not tested that lib but maybe its class com.ibm.icu.text.SimpleDateFormat supports the pattern symbol B, too (but then the javadoc is outdated).

    Another note:

    If you like style-based formatting without specifying a concrete pattern then you might go this way using a composition of date and time part:

        Locale loc = Locale.US;
    
        // our concrete values to be formatted
        Moment moment = SystemClock.currentMoment();
        PlainDate calendarDate = moment.toLocalTimestamp().getCalendarDate();
    
        // get the localized clock pattern
        Map<String, String> textForms = CalendarText.getIsoInstance(loc).getTextForms();
        String pattern = 
                textForms.containsKey("F_Bhm") ? textForms.get("F_Bhm") : "h:mm B";
    
        // construct the style-based date formatter
        ChronoFormatter<PlainDate> f1 = 
                ChronoFormatter.ofDateStyle(DisplayMode.SHORT, loc);
    
        // construct the time formatter
        ChronoFormatter<Moment> f2 = ChronoFormatter.ofMomentPattern(
            pattern,
            PatternType.CLDR,
            loc,
            Timezone.ofSystem().getID());
    
        System.out.println(f1.format(calendarDate) + " " + f2.format(moment));
        // example output => "12/11/18 6:18 in the morning"