Como Lidar com 'java.time.format.DateTimeParseException'

JavaBeginner
Pratique Agora

Introdução

Ao trabalhar com dados de data e hora em aplicações Java, os desenvolvedores frequentemente encontram a exceção java.time.format.DateTimeParseException. Esta exceção ocorre quando a aplicação tenta converter (parse) uma string em um objeto de data ou hora, mas o formato da string não corresponde ao padrão esperado ou contém valores inválidos.

Neste laboratório, você aprenderá como identificar as causas da DateTimeParseException, implementar soluções eficazes para resolvê-la e adotar as melhores práticas para a conversão (parsing) confiável de data e hora em suas aplicações Java.

Compreendendo a DateTimeParseException

A java.time.format.DateTimeParseException é uma exceção comum em tempo de execução que ocorre ao converter (parse) strings de data e hora em aplicações Java. Antes de podermos lidar efetivamente com esta exceção, precisamos entender o que a causa e como identificá-la.

O que causa a DateTimeParseException?

A DateTimeParseException normalmente ocorre por uma destas razões:

  1. Incompatibilidade de formato: O formato da string de entrada não corresponde ao padrão especificado no DateTimeFormatter
  2. Valores de data/hora inválidos: A string de entrada contém valores que representam uma data ou hora inválida (como 30 de fevereiro)
  3. Elementos ausentes ou extras: A string de entrada pode estar faltando elementos obrigatórios ou conter elementos adicionais inesperados

Vamos criar um programa Java simples para demonstrar um cenário típico de DateTimeParseException. Primeiro, abra o WebIDE e crie um novo arquivo Java:

  1. Na barra lateral esquerda, navegue até a seção do explorador de arquivos
  2. Clique com o botão direito na pasta /home/labex/project
  3. Selecione "New File" e nomeie-o como ParseExceptionDemo.java

Agora, copie e cole o seguinte código no arquivo:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class ParseExceptionDemo {
    public static void main(String[] args) {
        // Example 1: Format mismatch
        String dateStr1 = "2023/05/15";

        try {
            // Trying to parse with incorrect format (expects yyyy-MM-dd)
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            LocalDate date = LocalDate.parse(dateStr1, formatter);
            System.out.println("Parsed date: " + date);
        } catch (DateTimeParseException e) {
            System.out.println("Example 1 - Format mismatch error:");
            System.out.println("Exception: " + e.getClass().getName());
            System.out.println("Message: " + e.getMessage());
            System.out.println("Error position: " + e.getErrorIndex());
        }

        System.out.println("\n----------------------------------------\n");

        // Example 2: Invalid date value
        String dateStr2 = "2023-02-30";

        try {
            // February 30th is an invalid date
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            LocalDate date = LocalDate.parse(dateStr2, formatter);
            System.out.println("Parsed date: " + date);
        } catch (DateTimeParseException e) {
            System.out.println("Example 2 - Invalid date error:");
            System.out.println("Exception: " + e.getClass().getName());
            System.out.println("Message: " + e.getMessage());
            System.out.println("Input string: " + e.getParsedString());
        }
    }
}

Para compilar e executar este programa, abra um terminal no WebIDE e execute os seguintes comandos:

cd ~/project
javac ParseExceptionDemo.java
java ParseExceptionDemo

Você deve ver uma saída semelhante a esta:

Example 1 - Format mismatch error:
Exception: java.time.format.DateTimeParseException
Message: Text '2023/05/15' could not be parsed at index 4
Error position: 4

----------------------------------------

Example 2 - Invalid date error:
Exception: java.time.format.DateTimeParseException
Message: Text '2023-02-30' could not be parsed: Invalid date 'February 30'
Input string: 2023-02-30

Analisando a DateTimeParseException

Vamos analisar as informações da exceção:

  1. No primeiro exemplo, a exceção ocorre porque estamos tentando converter (parse) uma data com barras (2023/05/15) usando um formatador que espera hífens (yyyy-MM-dd). O índice de erro 4 aponta para o primeiro caractere de barra, que é o ponto onde a conversão (parsing) falhou.

  2. No segundo exemplo, a exceção ocorre porque 30 de fevereiro não é uma data válida em nenhum ano. O formato corresponde, mas o valor real da data é inválido.

A DateTimeParseException fornece informações úteis para ajudar a diagnosticar problemas de conversão (parsing):

  • A mensagem de erro descreve o que deu errado
  • O método getParsedString() retorna a string de entrada que não pôde ser convertida (parsed)
  • O método getErrorIndex() retorna a posição onde a conversão (parsing) falhou

