Как вы форматируете день месяца, чтобы сказать «11-й», «21-й» или «23-й» (порядковый индикатор)?


146

Я знаю , что это даст мне день месяца как число ( 11, 21, 23):

SimpleDateFormat formatDayOfMonth = new SimpleDateFormat("d");

Но как вы форматируете день месяца, чтобы включить порядковый индикатор , скажем 11th, 21stили 23rd?


13
Для справки они называются порядковыми номерами - en.wikipedia.org/wiki/Ordinal_number_(linguistics) .
ocodo 25.10.10

5
Просто запомните, что все, что создает ответ вместо поиска полного ответа в таблице, практически невозможно локализовать на других языках.
Турбьёрн Равн Андерсен

2
Ответ как-то неверно посмотри на мой ответ плз.
J888

2
Современный комментарий: я рекомендую вам избегать SimpleDateFormatзанятий. Он не только давно устарел, но и печально известен своими хлопотами. Сегодня у нас намного лучше в java.timeсовременном API даты и времени Java и его DateTimeFormatter.
Оле В.В.

Посмотрите на API чисел ( math.tools/api/numbers ). Он поддерживает порядковый, кардинал, номер прописан в другом языке, прописана в качестве валюты на разных языках и т.д.
DORS

Ответы:


181
// https://github.com/google/guava
import static com.google.common.base.Preconditions.*;

String getDayOfMonthSuffix(final int n) {
    checkArgument(n >= 1 && n <= 31, "illegal day of month: " + n);
    if (n >= 11 && n <= 13) {
        return "th";
    }
    switch (n % 10) {
        case 1:  return "st";
        case 2:  return "nd";
        case 3:  return "rd";
        default: return "th";
    }
}

Таблица из @kaliatech хороша, но поскольку та же информация повторяется, это открывает шанс для ошибки. Такая ошибка фактически существует в таблице для 7tn, 17tnи 27tn(эта ошибка может быть исправлена ​​с течением времени из-за изменчивой природы StackOverflow, поэтому проверьте историю версий в ответе, чтобы увидеть ошибку).


36
это действительно похоже на упущение в простом формате данных, не так ли?
stevevls

51
Получите удовольствие от интернационализации этого. : D
Трейказ

6
@Trejkaz За рамками вопроса :)
Грег Мэттес

3
Это решение поддерживает только английский язык. Используйте RuleBasedNumberFormat из ICU4J для локализованного решения.
Дмитро Яковлєв

7
Вы, ребята, в сорняках, и тон немного меняется. В этом нет необходимости. Этот вопрос не задавался о тонкостях интернационализации. Интернационализация, безусловно, является хорошей темой для обсуждения, но это должно быть сделано по другому вопросу с таким фокусом. Я всегда интерпретировал этот вопрос как просьбу о быстром и грязном способе сделать это на английском языке. Итак, я предлагаю, чтобы в мире Java не было хорошего решения, способного к интернационализации, чтобы мы запустили проект, например, на GitHub и создали хорошее решение. Дайте мне знать, если вы заинтересованы.
Грег Мэттс

51

В JDK нет ничего, чтобы сделать это.

  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];

Или используя календарь:

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

Согласно комментариям @ thorbjørn-ravn-andersen, такая таблица может быть полезна при локализации:

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

8
Если вы допустите, чтобы таблица содержала полные «21-й», «23-й», «29-й», она может быть выведена на внешние языки и локализована на другие языки. Для успешного программного обеспечения, которое может стать требованием.
Торбьерн Равн Андерсен

40
private String getCurrentDateInSpecificFormat(Calendar currentCalDate) {
    String dayNumberSuffix = getDayNumberSuffix(currentCalDate.get(Calendar.DAY_OF_MONTH));
    DateFormat dateFormat = new SimpleDateFormat(" d'" + dayNumberSuffix + "' MMMM yyyy");
    return dateFormat.format(currentCalDate.getTime());
}

private String getDayNumberSuffix(int day) {
    if (day >= 11 && day <= 13) {
        return "th";
    }
    switch (day % 10) {
    case 1:
        return "st";
    case 2:
        return "nd";
    case 3:
        return "rd";
    default:
        return "th";
    }
}

