Mise en œuvre des meilleures pratiques d'analyse date-heure
Dans cette dernière étape, nous allons créer un analyseur date-heure complet qui met en œuvre les meilleures pratiques de l'industrie pour la gestion des valeurs de date et d'heure. Nous explorerons également des techniques supplémentaires que les développeurs Java professionnels utilisent pour éviter et gérer DateTimeParseException.
Création d'un analyseur date-heure prêt pour la production
Créons une classe utilitaire qui intègre toutes les meilleures pratiques que nous avons apprises :
- Dans le WebIDE, cliquez avec le bouton droit sur le dossier
/home/labex/project
- Sélectionnez "New File" et nommez-le
DateTimeParserUtil.java
- Copiez et collez le code suivant :
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;
}
}
Maintenant, créons une classe de test pour démontrer l'utilitaire :
- Dans le WebIDE, créez un nouveau fichier nommé
DateTimeParsingDemo.java
- Copiez et collez le code suivant :
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);
}
}
}
Compilez et exécutez ce programme :
cd ~/project
javac DateTimeParserUtil.java DateTimeParsingDemo.java
java DateTimeParsingDemo
Vous devriez voir une sortie similaire à :
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:
Meilleures pratiques pour l'analyse date-heure en Java
Examinons les principales meilleures pratiques mises en œuvre dans notre classe utilitaire :
1. Utiliser le paramètre ResolverStyle.STRICT
.withResolverStyle(ResolverStyle.STRICT)
Le style de résolveur STRICT garantit que seules les dates et heures valides sont acceptées. Cela évite les problèmes tels que l'analyse de "February 31" en mars 3.
2. Spécifier un paramètre régional par défaut
.withLocale(Locale.US)
Spécifiez toujours le paramètre régional pour garantir un comportement d'analyse cohérent, en particulier pour les noms de mois et les indicateurs AM/PM.
3. Utiliser le type de retour Optional
public static Optional<LocalDate> parseDate(String dateStr) {
// ...
return Optional.empty();
}
L'utilisation de Optional communique clairement que l'analyse peut échouer et oblige le code appelant à gérer explicitement ce cas.
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd"),
createFormatter("yyyy/MM/dd"),
// ...
);
La prise en charge de plusieurs formats augmente la robustesse de votre logique d'analyse lors du traitement de diverses sources d'entrée.
5. Valeurs par défaut des champs d'heure manquants
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
La définition par défaut des champs d'heure manquants permet une analyse plus flexible, en particulier lors de la conversion entre différents types de date-heure.
6. Validation des entrées
if (dateStr == null || dateStr.trim().isEmpty()) {
return Optional.empty();
}
Validez toujours l'entrée avant de tenter l'analyse pour éviter les exceptions inutiles.
7. Gestion gracieuse des erreurs
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
return Optional.of(date);
} catch (DateTimeParseException e) {
// Try next formatter
}
Gérez les exceptions avec élégance et fournissez des commentaires clairs en cas d'échec de l'analyse.
8. Analyse composite intelligente
// 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()));
}
}
La décomposition des tâches d'analyse complexes en composants plus simples peut souvent donner de meilleurs résultats.
En mettant en œuvre ces meilleures pratiques, vous pouvez créer une logique d'analyse de date et d'heure robuste qui gère avec élégance un large éventail d'entrées et fournit des commentaires clairs en cas d'échec de l'analyse.