Compreender esses detalhes é essencial para depurar e resolver efetivamente problemas de conversão (parsing) de data em suas aplicações.

Resolvendo Problemas de Incompatibilidade de Formato

Uma das causas mais comuns de DateTimeParseException é uma incompatibilidade entre o formato da string de entrada e o formato esperado pelo DateTimeFormatter. Nesta etapa, aprenderemos como resolver esse tipo de problema.

Usando o Padrão de Formato Correto

Ao usar DateTimeFormatter, é crucial especificar um padrão que corresponda ao formato real das suas strings de entrada. Java fornece uma sintaxe de padrão flexível que permite definir exatamente como suas datas e horas são formatadas.

Vamos criar um novo arquivo para demonstrar como resolver problemas de incompatibilidade de formato:

  1. No WebIDE, clique com o botão direito na pasta /home/labex/project
  2. Selecione "New File" e nomeie-o como FormatMismatchSolution.java
  3. Copie e cole o seguinte código:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class FormatMismatchSolution {
    public static void main(String[] args) {
        // Different date string formats
        String[] dateStrings = {
            "2023/05/15",  // format: yyyy/MM/dd
            "15-05-2023",  // format: dd-MM-yyyy
            "May 15, 2023" // format: MMMM d, yyyy
        };

        for (String dateStr : dateStrings) {
            parseWithCorrectFormatter(dateStr);
            System.out.println("----------------------------------------");
        }
    }

    private static void parseWithCorrectFormatter(String dateStr) {
        System.out.println("Trying to parse: " + dateStr);

        // Try with yyyy/MM/dd pattern
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
            LocalDate date = LocalDate.parse(dateStr, formatter);
            System.out.println("Success using 'yyyy/MM/dd': " + date);
            return;
        } catch (DateTimeParseException e) {
            System.out.println("Failed with 'yyyy/MM/dd' pattern");
        }

        // Try with dd-MM-yyyy pattern
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
            LocalDate date = LocalDate.parse(dateStr, formatter);
            System.out.println("Success using 'dd-MM-yyyy': " + date);
            return;
        } catch (DateTimeParseException e) {
            System.out.println("Failed with 'dd-MM-yyyy' pattern");
        }

        // Try with MMMM d, yyyy pattern
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy");
            LocalDate date = LocalDate.parse(dateStr, formatter);
            System.out.println("Success using 'MMMM d, yyyy': " + date);
            return;
        } catch (DateTimeParseException e) {
            System.out.println("Failed with 'MMMM d, yyyy' pattern");
        }

        System.out.println("Could not parse date string with any of the available formatters");
    }
}

Compile e execute este programa:

cd ~/project
javac FormatMismatchSolution.java
java FormatMismatchSolution

Você deve ver uma saída semelhante a esta:

Trying to parse: 2023/05/15
Success using 'yyyy/MM/dd': 2023-05-15
----------------------------------------
Trying to parse: 15-05-2023
Failed with 'yyyy/MM/dd' pattern
Success using 'dd-MM-yyyy': 2023-05-15
----------------------------------------
Trying to parse: May 15, 2023
Failed with 'yyyy/MM/dd' pattern
Failed with 'dd-MM-yyyy' pattern
Success using 'MMMM d, yyyy': 2023-05-15
----------------------------------------

Compreendendo os Padrões do DateTimeFormatter

O DateTimeFormatter usa letras de padrão para representar diferentes partes de uma data ou hora. Aqui estão algumas das letras de padrão mais comuns:

  • y: Ano (por exemplo, yyyy para 2023)
  • M: Mês (por exemplo, MM para 05, MMM para May, MMMM para May)
  • d: Dia do mês (por exemplo, dd para 15)
  • H: Hora do dia 0-23 (por exemplo, HH para 14)
  • m: Minuto (por exemplo, mm para 30)
  • s: Segundo (por exemplo, ss para 45)

Você pode combinar essas letras de padrão com literais (como barras, hífens, espaços e vírgulas) para corresponder ao formato exato das suas strings de data.

Criando um Conversor de Data Flexível

Agora, vamos criar um conversor de data mais flexível que pode lidar com vários formatos automaticamente:

  1. No WebIDE, crie um novo arquivo chamado FlexibleDateParser.java
  2. Copie e cole o seguinte código:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.List;