Я только что попробовал ваше решение, опуская одинарные кавычки в строке шаблона, предоставленной конструктору SimpleDateFormat. Теперь я понимаю, почему они там. Я получаю java.lang.IllegalArgumentExceptionиз-за "недопустимого образца характера, т."
типичный Дэнни

17

Вопрос немного староват. Поскольку этот вопрос очень шумный, публикуем то, что я решил, используя статический метод в качестве утилиты. Просто скопируйте, вставьте и используйте его!

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

Для тестирования мочеиспускания

Пример: вызов его из основного метода!

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

Вывод:

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

16
String ordinal(int num)
{
    String[] suffix = {"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"};
    int m = num % 100;
    return String.valueOf(num) + suffix[(m > 3 && m < 21) ? 0 : (m % 10)];
}

15

Я хотел бы внести современный ответ. SimpleDateFormatКласс был нормально использовать , когда вопрос был задан 8 лет назад, но вы должны избегать его сейчас , как это не только давно устарели, но и как известно , хлопотное. Используйте java.timeвместо этого.

редактировать

DateTimeFormatterBuilder.appendText(TemporalField, Map<Long, String>)отлично подходит для этой цели. Используя его, мы создаем форматтер, который делает работу за нас:

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

Выход из этого фрагмента:

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

Старый ответ

Этот код короче, но ИМХО не так элегантно.

    // 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()]);

Запустив этот фрагмент только сейчас, я получил

двадцать третий

Одна из многих особенностей java.timeзаключается в том, что получить день месяца в виде простого и надежного int, очевидно, необходимо для выбора правильного суффикса из таблицы.

Я рекомендую вам написать модульный тест тоже.

PS Подобный форматер также можно использовать для анализа строки даты, содержащей порядковые номера, например 1st, 2ndи т. Д. Это было сделано в этом вопросе: Java - анализ даты с дополнительными секундами .

Ссылка: Oracle, учебник: Дата и время, объясняющие, как использовать java.time.


Хотелось бы получить этот ответ выше, так как этот более современный ответ лучше, чем попытка развернуть собственную реализацию.
Krease

9

Если вы попытаетесь узнать о i18n, решение станет еще сложнее.

Проблема в том, что в других языках суффикс может зависеть не только от самого числа, но и от существительного, которое он считает. Например, на русском языке это будет «2-ой день», но «2-ая неделя» (это означает «2-й день», но «2-я неделя»). Это не применимо, если мы форматируем только дни, но в более общем случае вы должны знать о сложности.

Я думаю, что хорошим решением (у меня не было времени на самом деле реализовать) было бы расширить SimpleDateFormetter, чтобы применять Locale-осознавший MessageFormat перед передачей в родительский класс. Таким образом, вы сможете поддерживать, скажем, для мартовских форматов% M для получения «3-го»,% MM для «03-го» и% MMM для «третьего». Со стороны этот класс выглядит как обычный SimpleDateFormatter, но поддерживает больше форматов. Также, если этот шаблон будет по ошибке применен обычным SimpleDateFormetter, результат будет неправильно отформатирован, но все еще читаем.


Хорошее замечание о полах в русском языке, но это технически сделает% MMM невозможным без дополнительного контекста.
Безумный физик

@Mad Physicist, это не так, так как% MMM будет применяться к месяцу, поэтому мы знаем существительное для спряжения.
CAB

6

Многие из приведенных здесь примеров не будут работать для 11, 12, 13. Это более общее и будет работать для всех случаев.

switch (date) {
                case 1:
                case 21:
                case 31:
                    return "" + date + "st";

                case 2:
                case 22:
                    return "" + date + "nd";

                case 3:
                case 23:
                    return "" + date + "rd";

                default:
                    return "" + date + "th";
}

5

Я не могу быть удовлетворен ответами, призывающими к решению только на английском языке на основе ручных форматов. Я давно искал подходящее решение и наконец нашел его.

Вы должны использовать RuleBasedNumberFormat . Это работает отлично, и это с уважением к Locale.


3

Существует более простой и надежный способ сделать это. Вам нужно использовать функцию getDateFromDateString (dateString); Он в основном удаляет st / nd / rd / th из строки даты и просто анализирует ее. Вы можете изменить свой SimpleDateFormat на что угодно, и это будет работать.

public static final SimpleDateFormat sdf = new SimpleDateFormat("d");
public static final Pattern p = Pattern.compile("([0-9]+)(st|nd|rd|th)");

