Implementando as Melhores Práticas de Conversão (Parsing) de Data e Hora
Nesta etapa final, criaremos um conversor (parser) abrangente de data e hora que implementa as melhores práticas da indústria para lidar com valores de data e hora. Também exploraremos técnicas adicionais que os desenvolvedores Java profissionais usam para evitar e lidar com DateTimeParseException.
Criando um Conversor de Data e Hora Pronto para Produção
Vamos criar uma classe utilitária que incorpora todas as melhores práticas que aprendemos:
- No WebIDE, clique com o botão direito na pasta
/home/labex/project
- Selecione "New File" e nomeie-o como
DateTimeParserUtil.java
- Copie e cole o seguinte 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;
/**
* Classe utilitária para converter (parse) strings de data e hora de forma robusta.
*/
public class DateTimeParserUtil {
// Conversores (formatters) de data comuns com resolução (resolver) STRICT para validação
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")
);
// Conversores (formatters) de hora comuns
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")
);
// Conversores (formatters) de data e hora comuns
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")
);
/**
* Cria um DateTimeFormatter com estilo de resolução estrito.
*/
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);
}
/**
* Converte (parse) uma string em um LocalDate.
*
* @param dateStr a string de data a ser convertida (parsed)
* @return um Optional contendo o LocalDate convertido (parsed), ou vazio se a conversão (parsing) falhar
*/
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) {
// Tentar o próximo formatador
}
}
return Optional.empty();
}
/**
* Converte (parse) uma string em um LocalTime.
*
* @param timeStr a string de hora a ser convertida (parsed)
* @return um Optional contendo o LocalTime convertido (parsed), ou vazio se a conversão (parsing) falhar
*/
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) {
// Tentar o próximo formatador
}
}
return Optional.empty();
}
/**
* Converte (parse) uma string em um LocalDateTime.
*
* @param dateTimeStr a string de data e hora a ser convertida (parsed)
* @return um Optional contendo o LocalDateTime convertido (parsed), ou vazio se a conversão (parsing) falhar
*/
public static Optional<LocalDateTime> parseDateTime(String dateTimeStr) {
if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
return Optional.empty();
}
// Primeiro, tente com formatadores combinados de data e hora
for (DateTimeFormatter formatter : DATETIME_FORMATTERS) {
try {
LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, formatter);
return Optional.of(dateTime);
} catch (DateTimeParseException e) {
// Tentar o próximo formatador
}
}
// Em seguida, tente dividir em partes de data e hora
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();
}
/**
* Tenta converter (parse) uma string como um valor de data, hora ou data e hora.
*
* @param input a string a ser convertida (parsed)
* @return uma string descrevendo o resultado convertido (parsed) ou uma mensagem de erro
*/
public static String parseAny(String input) {
Optional<LocalDate> date = parseDate(input);
if (date.isPresent()) {
return "Convertido (parsed) como data: " + date.get();
}
Optional<LocalTime> time = parseTime(input);
if (time.isPresent()) {
return "Convertido (parsed) como hora: " + time.get();
}
Optional<LocalDateTime> dateTime = parseDateTime(input);
if (dateTime.isPresent()) {
return "Convertido (parsed) como data e hora: " + dateTime.get();
}
return "Não foi possível converter (parse): " + input;
}
}
Agora, vamos criar uma classe de teste para demonstrar a utilidade:
- No WebIDE, crie um novo arquivo chamado
DateTimeParsingDemo.java
- Copie e cole o seguinte código:
public class DateTimeParsingDemo {
public static void main(String[] args) {
// Teste com várias strings de data, hora e data e hora
String[] inputs = {
// Datas válidas
"2023-05-15",
"05/15/2023",
"15 May 2023",
// Horas válidas
"14:30:00",
"2:30 PM",
"14:30",
// Datas e horas válidas
"2023-05-15 14:30:00",
"2023-05-15T14:30:00",
"05/15/2023 14:30:00",
"15-05-2023 14:30:00",
// Exemplos inválidos
"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("Entrada: \"" + input + "\" → " + result);
}
}
}
Compile e execute este programa:
cd ~/project
javac DateTimeParserUtil.java DateTimeParsingDemo.java
java DateTimeParsingDemo
Você deve ver uma saída semelhante a:
Input: "2023-05-15" → Convertido (parsed) como data: 2023-05-15
Input: "05/15/2023" → Convertido (parsed) como data: 2023-05-15
Input: "15 May 2023" → Convertido (parsed) como data: 2023-05-15
Input: "14:30:00" → Convertido (parsed) como hora: 14:30
Input: "2:30 PM" → Convertido (parsed) como hora: 14:30
Input: "14:30" → Convertido (parsed) como hora: 14:30
Input: "2023-05-15 14:30:00" → Convertido (parsed) como data e hora: 2023-05-15T14:30
Input: "2023-05-15T14:30:00" → Convertido (parsed) como data e hora: 2023-05-15T14:30
Input: "05/15/2023 14:30:00" → Convertido (parsed) como data e hora: 2023-05-15T14:30
Input: "15-05-2023 14:30:00" → Convertido (parsed) como data e hora: 2023-05-15T14:30
Input: "2023-02-30" → Não foi possível converter (parse): 2023-02-30
Input: "25:30:00" → Não foi possível converter (parse): 25:30:00
Input: "2023-13-01" → Não foi possível converter (parse): 2023-13-01
Input: "Not a date or time" → Não foi possível converter (parse): Not a date or time
Input: "" → Não foi possível converter (parse):
Melhores Práticas para Conversão (Parsing) de Data e Hora em Java
Vamos examinar as principais melhores práticas implementadas em nossa classe utilitária:
1. Use a Configuração ResolverStyle.STRICT
.withResolverStyle(ResolverStyle.STRICT)
O estilo de resolução STRICT garante que apenas datas e horas válidas sejam aceitas. Isso evita problemas como converter (parse) "31 de fevereiro" para 3 de março.
2. Especifique uma Localidade Padrão
.withLocale(Locale.US)
Sempre especifique a localidade para garantir um comportamento de conversão (parsing) consistente, especialmente para nomes de meses e indicadores AM/PM.
3. Use o Tipo de Retorno Optional
public static Optional<LocalDate> parseDate(String dateStr) {
// ...
return Optional.empty();
}
Usar Optional comunica claramente que a conversão (parsing) pode falhar e força o código de chamada a lidar com esse caso explicitamente.
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd"),
createFormatter("yyyy/MM/dd"),
// ...
);
Suportar múltiplos formatos aumenta a robustez da sua lógica de conversão (parsing) ao lidar com várias fontes de entrada.
5. Valores de Tempo Ausentes Padrão
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
Definir valores padrão para campos de tempo ausentes permite uma conversão (parsing) mais flexível, especialmente ao converter entre diferentes tipos de data e hora.
6. Validação de Entrada
if (dateStr == null || dateStr.trim().isEmpty()) {
return Optional.empty();
}
Sempre valide a entrada antes de tentar converter (parse) para evitar exceções desnecessárias.
7. Tratamento de Erros Elegante
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
return Optional.of(date);
} catch (DateTimeParseException e) {
// Tentar o próximo formatador
}
Lide com exceções de forma elegante e forneça feedback claro quando a conversão (parsing) falhar.
8. Conversão (Parsing) Composta Inteligente
// Em seguida, tente dividir em partes de data e hora
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 tarefas complexas de conversão (parsing) em componentes mais simples pode frequentemente produzir melhores resultados.
Ao implementar essas melhores práticas, você pode criar uma lógica robusta de conversão (parsing) de data e hora que lida de forma elegante com uma ampla gama de entradas e fornece feedback claro quando a conversão (parsing) falha.