How do you format the day of the month to say "11th", "21st" or "23rd" (ordinal indicator)?

Viewed 180791

I know this will give me the day of the month as a number (11, 21, 23):

SimpleDateFormat formatDayOfMonth = new SimpleDateFormat("d");

But how do you format the day of the month to include an ordinal indicator, say 11th, 21st or 23rd?

23 Answers

I should like to contribute the modern answer. The SimpleDateFormat class was OK to use when the question was asked 8 years ago, but you should avoid it now as it is not only long outdated, but also notoriously troublesome. Use java.time instead.

Edit

DateTimeFormatterBuilder.appendText(TemporalField, Map<Long, String>) is great for this purpose. Using it we build a formatter that does the work for us:

    Map<Long, String> ordinalNumbers = new HashMap<>(42);
    ordinalNumbers.put(1L, "1st");
    ordinalNumbers.put(2L, "2nd");
    ordinalNumbers.put(3L, "3rd");
    ordinalNumbers.put(21L, "21st");
    ordinalNumbers.put(22L, "22nd");
    ordinalNumbers.put(23L, "23rd");
    ordinalNumbers.put(31L, "31st");
    for (long d = 1; d <= 31; d++) {
        ordinalNumbers.putIfAbsent(d, "" + d + "th");
    }

    DateTimeFormatter dayOfMonthFormatter = new DateTimeFormatterBuilder()
            .appendText(ChronoField.DAY_OF_MONTH, ordinalNumbers)
            .appendPattern(" MMMM")
            .toFormatter();

    LocalDate date = LocalDate.of(2018, Month.AUGUST, 30);
    for (int i = 0; i < 6; i++) {
        System.out.println(date.format(dayOfMonthFormatter));
        date = date.plusDays(1);
    }

The output from this snippet is:

30th August
31st August
1st September
2nd September
3rd September
4th September

Old answer

This code is shorter, but IMHO not so elegant.

    // ordinal indicators by numbers (1-based, cell 0 is wasted)
    String[] ordinalIndicators = new String[31 + 1];
    Arrays.fill(ordinalIndicators, 1, ordinalIndicators.length, "th");
    ordinalIndicators[1] = ordinalIndicators[21] = ordinalIndicators[31] = "st";
    ordinalIndicators[2] = ordinalIndicators[22] = "nd";
    ordinalIndicators[3] = ordinalIndicators[23] = "rd";

    DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d");

    LocalDate today = LocalDate.now(ZoneId.of("America/Menominee")).plusWeeks(1);
    System.out.println(today.format(dayOfMonthFormatter) 
                        + ordinalIndicators[today.getDayOfMonth()]);

Running this snippet just now I got

23rd

One of the many features of java.time is that it’s straightforward and reliable to get the day of month as an int, which is obviously needed for picking the right suffix from the table.

I recommend you write a unit test too.

PS A similar formatter can also be used for parsing a date string containing ordinal numbers like 1st, 2nd, etc. That was done in this question: Java - Parse date with optional seconds.

Link: Oracle tutorial: Date Time explaining how to use java.time.

I can't be satisfied by the answers calling for a English-only solution based on manual formats. I've been looking for a proper solution for a while now and I finally found it.

You should be using RuleBasedNumberFormat. It works perfectly and it's respectful of the Locale.

RuleBasedNumberFormat in ICU library

I appreciated the link to the ICU project's library from @Pierre-Olivier Dybman (http://www.icu-project.org/apiref/icu4j/com/ibm/icu/text/RuleBasedNumberFormat.html), however still had to figure out how to use it, so an example of the RuleBasedNumberFormat usage is below.

It will only format single number rather than the whole date, so you will need to build a combined string if looking for a date in format: Monday 3rd February, for example.

The below code sets up the RuleBasedNumberFormat as an Ordinal format for a given Locale, creates a java.time ZonedDateTime, and then formats the number with its ordinal into a string.

RuleBasedNumberFormat numOrdinalFormat = new RuleBasedNumberFormat(Locale.UK,
    RuleBasedNumberFormat.ORDINAL);
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Pacific/Auckland"));

String dayNumAndOrdinal = numOrdinalFormat.format(zdt.toLocalDate().getDayOfMonth());

Example output:

3rd

Or

4th

etc.

Using the new java.time package and the newer Java switch statement, the following easily allows an ordinal to be placed on a day of the month. One drawback is that this does not lend itself to canned formats specified in the DateFormatter class.

Simply create a day of some format but include %s%s to add the day and ordinal later.

ZonedDateTime ldt = ZonedDateTime.now();
String format = ldt.format(DateTimeFormatter
        .ofPattern("EEEE, MMMM '%s%s,' yyyy hh:mm:ss a zzz"));

Now pass the day of the week and the just formatted date to a helper method to add the ordinal day.


int day = ldt.getDayOfMonth();
System.out.println(applyOrdinalDaySuffix(format, day));

Prints

Tuesday, October 6th, 2020 11:38:23 AM EDT

