Analisando o indicador ordinal de uma data (st, nd, rd, th) em uma string de data e hora

Eu verifiquei o javadoc SimpleDateFormat , mas não consigo encontrar uma maneira de analisar o indicador ordinal em um formato de data como este:

  Feb 13th 2015 9:00AM 

Eu tentei "MMM dd yyyy hh:mma" , mas os dias tem que ser em número para que seja correto?

É possível analisar a data “13” usando um SimpleDateFormat sem ter que truncar a seqüência de caracteres?

O SimpleDateFormat do Java não suporta um sufixo ordinal, mas o sufixo ordinal é apenas um efeito visual – é redundante e pode ser facilmente removido para permitir uma análise direta:

 Date date = new SimpleDateFormat("MMM dd yyyy hh:mma") .parse(str.replaceAll("(?< =\\d)(st|nd|rd|th)", "")); 

A regex de substituição é tão simples porque essas sequências não aparecerão em nenhum outro lugar em uma data válida.


Para lidar com qualquer idioma que acrescente qualquer tamanho de caracteres indicadores ordinais de qualquer idioma como um sufixo:

 Date date = new SimpleDateFormat("MMM dd yyyy hh:mma") .parse(str.replaceAll("(?< =\\d)(?=\\D* \\d+ )\\p{L}+", "")); 

Algumas línguas, por exemplo, o mandarim, preenchem o seu indicador ordinal, mas isso poderia ser tratado também usando uma alternação - deixada como um exercício para o leitor 🙂

Resposta do Java 8 (e Java 6 e 7) (porque quando essa pergunta foi feita em 2015, a substituição do SimpleDateFormat já estava fora):

  DateTimeFormatter parseFormatter = DateTimeFormatter .ofPattern("MMM d['st']['nd']['rd']['th'] uuuu h:mma", Locale.ENGLISH); LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, parseFormatter); 

Com a data da amostra da pergunta esta yiedls:

 2015-02-13T09:00 

No formato padrão [] indica partes opcionais e '' indica partes literais. Então o padrão diz que o número pode ser seguido por st , nd , rd ou th .

Para usar isso no Java 6 ou 7, você precisa do ThreeTen Backport . Ou para o Android ThreeTenABP .

Como esses sufixos são especiais para o inglês, e outros idiomas / localidades têm outros usos para escrever datas e horas (eles também não usam AM / PM), acredito que, a menos que você tenha outros requisitos, tente implementá-lo para Apenas datas e horários em inglês. Além disso, você deve fornecer uma localidade em inglês explicitamente para que funcione independentemente da configuração de localidade do seu computador ou da JVM.

Eu tentei combinar as melhores partes das respostas de Hugo e eu a uma questão de duplicidade. Sob essa questão duplicada ainda há mais respostas do java 8. Uma limitação do código acima é que ele não tem uma validação muito rígida: você vai se safar com o dia Feb 13rd e até Feb 13rd Feb 13stndrdth .

No caso de alguém achar útil: Construtor DateTimeFormatter. Este formatador permite que você formate e analise datas do Reino Unido com sufixos ordinais (por exemplo, “1º de janeiro de 2017”):

 public class UkDateFormatterBuilder { /** * The UK date formatter that formats a date without an offset, such as '14th September 2020' or '1st January 2017'. * @return an immutable formatter which uses the {@link ResolverStyle#SMART SMART} resolver style. It has no override chronology or zone. */ public DateTimeFormatter build() { return new DateTimeFormatterBuilder() .parseCaseInsensitive() .parseLenient() .appendText(DAY_OF_MONTH, dayOfMonthMapping()) .appendLiteral(' ') .appendText(MONTH_OF_YEAR, monthOfYearMapping()) .appendLiteral(' ') .appendValue(YEAR, 4) .toFormatter(Locale.UK); } private Map monthOfYearMapping() { Map monthOfYearMapping = new HashMap<>(); monthOfYearMapping.put(1L, "January"); monthOfYearMapping.put(2L, "February"); monthOfYearMapping.put(3L, "March"); monthOfYearMapping.put(4L, "April"); monthOfYearMapping.put(5L, "May"); monthOfYearMapping.put(6L, "June"); monthOfYearMapping.put(7L, "July"); monthOfYearMapping.put(8L, "August"); monthOfYearMapping.put(9L, "September"); monthOfYearMapping.put(10L, "October"); monthOfYearMapping.put(11L, "November"); monthOfYearMapping.put(12L, "December"); return monthOfYearMapping; } private Map dayOfMonthMapping() { Map suffixes = new HashMap<>(); for (int day=1; day< =31; day++) { suffixes.put((long)day, String.format("%s%s", (long) day, dayOfMonthSuffix(day))); } return suffixes; } private String dayOfMonthSuffix(final int day) { Preconditions.checkArgument(day >= 1 && day < = 31, "Illegal day of month: " + 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"; } } } 

Mais um fragment da class de teste:

 public class UkDateFormatterBuilderTest { DateTimeFormatter formatter = new UkDateFormatterBuilder().build(); @Test public void shouldFormat1stJanuaryDate() { final LocalDate date = LocalDate.of(2017, 1, 1); final String formattedDate = date.format(formatter); Assert.assertEquals("1st January 2017", formattedDate); } @Test public void shouldParse1stJanuaryDate() { final String formattedDate = "1st January 2017"; final LocalDate parsedDate = LocalDate.parse(formattedDate, formatter); Assert.assertEquals(LocalDate.of(2017, 1, 1), parsedDate); } } 

PS. Eu usei a solução de Greg Mattes para sufixos ordinais a partir daqui: Como você formata o dia do mês para dizer "11th", "21st" ou "23rd" em Java? (indicador ordinal)

Você deve estar usando RuleBasedNumberFormat . Ele funciona perfeitamente e é respeitoso com o Locale.