Como você formata o dia do mês para dizer “11º”, “21º” ou “23º” (indicador ordinal)?


146

Eu sei que isso vai me dar o dia do mês como um número ( 11, 21, 23):

SimpleDateFormat formatDayOfMonth = new SimpleDateFormat("d");

Mas como você formata o dia do mês para incluir um indicador ordinal , digamos 11th, 21stou 23rd?


13
Para referência, esses são chamados números ordinais - en.wikipedia.org/wiki/Ordinal_number_(linguistics) .
ocodo 25/10/10

5
Apenas para constar, qualquer coisa que construa a resposta em vez de procurar a resposta inteira em uma tabela é quase impossível de localizar em outros idiomas.
Thorbjørn Ravn Andersen

2
A resposta é de alguma forma incorreta, dê uma olhada na minha resposta plz.
J888

2
Comentário moderno: eu recomendo que você evite a SimpleDateFormataula. Não é apenas desatualizado há muito tempo, também é notoriamente problemático. Hoje, temos muito melhor java.time, a moderna API de data e hora de Java e suas DateTimeFormatter.
Ole VV

Dê uma olhada na API de números ( math.tools/api/numbers ). Tem suporte para ordinal, cardinal, número de enunciados em linguagem diferente, explicitada como moeda em várias línguas etc.
dors

Respostas:


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

A tabela do @kaliatech é boa, mas, como as mesmas informações são repetidas, abre a chance de um bug. Na verdade, esse erro existe na tabela para 7tn, 17tne 27tn(esse erro pode ser corrigido com o passar do tempo devido à natureza fluida do StackOverflow, portanto, verifique o histórico da versão na resposta para ver o erro).


36
realmente parece um descuido no formato de dados simples, não é?
precisa

51
Divirta-se internacionalizando isso. : D
Trejkaz

6
@Trejkaz Fora do escopo da pergunta :) #
Greg Mattes

3
Esta solução suporta apenas inglês. Use RuleBasedNumberFormat do ICU4J para a solução localizada.
precisa saber é o seguinte

7
Vocês estão perdidos, e o tom está mudando um pouco. Não é preciso isso. Esta pergunta não perguntou sobre os meandros da internacionalização. A internacionalização é certamente um bom tópico a ser discutido, mas deve ser feito em outra questão com esse foco. Eu sempre interpretei essa pergunta como pedindo uma maneira rápida e suja de fazer isso em inglês. Portanto, sugiro que, se realmente não houver uma boa solução no mundo Java que seja favorável à internacionalização, iniciemos um projeto, por exemplo, no GitHub e crie uma boa solução. Deixe-me saber se você está interessado.
precisa

51

Não há nada no JDK para fazer isso.

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

Ou usando o Calendário:

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

Por comentários de @ thorbjørn-ravn-andersen, uma tabela como essa pode ser útil ao localizar:

  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
Se você deixar a tabela conter o "21º", "23º", "29º" completo, ela poderá ser externa e localizada em outros idiomas. Para software de sucesso que pode se tornar um requisito.
Thorbjørn Ravn Andersen

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

Eu apenas tentei sua solução enquanto omitia as aspas simples na cadeia de padrão fornecida ao construtor SimpleDateFormat. Agora vejo por que eles estão lá. Estou recebendo um java.lang.IllegalArgumentExceptionpor causa de "um caractere de padrão ilegal, t".
típico danny

17

A pergunta é um pouco antiga. Como esta pergunta é muito barulhenta, postando o que resolvi com o método estático como um utilitário. Basta copiar, colar e usá-lo!

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

Para testar a purose

Exemplo: chamando-o do método principal!

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

Resultado:

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

Eu gostaria de contribuir com a resposta moderna. A SimpleDateFormataula foi boa para usar quando a pergunta foi feita há oito anos, mas você deve evitá-la agora, pois ela não só está desatualizada há muito tempo, mas também é notoriamente problemática. Use em java.timevez disso.

Editar

DateTimeFormatterBuilder.appendText(TemporalField, Map<Long, String>)é ótimo para esse fim. Utilizando-o, construímos um formatador que faz o trabalho para nós:

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

A saída desse snippet é:

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

Resposta antiga

Este código é mais curto, mas IMHO não é tão elegante.

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

Executando esse trecho agora que eu recebi

23

