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

后端 未结 20 986
逝去的感伤
逝去的感伤 2020-11-22 02:41

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

SimpleDateFormat formatDayOfMonth = new Simple         


        
相关标签:
20条回答
  • 2020-11-22 02:57

    Question is little old. As this question is very noisy so posting what I did solved with static method as a util. Just copy, paste and use it!

     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);
        }
    

    For testing purose

    Example: calling it from main method!

    Date date = new Date();
            Calendar cal=Calendar.getInstance();
            cal.setTime(date);
            for(int i=0;i<32;i++){
              System.out.println(getFormattedDate(cal.getTime()));
              cal.set(Calendar.DATE,(cal.getTime().getDate()+1));
            }
    

    Output:

    22nd of February 2018
    23rd of February 2018
    24th of February 2018
    25th of February 2018
    26th of February 2018
    27th of February 2018
    28th of February 2018
    1st of March 2018
    2nd of March 2018
    3rd of March 2018
    4th of March 2018
    5th of March 2018
    6th of March 2018
    7th of March 2018
    8th of March 2018
    9th of March 2018
    10th of March 2018
    11th of March 2018
    12th of March 2018
    13th of March 2018
    14th of March 2018
    15th of March 2018
    16th of March 2018
    17th of March 2018
    18th of March 2018
    19th of March 2018
    20th of March 2018
    21st of March 2018
    22nd of March 2018
    23rd of March 2018
    24th of March 2018
    25th of March 2018
    
    0 讨论(0)
  • 2020-11-22 02:57

    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

    0 讨论(0)
  • 2020-11-22 02:58

    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.

    0 讨论(0)
  • 2020-11-22 02:59

    There is nothing in JDK to do this.

      static String[] suffixes =
      //    0     1     2     3     4     5     6     7     8     9
         { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th",
      //    10    11    12    13    14    15    16    17    18    19
           "th", "th", "th", "th", "th", "th", "th", "th", "th", "th",
      //    20    21    22    23    24    25    26    27    28    29
           "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th",
      //    30    31
           "th", "st" };
    
     Date date = new Date();
     SimpleDateFormat formatDayOfMonth  = new SimpleDateFormat("d");
     int day = Integer.parseInt(formatDateOfMonth.format(date));
     String dayStr = day + suffixes[day];
    

    Or using Calendar:

     Calendar c = Calendar.getInstance();
     c.setTime(date);
     int day = c.get(Calendar.DAY_OF_MONTH);
     String dayStr = day + suffixes[day];
    

    Per comments by @thorbjørn-ravn-andersen, a table like this can be helpful when localizing:

      static String[] suffixes =
         {  "0th",  "1st",  "2nd",  "3rd",  "4th",  "5th",  "6th",  "7th",  "8th",  "9th",
           "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th",
           "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th",
           "30th", "31st" };
    
    0 讨论(0)
  • 2020-11-22 03:03

    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);
    }
    
    0 讨论(0)
  • 2020-11-22 03:05

    If you try to be aware of i18n the solution get even more complicated.

    The problem is that in other languages the suffix may depend not only on the number itself, but also on the noun it counts. For example in Russian it would be "2-ой день", but "2-ая неделя" (these mean "2nd day", but "2nd week"). This is not apply if we formatting only days, but in a bit more generic case you should be aware of complexity.

    I think nice solution (I didn't have time to actually implement) would be to extend SimpleDateFormetter to apply Locale-aware MessageFormat before passing to the parent class. This way you would be able to support let say for March formats %M to get "3-rd", %MM to get "03-rd" and %MMM to get "third". From outside this class looks like regular SimpleDateFormatter, but supports more formats. Also if this pattern would be by mistake applied by regular SimpleDateFormetter the result would be incorrectly formatted, but still readable.

    0 讨论(0)
提交回复
热议问题