Here is the helper method.

Using the Java 14 switch expressions makes getting the ordinal very easy.

public static String applyOrdinalDaySuffix(String format,
        int day) {
    if (day < 1 || day > 31)
        throw new IllegalArgumentException(
                String.format("Bad day of month (%s)", day));
    String ord = switch (day) {
        case 1, 21, 31 -> "st";
        case 2, 22 -> "nd";
        case 3, 23 -> "rd";
        default -> "th";
    };
    
    return String.format(format, day, ord);
}

I wrote my self a helper method to get patterns for this.

public static String getPattern(int month) {
    String first = "MMMM dd";
    String last = ", yyyy";
    String pos = (month == 1 || month == 21 || month == 31) ? "'st'" : (month == 2 || month == 22) ? "'nd'" : (month == 3 || month == 23) ? "'rd'" : "'th'";
    return first + pos + last;
}

and then we can call it as

LocalDate localDate = LocalDate.now();//For reference
int month = localDate.getDayOfMonth();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(getPattern(month));
String date = localDate.format(formatter);
System.out.println(date);

the output is

December 12th, 2018

Try below function:

public static String getFormattedDate(Date date) 
{
  Calendar cal = Calendar.getInstance();
  cal.setTime(date);
  //2nd of march 2015
  int day = cal.get(Calendar.DATE);
  if (!((day > 10) && (day < 19)))
   switch (day % 10) {
    case 1:
     return new SimpleDateFormat("d'st' 'of' MMMM yyyy").format(date);
    case 2:
     return new SimpleDateFormat("d'nd' 'of' MMMM yyyy").format(date);
    case 3:
     return new SimpleDateFormat("d'rd' 'of' MMMM yyyy").format(date);
    default:
     return new SimpleDateFormat("d'th' 'of' MMMM yyyy").format(date);
   }
  return new SimpleDateFormat("d'th' 'of' MMMM yyyy").format(date);
}

Here is an approach that updates a DateTimeFormatter pattern with the correct suffix literal if it finds the pattern d'00', e.g. for day of month 1 it would be replaced with d'st'. Once the pattern has been updated it can then just be fed into the DateTimeFormatter to do the rest.

private static String[] suffixes = {"th", "st", "nd", "rd"};

private static String updatePatternWithDayOfMonthSuffix(TemporalAccessor temporal, String pattern) {
    String newPattern = pattern;
    // Check for pattern `d'00'`.
    if (pattern.matches(".*[d]'00'.*")) {
        int dayOfMonth = temporal.get(ChronoField.DAY_OF_MONTH);
        int relevantDigits = dayOfMonth < 30 ? dayOfMonth % 20 : dayOfMonth % 30;
        String suffix = suffixes[relevantDigits <= 3 ? relevantDigits : 0];
        newPattern = pattern.replaceAll("[d]'00'", "d'" + suffix + "'");
    }

    return newPattern;
}

It does require that the original pattern is updated just prior to every formatting call, e.g.

public static String format(TemporalAccessor temporal, String pattern) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(updatePatternWithDayOfMonthSuffix(temporal, pattern));
    return formatter.format(temporal);
}

So this is useful if the formatting pattern is defined outside of Java code, e.g. a template, where as if you can define the pattern in Java then the answer by @OleV.V. might be more appropriate

public static String getReadableDate(final int date){
    String suffix = "th";
    switch (date){
        case 1:
        case 21:
        case 31:
            suffix = "st";
            break;
        case 2:
        case 22:
            suffix = "nd";
            break;
        case 3:
        case 23:
            suffix = "rd";
            break;
    }
    return date + suffix;
}

Here is my answer:

public String getOrdinal(int day) { 
    String ordinal; 
    switch (day % 20) { 
        case 1: 
            ordinal = "st"; 
            break; 
        case 2: 
            ordinal = "nd"; 
            break; 
        case 3: 
            ordinal = "rd"; 
            break; 
        default: 
            ordinal = day > 30 > "st" : "th"; 
    } 
    return ordinal; 
} 

Just do the modulo with 20 and it will work for all dates. To get today's day you can use LocalDate.now().getDayOfMonth(). or pass any day like this

LocalDate.getDayOfMonth()

For Kotlin try this

fun Int.ordinalAbbrev() =
        if (this % 100 / 10 == 1) "th"
        else when (this % 10) { 1 -> "st" 2 -> "nd" 3 -> "rd" else -> "th" }

it takes int value and returns like this '3rd' '1st' '11th' '2nd'. So you can use it for date format also.

Usage

fun getFormatedDate(date: String): String {
        date.let {
            try {
                val parser = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
                val formatter = SimpleDateFormat("dd MMMM", Locale.getDefault())
                val dateArray = formatter.format(parser.parse(it)).split(" ").toTypedArray()
                val formatedDate = String.format(
                    "${dateArray[0]}${
                        dateArray[0].toInt().ordinalAbbrev()
                    } ${dateArray[1]}"
                )

                return formatedDate
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        return date
    }
Related