Um dos muitos recursos java.timeé que é simples e confiável obter o dia do mês como um int, o que é obviamente necessário para escolher o sufixo certo da tabela.

Eu recomendo que você escreva um teste de unidade também.

PS Um formatador semelhante também pode ser usado para analisar uma seqüência de data contendo números ordinais, como 1st, 2nd, etc. Isso foi feito em esta pergunta: Java - data Parse com segundos opcionais .

Link: Tutorial do Oracle: Data e hora explicando como usar java.time.


Gostaria muito de obter uma resposta mais alta, pois essa resposta mais moderna é melhor do que tentar implementar sua própria implementação.
Krease

9

Se você tentar conhecer o i18n, a solução ficará ainda mais complicada.

O problema é que, em outras línguas, o sufixo pode depender não apenas do número em si, mas também do substantivo que conta. Por exemplo, em russo, seria "2-ой день", mas "2-ая неделя" (isso significa "2º dia", mas "2ª semana"). Isso não se aplica se formatarmos apenas dias, mas, em um caso um pouco mais genérico, você deve estar ciente da complexidade.

Acho que a melhor solução (não tive tempo de implementar) seria estender o SimpleDateFormetter para aplicar o MessageFormat com reconhecimento de localidade antes de passar para a classe pai. Dessa forma, você poderá oferecer suporte aos formatos% M para obter "3º",% MM para "3º" e% MMM para "3º". De fora desta classe parece simples SimpleDateFormatter, mas suporta mais formatos. Além disso, se esse padrão fosse aplicado por engano pelo SimpleDateFormetter comum, o resultado seria formatado incorretamente, mas ainda legível.


Bom argumento sobre os gêneros em russo, mas isso tecnicamente tornaria% MMM impossível sem contexto adicional.
Mad Physicist

@ Mad Physicist, isso não é verdade, pois% MMM será aplicado por mês, então sabemos que o substantivo deve ser conjugado.
CAB

6

Muitos dos exemplos aqui não funcionarão para 11, 12, 13. Isso é mais genérico e funcionará para todos os casos.

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

Não estou satisfeito com as respostas que exigem uma solução somente em inglês com base em formatos manuais. Estou procurando uma solução adequada há um tempo e finalmente a encontrei.

Você deve estar usando RuleBasedNumberFormat . Funciona perfeitamente e respeita a localidade.


3

Existe uma maneira mais simples e segura de fazer isso. A função que você precisará usar é getDateFromDateString (dateString); Ele basicamente remove o st / nd / rd / th de uma string de data e simplesmente o analisa. Você pode alterar seu SimpleDateFormat para qualquer coisa e isso funcionará.

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
Esta resposta é sobre a análise de uma seqüência de caracteres, enquanto a pergunta é sobre a geração de uma seqüência de caracteres. Mas ainda é apropriado, pois é provável que você precise das duas direções. Além disso, esta resposta resolve esta outra pergunta .
Basil Bourque

3

O único problema da solução fornecida por Greg é que ela não é responsável por números maiores que 100, com os números "adolescentes" terminando. Por exemplo, 111 deve ser 111, não 111. Esta é a minha solução:

/**
 * 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
Em que caso uma sequência do mês do dia teria mais de 31 dias?
precisa saber é o seguinte

@SatanEnglish, o lado bom desse método estático de fábrica é que ele pode ser usado por mais do que apenas obter o sufixo de um mês. :)
Jared Rummler

1
Este método retorna st para 11, nd para 12 e rd para 13
TheIT

@SatanEnglish. Dado que hoje é 137 de fevereiro no meu calendário de escolha, acho que sua pergunta responde a si mesma. Sério, porém, os calendários não gregorianos abundam se você souber onde procurar.
Físico Louco

Não, @ TheIT, não. Eu testei. Retorna thpara 11, 12 e 13 como deveria. Acredito que o if ( hunRem - tenRem == 10 )caso garanta que sim.
Ole VV

2

RuleBasedNumberFormat na biblioteca da UTI

Apreciei o link para a biblioteca do projeto da UTI de @ Pierre-Olivier Dybman ( http://www.icu-project.org/apiref/icu4j/com/ibm/icu/text/RuleBasedNumberFormat.html ), mas ainda precisava descobrir como usá-lo, então um exemplo do RuleBasedNumberFormatuso está abaixo.

Ele formatará apenas um número e não a data inteira, portanto, você precisará criar uma sequência combinada se procurar uma data no formato: segunda-feira, 3 de fevereiro, por exemplo.

O código abaixo configura o RuleBasedNumberFormatformato como ordinal para um determinado local, cria um java.time ZonedDateTimee formata o número com seu ordinal em uma seqüência de caracteres.

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

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

Exemplo de saída:

Ou

etc.


1

Aqui está uma abordagem que atualiza um padrão DateTimeFormatter com o literal correto do sufixo se encontrar o padrão d'00', por exemplo, para o dia do mês 1, ele seria substituído por d'st'. Depois que o padrão é atualizado, ele pode apenas ser alimentado no DateTimeFormatter para fazer o resto.

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

Requer que o padrão original seja atualizado imediatamente antes de cada chamada de formatação, por exemplo

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

Portanto, isso é útil se o padrão de formatação for definido fora do código Java, por exemplo, um modelo, onde, como se você pudesse definir o padrão em Java, a resposta seria @ OleV.V. pode ser mais apropriado


1
Criativo e complicado. Certamente demorei muito tempo para entender como isso funciona. Isso não é um sinal de bom código.
Ole VV

1
@ OleV.V. thx pelo feedback - eu o reestruturei um pouco para ficar um pouco menos detalhado. Acabei de ver sua abordagem e eu gosto! Penso que ambas as abordagens são válidas com diferentes compensações. O seu não requer nenhum suporte adicional no ponto de formatação, mas exige que o padrão seja definido usando o construtor que impede a definição de padrão no código não Java. Minha abordagem requer suporte adicional no ponto de formatação, mas nenhuma dependência do construtor para criar o padrão, tornando-o um pouco mais flexível onde o padrão pode ser definido. Meus requisitos ditaram o último
tazmaniax

3
Muito boa conta de prós e contras. Você pode incluí-lo na resposta? Apenas uma ideia.
Ole VV

1

Eu escrevi para mim mesmo um método auxiliar para obter padrões para isso.

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

e então podemos chamá-lo como

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

a saída é

December 12th, 2018

Isto requer uma API mínimo de 26 embora :)
Mikkel Larsen

@MikkelLarsen esta pergunta não era sobre android, eu estava usando o Java 8 para isso. Android não suportar um grande api java para 8.
SamHoque

@SamSakerz my bad :)
Mikkel Larsen

@MikkelLarsen A maioria das funcionalidades do java.time é portada para Java 6 e Java 7 no projeto ThreeTen-Backport . Mais adaptado para Android anterior (<26) no ThreeTenABP . Consulte Como usar o ThreeTenABP… .
Basil Bourque

1

Tente abaixo da função:

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

Ao responder a uma pergunta antiga, sua resposta seria muito mais útil para outros usuários do StackOverflow se você incluísse algum contexto para explicar como sua resposta ajuda, principalmente para uma pergunta que já tenha uma resposta aceita. Veja: Como escrevo uma boa resposta .
David Buck

Obrigado por querer contribuir. Eu recomendo que não usamos Date, Calendare SimpleDateFormat. Essas classes são mal planejadas e desatualizadas, a última em particular notoriamente problemática. Em vez disso, use LocalDatee DateTimeFormatter, ambos de java.time, a moderna API de data e hora de Java . Também estou em dúvida de que nova sua resposta contribui em comparação com as outras 18 respostas?
Ole VV

0

No kotlin você pode usar assim

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

definido assim

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

-3

O método a seguir pode ser usado para obter a seqüência de caracteres formatada da data que é passada para ele. Ele formatará a data para dizer 1º, 2º, 3º, 4º .. usando SimpleDateFormat em Java. Por exemplo: - 1 de setembro de 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

A seguir, é uma resposta mais eficiente à pergunta, em vez de codificar o estilo.

Para alterar o dia para o número ordinal, você precisa usar o seguinte sufixo .

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

OR to spell the number add SP to the format

DD + SPTH = DDSPTH   result >>> FOURTH

Encontre minha resposta completa nesta pergunta.


A questão é formatar em Java, não no banco de dados Oracle docs.oracle.com/cd/B12037_01/server.101/b10759/… Java usa SimpleDateFormat para a data: docs.oracle.com/javase/tutorial/i18n/format/…
Belal mazlom 25/08/2015

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

Não deve usar endsWith (); que produzem saída errada
Rahul Sharma
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.