private static Date getDateFromDateString(String dateString) throws ParseException {
     return sdf.parse(deleteOrdinal(dateString));
}

private static String deleteOrdinal(String dateString) {
    Matcher m = p.matcher(dateString);
    while (m.find()) {
        dateString = dateString.replaceAll(Matcher.quoteReplacement(m.group(0)), m.group(1));
    }
    return dateString;

}


1
Этот ответ о разборе строки, а вопрос о генерации строки. Но все же уместно, поскольку, вероятно, понадобятся оба направления. Кроме того, этот ответ решает этот другой вопрос .
Василий Бурк

3

Единственная проблема с решением, предоставленным Грегом, состоит в том, что он не учитывает число больше 100 с окончанием «подростковых» чисел. Например, 111 должен быть 111-м, а не 111-м. Это мое решение:

/**
 * Return ordinal suffix (e.g. 'st', 'nd', 'rd', or 'th') for a given number
 * 
 * @param value
 *           a number
 * @return Ordinal suffix for the given number
 */
public static String getOrdinalSuffix( int value )
{
    int hunRem = value % 100;
    int tenRem = value % 10;

    if ( hunRem - tenRem == 10 )
    {
        return "th";
    }
    switch ( tenRem )
    {
    case 1:
        return "st";
    case 2:
        return "nd";
    case 3:
        return "rd";
    default:
        return "th";
    }
}

6
В каком случае последовательность «Дневной месяц» будет иметь более 31 дня?
SatanEnglish

@SatanEnglish, хорошо в этом статическом фабричном методе является то, что его можно использовать не только для получения суффикса за месяц. :)
Джаред Раммлер

1
Этот метод возвращает st для 11, nd для 12 и rd для 13
TheIT

@SatanEnglish. Учитывая, что сегодня в моем избранном календаре 137 февраля, я думаю, что ваш вопрос отвечает сам собой. Если серьезно, то если вы знаете, где искать, то существует множество негригорианских календарей.
Безумный физик

Нет, @ TheIT, это не так. Я проверял. Он возвращается thна 11, 12 и 13, как и должно быть. Я верю, что if ( hunRem - tenRem == 10 )дело подтверждает это.
Оле В.В.

2

RuleBasedNumberFormat в библиотеке ICU

Я высоко оценил ссылку на библиотеку проекта ICU от @ Pierre-Olivier Dybman ( http://www.icu-project.org/apiref/icu4j/com/ibm/icu/text/RuleBasedNumberFormat.html ), однако все еще должен был понять как его использовать, поэтому пример RuleBasedNumberFormatиспользования приведен ниже.

Он будет форматировать только одно число, а не всю дату, поэтому вам нужно будет создать комбинированную строку, если вы ищете дату в формате: понедельник, 3 февраля, например.

Приведенный ниже код устанавливает RuleBasedNumberFormatформат Ordinal для данного Locale, создает java.time ZonedDateTime, а затем форматирует число с порядковым номером в строку.

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

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

Пример вывода:

третий

Или

четвёртая

и т.п.


1

Вот подход, который обновляет шаблон DateTimeFormatter с правильным литеральным суффиксом, если он находит шаблон d'00', например, для дня месяца 1 он будет заменен на d'st'. После того, как шаблон был обновлен, он может быть передан в DateTimeFormatter, чтобы сделать все остальное.

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

Требуется, чтобы исходный шаблон обновлялся непосредственно перед каждым вызовом форматирования, например

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

Так что это полезно, если шаблон форматирования определен вне Java-кода, например, шаблона, где, как если бы вы могли определить шаблон в Java, тогда ответ @ OleV.V. может быть более подходящим


1
Творческий и запутанный. Конечно, мне понадобилось много времени, чтобы понять, как это работает. Это не признак хорошего кода.
Оле В.В.

1
@ OleV.V. Спасибо за отзыв - я его немного реструктурировал, так что он немного менее многословен. Просто видел твой подход и мне это нравится! Я думаю, что оба подхода действительны с различными компромиссами. Ваша не требует дополнительной поддержки на этапе форматирования, но требует, чтобы шаблон определялся с помощью компоновщика, который исключает определение шаблона в не Java-коде. Мой подход требует дополнительной поддержки на этапе форматирования, но не зависит от сборщика при создании шаблона, поэтому он становится немного более гибким при определении шаблона. Мои требования продиктованы последним
tazmaniax

3
Очень хороший отчет о плюсах и минусах. Вы можете включить это в ответ? Просто идея.
Оле ВВ

1

Я написал себе вспомогательный метод, чтобы получить шаблоны для этого.

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

и тогда мы можем назвать это как

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

выход

December 12th, 2018

Это требует минимального API 26, хотя :)
Миккель Ларсен

