Implementación de las mejores prácticas de análisis de fecha y hora
En este paso final, crearemos un analizador de fecha y hora completo que implemente las mejores prácticas de la industria para manejar valores de fecha y hora. También exploraremos técnicas adicionales que los desarrolladores profesionales de Java utilizan para evitar y manejar DateTimeParseException.
Creación de un analizador de fecha y hora listo para producción
Creemos una clase de utilidad que incorpore todas las mejores prácticas que hemos aprendido:
- En el WebIDE, haga clic con el botón derecho en la carpeta
/home/labex/project
- Seleccione "Nuevo archivo" y asígnele el nombre
DateTimeParserUtil.java
- Copie y pegue el siguiente código:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
/**
* Utility class for parsing date and time strings in a robust manner.
*/
public class DateTimeParserUtil {
// Common date formatters with STRICT resolver for validation
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd"),
createFormatter("yyyy/MM/dd"),
createFormatter("MM/dd/yyyy"),
createFormatter("dd-MM-yyyy"),
createFormatter("d MMM yyyy"),
createFormatter("MMMM d, yyyy")
);
// Common time formatters
private static final List<DateTimeFormatter> TIME_FORMATTERS = Arrays.asList(
createFormatter("HH:mm:ss"),
createFormatter("HH:mm"),
createFormatter("h:mm a"),
createFormatter("h:mm:ss a")
);
// Common date-time formatters
private static final List<DateTimeFormatter> DATETIME_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd HH:mm:ss"),
createFormatter("yyyy-MM-dd'T'HH:mm:ss"),
createFormatter("yyyy-MM-dd HH:mm"),
createFormatter("MM/dd/yyyy HH:mm:ss"),
createFormatter("dd-MM-yyyy HH:mm:ss")
);
/**
* Creates a DateTimeFormatter with strict resolver style.
*/
private static DateTimeFormatter createFormatter(String pattern) {
return new DateTimeFormatterBuilder()
.appendPattern(pattern)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter()
.withResolverStyle(ResolverStyle.STRICT)
.withLocale(Locale.US);
}
/**
* Parses a string into a LocalDate.
*
* @param dateStr the date string to parse
* @return an Optional containing the parsed LocalDate, or empty if parsing fails
*/
public static Optional<LocalDate> parseDate(String dateStr) {
if (dateStr == null || dateStr.trim().isEmpty()) {
return Optional.empty();
}
for (DateTimeFormatter formatter : DATE_FORMATTERS) {
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
return Optional.of(date);
} catch (DateTimeParseException e) {
// Try next formatter
}
}
return Optional.empty();
}
/**
* Parses a string into a LocalTime.
*
* @param timeStr the time string to parse
* @return an Optional containing the parsed LocalTime, or empty if parsing fails
*/
public static Optional<LocalTime> parseTime(String timeStr) {
if (timeStr == null || timeStr.trim().isEmpty()) {
return Optional.empty();
}
for (DateTimeFormatter formatter : TIME_FORMATTERS) {
try {
LocalTime time = LocalTime.parse(timeStr, formatter);
return Optional.of(time);
} catch (DateTimeParseException e) {
// Try next formatter
}
}
return Optional.empty();
}
/**
* Parses a string into a LocalDateTime.
*
* @param dateTimeStr the date-time string to parse
* @return an Optional containing the parsed LocalDateTime, or empty if parsing fails
*/
public static Optional<LocalDateTime> parseDateTime(String dateTimeStr) {
if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
return Optional.empty();
}
// First try with combined date-time formatters
for (DateTimeFormatter formatter : DATETIME_FORMATTERS) {
try {
LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, formatter);
return Optional.of(dateTime);
} catch (DateTimeParseException e) {
// Try next formatter
}
}
// Then try to split into date and time parts
String[] parts = dateTimeStr.split(" ", 2);
if (parts.length == 2) {
Optional<LocalDate> date = parseDate(parts[0]);
Optional<LocalTime> time = parseTime(parts[1]);
if (date.isPresent() && time.isPresent()) {
return Optional.of(LocalDateTime.of(date.get(), time.get()));
}
}
return Optional.empty();
}
/**
* Attempts to parse a string as a date, time, or date-time value.
*
* @param input the string to parse
* @return a string describing the parsed result or an error message
*/
public static String parseAny(String input) {
Optional<LocalDate> date = parseDate(input);
if (date.isPresent()) {
return "Parsed as date: " + date.get();
}
Optional<LocalTime> time = parseTime(input);
if (time.isPresent()) {
return "Parsed as time: " + time.get();
}
Optional<LocalDateTime> dateTime = parseDateTime(input);
if (dateTime.isPresent()) {
return "Parsed as date-time: " + dateTime.get();
}
return "Could not parse: " + input;
}
}
Ahora, creemos una clase de prueba para demostrar la utilidad:
- En el WebIDE, cree un nuevo archivo llamado
DateTimeParsingDemo.java
- Copie y pegue el siguiente código:
public class DateTimeParsingDemo {
public static void main(String[] args) {
// Test with various date, time, and date-time strings
String[] inputs = {
// Valid dates
"2023-05-15",
"05/15/2023",
"15 May 2023",
// Valid times
"14:30:00",
"2:30 PM",
"14:30",
// Valid date-times
"2023-05-15 14:30:00",
"2023-05-15T14:30:00",
"05/15/2023 14:30:00",
"15-05-2023 14:30:00",
// Invalid examples
"2023-02-30",
"25:30:00",
"2023-13-01",
"Not a date or time",
""
};
for (String input : inputs) {
String result = DateTimeParserUtil.parseAny(input);
System.out.println("Input: \"" + input + "\" → " + result);
}
}
}
Compile y ejecute este programa:
cd ~/project
javac DateTimeParserUtil.java DateTimeParsingDemo.java
java DateTimeParsingDemo
Debería ver una salida similar a:
Input: "2023-05-15" → Parsed as date: 2023-05-15
Input: "05/15/2023" → Parsed as date: 2023-05-15
Input: "15 May 2023" → Parsed as date: 2023-05-15
Input: "14:30:00" → Parsed as time: 14:30
Input: "2:30 PM" → Parsed as time: 14:30
Input: "14:30" → Parsed as time: 14:30
Input: "2023-05-15 14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "2023-05-15T14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "05/15/2023 14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "15-05-2023 14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "2023-02-30" → Could not parse: 2023-02-30
Input: "25:30:00" → Could not parse: 25:30:00
Input: "2023-13-01" → Could not parse: 2023-13-01
Input: "Not a date or time" → Could not parse: Not a date or time
Input: "" → Could not parse:
Mejores prácticas para el análisis de fecha y hora en Java
Examinemos las principales mejores prácticas implementadas en nuestra clase de utilidad:
1. Use la configuración ResolverStyle.STRICT
.withResolverStyle(ResolverStyle.STRICT)
El estilo de resolución STRICT garantiza que solo se acepten fechas y horas válidas. Esto evita problemas como analizar "31 de febrero" a marzo 3.
2. Especifique una configuración regional predeterminada
.withLocale(Locale.US)
Siempre especifique la configuración regional para garantizar un comportamiento de análisis consistente, especialmente para los nombres de los meses y los indicadores AM/PM.
3. Use el tipo de retorno Optional
public static Optional<LocalDate> parseDate(String dateStr) {
// ...
return Optional.empty();
}
Usar Optional comunica claramente que el análisis podría fallar y obliga al código que llama a manejar este caso explícitamente.
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd"),
createFormatter("yyyy/MM/dd"),
// ...
);
Admitir múltiples formatos aumenta la solidez de su lógica de análisis al tratar con varias fuentes de entrada.
5. Campos de tiempo faltantes predeterminados
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
Los campos de tiempo faltantes predeterminados permiten un análisis más flexible, especialmente al convertir entre diferentes tipos de fecha y hora.
6. Validación de entrada
if (dateStr == null || dateStr.trim().isEmpty()) {
return Optional.empty();
}
Siempre valide la entrada antes de intentar analizar para evitar excepciones innecesarias.
7. Manejo de errores elegante
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
return Optional.of(date);
} catch (DateTimeParseException e) {
// Try next formatter
}
Maneje las excepciones con elegancia y proporcione comentarios claros cuando el análisis falla.
8. Análisis compuesto inteligente
// Then try to split into date and time parts
String[] parts = dateTimeStr.split(" ", 2);
if (parts.length == 2) {
Optional<LocalDate> date = parseDate(parts[0]);
Optional<LocalTime> time = parseTime(parts[1]);
if (date.isPresent() && time.isPresent()) {
return Optional.of(LocalDateTime.of(date.get(), time.get()));
}
}
Dividir las tareas de análisis complejas en componentes más simples a menudo puede producir mejores resultados.
Al implementar estas mejores prácticas, puede crear una lógica de análisis de fecha y hora sólida que maneje con elegancia una amplia gama de entradas y proporcione comentarios claros cuando el análisis falla.