public class FlexibleDateParser {
    // List of common date formats
    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
        DateTimeFormatter.ofPattern("yyyy-MM-dd"),
        DateTimeFormatter.ofPattern("yyyy/MM/dd"),
        DateTimeFormatter.ofPattern("MM/dd/yyyy"),
        DateTimeFormatter.ofPattern("dd-MM-yyyy"),
        DateTimeFormatter.ofPattern("d MMM yyyy"),
        DateTimeFormatter.ofPattern("MMMM d, yyyy")
    );

    public static void main(String[] args) {
        // Test with different date formats
        String[] dates = {
            "2023-05-15",
            "2023/05/15",
            "05/15/2023",
            "15-05-2023",
            "15 May 2023",
            "May 15, 2023"
        };

        for (String dateStr : dates) {
            try {
                LocalDate date = parseDate(dateStr);
                System.out.println("Successfully parsed '" + dateStr + "' to: " + date);
            } catch (IllegalArgumentException e) {
                System.out.println("Failed to parse '" + dateStr + "': " + e.getMessage());
            }
        }
    }

    /**
     * Tries to parse a date string using multiple formatters
     * @param dateStr the date string to parse
     * @return the parsed LocalDate
     * @throws IllegalArgumentException if the string cannot be parsed with any formatter
     */
    public static LocalDate parseDate(String dateStr) {
        if (dateStr == null || dateStr.trim().isEmpty()) {
            throw new IllegalArgumentException("Date string cannot be null or empty");
        }

        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                // Try to parse with this formatter
                return LocalDate.parse(dateStr, formatter);
            } catch (DateTimeParseException e) {
                // This formatter didn't work, continue to the next one
                continue;
            }
        }

        // If we get here, none of the formatters worked
        throw new IllegalArgumentException("Cannot parse date: " + dateStr +
                                          ". Supported formats: yyyy-MM-dd, yyyy/MM/dd, " +
                                          "MM/dd/yyyy, dd-MM-yyyy, d MMM yyyy, MMMM d, yyyy");
    }
}

Compile e execute este programa:

cd ~/project
javac FlexibleDateParser.java
java FlexibleDateParser

Você deve ver uma saída como:

Successfully parsed '2023-05-15' to: 2023-05-15
Successfully parsed '2023/05/15' to: 2023-05-15
Successfully parsed '05/15/2023' to: 2023-05-15
Successfully parsed '15-05-2023' to: 2023-05-15
Successfully parsed '15 May 2023' to: 2023-05-15
Successfully parsed 'May 15, 2023' to: 2023-05-15

Este conversor de data flexível pode lidar com vários formatos de data, tornando sua aplicação mais robusta ao lidar com entradas de data de várias fontes. Ele tenta cada formatador em sequência até encontrar um que funcione ou esgote todas as possibilidades.

Lembre-se de usar essa abordagem quando precisar converter datas com formatos desconhecidos ou variáveis.

Lidando com Valores de Data Inválidos

Outra causa comum de DateTimeParseException é quando a string de entrada contém valores de data que são logicamente inválidos, como 30 de fevereiro ou 31 de setembro. Nesta etapa, exploraremos como lidar com esses casos de forma eficaz.

O Problema com Datas Inválidas

Mesmo quando o formato de uma string de data corresponde ao padrão especificado, a conversão (parsing) falhará se a própria data for inválida. Isso ocorre porque a API de data e hora do Java realiza a validação para garantir que apenas datas válidas possam ser criadas.

Vamos criar um novo arquivo para experimentar o tratamento de datas inválidas:

  1. No WebIDE, clique com o botão direito na pasta /home/labex/project
  2. Selecione "New File" e nomeie-o como InvalidDateHandler.java
  3. Copie e cole o seguinte código:
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class InvalidDateHandler {
    public static void main(String[] args) {
        // Array of date strings - some valid, some invalid
        String[] dateStrings = {
            "2023-04-30", // Valid - April has 30 days
            "2023-04-31", // Invalid - April has only 30 days
            "2023-02-28", // Valid - February 2023 has 28 days
            "2023-02-29", // Invalid - 2023 is not a leap year
            "2024-02-29", // Valid - 2024 is a leap year
            "2023-13-01"  // Invalid - Month 13 doesn't exist
        };

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        System.out.println("Testing dates with simple parsing:");
        for (String dateStr : dateStrings) {
            try {
                LocalDate date = LocalDate.parse(dateStr, formatter);
                System.out.println("Valid date: " + dateStr + " => " + date);
            } catch (DateTimeParseException e) {
                System.out.println("Invalid date: " + dateStr + " => " + e.getMessage());
            }
        }

        System.out.println("\nTesting dates with manual validation:");
        for (String dateStr : dateStrings) {
            if (isValidDate(dateStr)) {
                try {
                    LocalDate date = LocalDate.parse(dateStr, formatter);
                    System.out.println("Valid date: " + dateStr + " => " + date);
                } catch (DateTimeParseException e) {
                    // This should not happen if isValidDate returns true
                    System.out.println("Unexpected error for " + dateStr + ": " + e.getMessage());
                }
            } else {
                System.out.println("Invalid date detected: " + dateStr);
            }
        }
    }

    /**
     * Validates a date string by checking if it represents a valid date.
     *
     * @param dateStr the date string in yyyy-MM-dd format
     * @return true if the date is valid, false otherwise
     */
    public static boolean isValidDate(String dateStr) {
        try {
            // Split the date string into components
            String[] parts = dateStr.split("-");
            if (parts.length != 3) {
                return false;
            }

            int year = Integer.parseInt(parts[0]);
            int month = Integer.parseInt(parts[1]);
            int day = Integer.parseInt(parts[2]);

            // Check basic ranges
            if (year < 1 || month < 1 || month > 12 || day < 1 || day > 31) {
                return false;
            }

            // Validate the date by trying to create it
            LocalDate.of(year, month, day);
            return true;
        } catch (DateTimeException | NumberFormatException e) {
            // DateTimeException is thrown for invalid dates
            // NumberFormatException is thrown if parts aren't numbers
            return false;
        }
    }
}