@MikkelLarsen этот вопрос был не об Android, я использовал Java 8 для этого. Android не поддерживает большой API-
интерфейс

@SamSakerz мой плохой :)
Миккель Ларсен

@MikkelLarsen Большинство функций java.time перенесены в Java 6 и Java 7 в проекте ThreeTen-Backport . Далее адаптирован для более ранней версии Android (<26) в ThreeTenABP . Смотрите Как использовать ThreeTenABP… .
Василий Бурк

1

Попробуйте функцию ниже:

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

При ответе на старый вопрос ваш ответ был бы гораздо более полезным для других пользователей StackOverflow, если бы вы включили некоторый контекст для объяснения того, как ваш ответ помогает, особенно для вопроса, на который уже есть принятый ответ. Смотрите: Как мне написать хороший ответ .
Дэвид Бак

Спасибо за желание внести свой вклад. Я рекомендую не использовать Date, Calendarа SimpleDateFormat. Эти классы плохо спроектированы и давно устарели, последние особенно печально известны. Вместо этого используйте LocalDateи DateTimeFormatter, как из java.time, современный Java-интерфейс даты и времени . Также я сомневаюсь, что нового дает ваш ответ по сравнению с другими 18 ответами?
Оле В.В.

0

В kotlin вы можете использовать как это

fun changeDateFormats(currentFormat: String, dateString: String): String {
        var result = ""
        try {
            val formatterOld = SimpleDateFormat(currentFormat, Locale.getDefault())
            formatterOld.timeZone = TimeZone.getTimeZone("UTC")

            var date: Date? = null

            date = formatterOld.parse(dateString)

            val dayFormate = SimpleDateFormat("d", Locale.getDefault())
            var day = dayFormate.format(date)

            val formatterNew = SimpleDateFormat("hh:mm a, d'" + getDayOfMonthSuffix(day.toInt()) + "' MMM yy", Locale.getDefault())

            if (date != null) {
                result = formatterNew.format(date)
            }

        } catch (e: ParseException) {
            e.printStackTrace()
            return dateString
        }

        return result
    }


    private fun getDayOfMonthSuffix(n: Int): String {
        if (n in 11..13) {
            return "th"
        }
        when (n % 10) {
            1 -> return "st"
            2 -> return "nd"
            3 -> return "rd"
            else -> return "th"
        }
    }

установить так

  txt_chat_time_me.text = changeDateFormats("SERVER_DATE", "DATE")

-3

Следующий метод может использоваться для получения отформатированной строки даты, которая передана ей. Он отформатирует дату, скажем 1-й, 2-й, 3-й, 4-й .. используя SimpleDateFormat в Java. например: - 1 сентября 2015

public String getFormattedDate(Date date){
            Calendar cal=Calendar.getInstance();
            cal.setTime(date);
            //2nd of march 2015
            int day=cal.get(Calendar.DATE);

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

-4

Ниже приведен более эффективный ответ на вопрос, чем жесткое программирование стиля.

Чтобы изменить день на порядковый номер, вам нужно использовать следующий суффикс .

DD +     TH = DDTH  result >>>> 4TH

OR to spell the number add SP to the format

DD + SPTH = DDSPTH   result >>> FOURTH

Найдите мой законченный ответ в этом вопросе.


Вопрос заключается в формате в Java не в базе данных Oracle docs.oracle.com/cd/B12037_01/server.101/b10759/... Java использование SimpleDateFormat на дату: docs.oracle.com/javase/tutorial/i18n/format/...
Belal Мазлом

-9
public String getDaySuffix(int inDay)
{
  String s = String.valueOf(inDay);

  if (s.endsWith("1"))
  {
    return "st";
  }
  else if (s.endsWith("2"))
  {
    return "nd";
  }
  else if (s.endsWith("3"))
  {
    return "rd";
  }
  else
  {
    return "th";
  }
}

Он не должен использовать endWith (); которые дают неправильный результат
Рахул Шарма
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.