Analizar el indicador ordinal de una fecha (st, nd, rd, th) en una cadena de fecha y hora

SimpleDateFormat javadoc SimpleDateFormat , pero no puedo encontrar una forma de analizar el indicador ordinal en un formato de fecha como este:

  Feb 13th 2015 9:00AM 

Intenté "MMM dd yyyy hh:mma" , pero los días tienen que ser en número para que sea correcto?

¿Es posible analizar la fecha “13” con un SimpleDateFormat sin tener que truncar la cadena?

El SimpleDateFormat de Java no es compatible con un sufijo ordinal, pero el sufijo ordinal es simplemente delicado: es redundante y puede eliminarse fácilmente para permitir un análisis directo:

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

La regex de reemplazo es tan simple porque esas secuencias no aparecerán en ningún otro lado en una fecha válida.


Para manejar cualquier idioma que añada cualquier longitud de caracteres del indicador ordinal de cualquier idioma como sufijo:

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

Algunos idiomas, por ejemplo, el mandarín, antependen su indicador ordinal, pero eso también podría manejarse usando una alternancia: dejó como ejercicio para el lector 🙂

Respuesta de Java 8 (y Java 6 y 7) (porque cuando se formuló esta pregunta en 2015, el reemplazo de SimpleDateFormat ya estaba fuera):

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

Con la fecha de la muestra de la pregunta, esto yiedls:

 2015-02-13T09:00 

En el formato, el patrón [] denota partes opcionales y '' denota partes literales. Entonces el patrón dice que el número puede ser seguido por st , nd , rd o th .

Para usar esto en Java 6 o 7 necesita ThreeTen Backport . O para Android ThreeTenABP .

Como esos sufijos son especiales para el inglés, y otros idiomas / locales tienen usos completamente diferentes para escribir fechas y horas (también no usan AM / PM), creo que a menos que tenga otros requisitos, debe intentar implementar esto para Fechas y horarios en inglés solamente. También debe proporcionar explícitamente una configuración regional de habla inglesa para que funcione independientemente de la configuración regional de su computadora o JVM.

Intenté combinar las mejores partes de las respuestas de Hugo y yo con una pregunta duplicada. Debajo de esa pregunta duplicada todavía hay más 8 respuestas de java. Una limitación del código anterior es que no tiene una validación muy estricta: se saldrá con la suya Feb 13rd e incluso Feb 13stndrdth .

En caso de que alguien lo encuentre útil: DateTimeFormatter builder. Este formateador le permite formatear y analizar las fechas del Reino Unido con sufijos ordinales (por ejemplo, “1 de enero 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"; } } } 

Además de un fragmento de la clase de prueba:

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

PD. Utilicé la solución de Greg Mattes para sufijos ordinales desde aquí: ¿cómo formatear el día del mes para decir "11 °", "21 °" o "23 °" en Java? (indicador ordinal)

Debería usar RuleBasedNumberFormat . Funciona perfectamente y es respetuoso de la configuración regional.