Compile e execute este programa:

cd ~/project
javac InvalidDateHandler.java
java InvalidDateHandler

Você deve ver uma saída semelhante a:

Testing dates with simple parsing:
Valid date: 2023-04-30 => 2023-04-30
Invalid date: 2023-04-31 => Text '2023-04-31' could not be parsed: Invalid date 'April 31'
Valid date: 2023-02-28 => 2023-02-28
Invalid date: 2023-02-29 => Text '2023-02-29' could not be parsed: Invalid date 'February 29' as '2023' is not a leap year
Valid date: 2024-02-29 => 2024-02-29
Invalid date: 2023-13-01 => Text '2023-13-01' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 13

Testing dates with manual validation:
Valid date: 2023-04-30 => 2023-04-30
Invalid date detected: 2023-04-31
Valid date: 2023-02-28 => 2023-02-28
Invalid date detected: 2023-02-29
Valid date: 2024-02-29 => 2024-02-29
Invalid date detected: 2023-13-01

Implementando um Conversor de Data Robusto

Agora, vamos criar um conversor de data mais robusto que pode lidar com incompatibilidades de formato e datas inválidas de forma elegante. Combinaremos o tratamento de formato flexível da etapa anterior com a validação de data inválida:

  1. No WebIDE, crie um novo arquivo chamado RobustDateParser.java
  2. Copie e cole o seguinte código:
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class RobustDateParser {
    // List of common date formats
    private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
        DateTimeFormatter.ofPattern("yyyy-MM-dd"),
        DateTimeFormatter.ofPattern("yyyy/MM/dd"),
        DateTimeFormatter.ofPattern("MM/dd/yyyy"),
        DateTimeFormatter.ofPattern("dd-MM-yyyy"),
        DateTimeFormatter.ofPattern("d MMM yyyy"),
        DateTimeFormatter.ofPattern("MMMM d, yyyy")
    );

    public static void main(String[] args) {
        // Test with various dates, including invalid ones
        List<String> testDates = Arrays.asList(
            "2023-05-15",    // valid, standard format
            "2023/05/15",    // valid, with slashes
            "05/15/2023",    // valid, US format
            "15-05-2023",    // valid, European format
            "15 May 2023",   // valid, with month name
            "May 15, 2023",  // valid, with month name first
            "2023-02-29",    // invalid: 2023 is not a leap year
            "2023-04-31",    // invalid: April has 30 days
            "2023-13-01",    // invalid: month 13 doesn't exist
            "random text",   // invalid format
            ""               // empty string
        );

        for (String dateStr : testDates) {
            Optional<LocalDate> result = parseDate(dateStr);
            if (result.isPresent()) {
                System.out.println("Successfully parsed '" + dateStr + "' to: " + result.get());
            } else {
                System.out.println("Could not parse '" + dateStr + "' - Invalid or unsupported format");
            }
        }
    }

    /**
     * Attempts to parse a date string using multiple formatters.
     * Returns an Optional containing the parsed date if successful,
     * or an empty Optional if parsing fails with all formatters.
     *
     * @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) {
        // Guard against null or empty input
        if (dateStr == null || dateStr.trim().isEmpty()) {
            return Optional.empty();
        }

        List<String> errors = new ArrayList<>();

        // Try each formatter
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                LocalDate date = LocalDate.parse(dateStr, formatter);
                return Optional.of(date);
            } catch (DateTimeParseException e) {
                errors.add("Failed with pattern " + formatter.toString() + ": " + e.getMessage());
            }
        }

        // If we get here, all formatters failed
        System.out.println("Debug - All formatters failed for '" + dateStr + "'");
        for (String error : errors) {
            System.out.println("  " + error);
        }

        return Optional.empty();
    }
}

Compile e execute este programa:

cd ~/project
javac RobustDateParser.java
java RobustDateParser

A saída mostrará quais strings de data podem ser convertidas (parsed) com sucesso e quais causam erros:

Successfully parsed '2023-05-15' to: 2023-05-15
Successfully parsed '2023/05/15' to: 2023-05-15
Successfully parsed '05/15/2023' to: 2023-05-15
Successfully parsed '15-05-2023' to: 2023-05-15
Successfully parsed '15 May 2023' to: 2023-05-15
Successfully parsed 'May 15, 2023' to: 2023-05-15
Debug - All formatters failed for '2023-02-29'
  Failed with pattern ... Text '2023-02-29' could not be parsed: Invalid date 'February 29' as '2023' is not a leap year
  ...
Could not parse '2023-02-29' - Invalid or unsupported format
Debug - All formatters failed for '2023-04-31'
  ...
Could not parse '2023-04-31' - Invalid or unsupported format
Debug - All formatters failed for '2023-13-01'
  ...
Could not parse '2023-13-01' - Invalid or unsupported format
Debug - All formatters failed for 'random text'
  ...
Could not parse 'random text' - Invalid or unsupported format
Could not parse '' - Invalid or unsupported format

Técnicas Chave para Lidar com Datas Inválidas

Esta solução usa várias técnicas importantes para uma conversão (parsing) de data robusta:

  1. Suporte a múltiplos formatos: Tenta converter (parse) com diferentes formatadores para lidar com vários formatos de entrada
  2. Tipo de retorno Optional: Usa o Optional do Java para indicar claramente quando a conversão (parsing) falha
  3. Log de erros detalhado: Coleta informações de erro para fins de depuração
  4. Validação de entrada: Verifica se a entrada é nula ou vazia antes de tentar converter (parse)
  5. Falha elegante: Retorna um Optional vazio em vez de lançar exceções

Ao implementar essas técnicas, suas aplicações podem lidar com a conversão (parsing) de data de forma mais robusta, mesmo ao lidar com entrada do usuário ou dados de sistemas externos que podem conter datas inválidas.

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:

  1. No WebIDE, clique com o botão direito na pasta /home/labex/project
  2. Selecione "New File" e nomeie-o como DateTimeParserUtil.java
  3. 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:

  1. No WebIDE, crie um novo arquivo chamado DateTimeParsingDemo.java
  2. 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.

4. Suporte a Múltiplos Formatos

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.

Resumo

Neste laboratório, você aprendeu como lidar efetivamente com o java.time.format.DateTimeParseException em aplicações Java. Você adquiriu experiência prática em:

  1. Entendendo o DateTimeParseException: Você aprendeu sobre as causas comuns dessa exceção, incluindo incompatibilidades de formato e valores de data inválidos.

  2. Resolvendo Problemas de Incompatibilidade de Formato: Você implementou um conversor (parser) de data flexível que pode lidar com múltiplos formatos de data e lidar graciosamente com incompatibilidades de formato.

  3. Lidando com Valores de Data Inválidos: Você criou uma solução robusta para detectar e lidar com valores de data inválidos, como 30 de fevereiro ou 31 de abril.

  4. Implementando as Melhores Práticas: Você construiu um utilitário abrangente de conversão (parsing) de data e hora que incorpora as melhores práticas da indústria, incluindo:

    • Usar o estilo de resolução STRICT
    • Especificar localidades padrão
    • Retornar valores Optional
    • Suportar múltiplos formatos
    • Validar a entrada
    • Implementar tratamento de erros elegante
    • Dividir tarefas complexas de conversão (parsing)

Ao seguir essas práticas, você pode tornar suas aplicações Java mais robustas ao lidar com dados de data e hora de várias fontes, reduzindo a probabilidade de exceções inesperadas e proporcionando uma melhor experiência para seus usuários.

Lembre-se de que a conversão (parsing) eficaz de data e hora é crucial para muitas aplicações, especialmente aquelas que lidam com entrada do usuário, importações de dados ou integração com sistemas externos. As técnicas que você aprendeu neste laboratório o ajudarão a lidar com esses cenários de forma mais eficaz em seus projetos Java.