如何处理 'java.time.format.DateTimeParseException' 异常

JavaBeginner
立即练习

介绍

在 Java 应用中处理日期和时间数据时,开发者经常会遇到 java.time.format.DateTimeParseException。当应用尝试将字符串解析成日期或时间对象,但字符串格式与预期模式不匹配或包含无效值时,就会发生此异常。

在这个实验(Lab)中,你将学习如何识别 DateTimeParseException 的原因,实现有效的解决方案来解决它,并在你的 Java 应用中采用最佳实践,以实现可靠的日期和时间解析。

理解 DateTimeParseException

java.time.format.DateTimeParseException 是一个常见的运行时异常,它发生在 Java 应用中解析日期和时间字符串时。在我们能够有效地处理这个异常之前,我们需要理解是什么原因导致了它,以及如何识别它。

导致 DateTimeParseException 的原因是什么?

DateTimeParseException 通常由于以下原因之一而发生:

  1. 格式不匹配:输入字符串格式与 DateTimeFormatter 中指定的模式不匹配。
  2. 无效的日期/时间值:输入字符串包含表示无效日期或时间的值(例如 2 月 30 日)。
  3. 缺少或多余的元素:输入字符串可能缺少必需的元素,或包含意外的额外元素。

让我们创建一个简单的 Java 程序来演示一个典型的 DateTimeParseException 场景。首先,打开 WebIDE 并创建一个新的 Java 文件:

  1. 在左侧边栏中,导航到文件资源管理器部分
  2. 右键单击 /home/labex/project 文件夹
  3. 选择“新建文件”并将其命名为 ParseExceptionDemo.java

现在,将以下代码复制并粘贴到文件中:

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

要编译并运行此程序,请在 WebIDE 中打开一个终端并执行以下命令:

cd ~/project
javac ParseExceptionDemo.java
java ParseExceptionDemo

你应该看到类似于这样的输出:

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

分析 DateTimeParseException

让我们分析一下异常信息:

  1. 在第一个例子中,异常发生的原因是我们试图使用期望连字符(yyyy-MM-dd)的格式化程序来解析带有斜杠(2023/05/15)的日期。错误索引 4 指向第一个斜杠字符,这是解析失败的点。

  2. 在第二个例子中,异常发生的原因是 2 月 30 日在任何年份都不是一个有效的日期。格式匹配,但实际的日期值无效。

DateTimeParseException 提供了有用的信息来帮助诊断解析问题:

  • 错误消息描述了出了什么问题
  • getParsedString() 方法返回无法解析的输入字符串
  • getErrorIndex() 方法返回解析失败的位置

理解这些细节对于在你的应用中有效地调试和解决日期解析问题至关重要。

解决格式不匹配问题

DateTimeParseException 最常见的原因之一是输入字符串格式与 DateTimeFormatter 期望的格式不匹配。在本步骤中,我们将学习如何解决这种类型的问题。

使用正确的格式模式

在使用 DateTimeFormatter 时,指定一个与你的输入字符串的实际格式匹配的模式至关重要。Java 提供了一种灵活的模式语法,允许你精确地定义日期和时间的格式。

让我们创建一个新文件来演示如何解决格式不匹配问题:

  1. 在 WebIDE 中,右键单击 /home/labex/project 文件夹
  2. 选择“新建文件”并将其命名为 FormatMismatchSolution.java
  3. 复制并粘贴以下代码:
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");
    }
}

编译并运行此程序:

cd ~/project
javac FormatMismatchSolution.java
java FormatMismatchSolution

你应该看到类似于这样的输出:

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
----------------------------------------

理解 DateTimeFormatter 模式

DateTimeFormatter 使用模式字母来表示日期或时间的不同部分。以下是一些最常见的模式字母:

  • y:年(例如,yyyy 代表 2023)
  • M:月(例如,MM 代表 05,MMM 代表 May,MMMM 代表 May)
  • d:日(例如,dd 代表 15)
  • H:小时(0-23)(例如,HH 代表 14)
  • m:分钟(例如,mm 代表 30)
  • s:秒(例如,ss 代表 45)

你可以将这些模式字母与字面量(如斜杠、连字符、空格和逗号)结合起来,以匹配你的日期字符串的确切格式。

创建一个灵活的日期解析器

现在,让我们创建一个更灵活的日期解析器,它可以自动处理多种格式:

  1. 在 WebIDE 中,创建一个名为 FlexibleDateParser.java 的新文件
  2. 复制并粘贴以下代码:
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");
    }
}

编译并运行此程序:

cd ~/project
javac FlexibleDateParser.java
java FlexibleDateParser

你应该看到类似这样的输出:

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

这个灵活的日期解析器可以处理多种日期格式,使你的应用在处理来自各种来源的日期输入时更加健壮。它会按顺序尝试每个格式化程序,直到找到一个有效的格式化程序,或者尝试完所有可能性。

请记住,当你需要解析具有未知或可变格式的日期时,使用这种方法。

处理无效的日期值

DateTimeParseException 的另一个常见原因是输入字符串包含逻辑上无效的日期值,例如 2 月 30 日或 9 月 31 日。在本步骤中,我们将探讨如何有效地处理这些情况。

无效日期的问题

即使日期字符串的格式与指定的模式匹配,如果日期本身无效,解析也会失败。这是因为 Java 的日期时间 API 执行验证,以确保只能创建有效的日期。

让我们创建一个新文件来试验无效日期的处理:

  1. 在 WebIDE 中,右键单击 /home/labex/project 文件夹
  2. 选择“新建文件”并将其命名为 InvalidDateHandler.java
  3. 复制并粘贴以下代码:
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;
        }
    }
}

编译并运行此程序:

cd ~/project
javac InvalidDateHandler.java
java InvalidDateHandler

你应该看到类似于这样的输出:

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

实现一个健壮的日期解析器

现在,让我们创建一个更健壮的日期解析器,它可以优雅地处理格式不匹配和无效日期。我们将把上一步的灵活格式处理与无效日期验证结合起来:

  1. 在 WebIDE 中,创建一个名为 RobustDateParser.java 的新文件
  2. 复制并粘贴以下代码:
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();
    }
}

编译并运行此程序:

cd ~/project
javac RobustDateParser.java
java RobustDateParser

输出将显示哪些日期字符串可以被成功解析,哪些会导致错误:

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

处理无效日期的关键技术

此解决方案使用几种重要的技术来实现健壮的日期解析:

  1. 多格式支持:尝试使用不同的格式化程序进行解析,以处理各种输入格式
  2. Optional 返回类型:使用 Java 的 Optional 清楚地表明解析失败的时间
  3. 详细的错误日志记录:收集错误信息以用于调试目的
  4. 输入验证:在尝试解析之前检查空或空的输入
  5. 优雅的失败:返回一个空的 Optional 而不是抛出异常

通过实现这些技术,你的应用可以更健壮地处理日期解析,即使处理可能包含无效日期的用户输入或来自外部系统的数据也是如此。

实现日期时间解析的最佳实践

在最后一步,我们将创建一个综合的日期时间解析器,该解析器实现了行业最佳实践,用于处理日期和时间值。我们还将探讨专业 Java 开发者用来避免和处理 DateTimeParseException 的其他技术。

创建一个生产就绪的日期时间解析器

让我们创建一个实用程序类,该类结合了我们学到的所有最佳实践:

  1. 在 WebIDE 中,右键单击 /home/labex/project 文件夹
  2. 选择“新建文件”并将其命名为 DateTimeParserUtil.java
  3. 复制并粘贴以下代码:
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;
    }
}

现在,让我们创建一个测试类来演示该实用程序:

  1. 在 WebIDE 中,创建一个名为 DateTimeParsingDemo.java 的新文件
  2. 复制并粘贴以下代码:
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);
        }
    }
}

编译并运行此程序:

cd ~/project
javac DateTimeParserUtil.java DateTimeParsingDemo.java
java DateTimeParsingDemo

你应该看到类似于这样的输出:

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:

Java 中日期时间解析的最佳实践

让我们检查一下在我们的实用程序类中实现的关键最佳实践:

1. 使用 ResolverStyle.STRICT 设置

.withResolverStyle(ResolverStyle.STRICT)

STRICT 解析器样式确保仅接受有效的日期和时间。这可以防止诸如将“2 月 31 日”解析为 3 月 3 日之类的问题。

2. 指定默认的 Locale

.withLocale(Locale.US)

始终指定区域设置以确保一致的解析行为,尤其是在处理月份名称和 AM/PM 指示符时。

3. 使用 Optional 返回类型

public static Optional<LocalDate> parseDate(String dateStr) {
    // ...
    return Optional.empty();
}

使用 Optional 清楚地表明解析可能会失败,并强制调用代码显式处理这种情况。

4. 支持多种格式

private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
    createFormatter("yyyy-MM-dd"),
    createFormatter("yyyy/MM/dd"),
    // ...
);

在处理各种输入源时,支持多种格式可以提高解析逻辑的健壮性。

5. 默认缺少的时间字段

.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)

默认缺少的时间字段允许更灵活的解析,尤其是在不同日期时间类型之间进行转换时。

6. 输入验证

if (dateStr == null || dateStr.trim().isEmpty()) {
    return Optional.empty();
}

在尝试解析之前,始终验证输入以避免不必要的异常。

7. 优雅的错误处理

try {
    LocalDate date = LocalDate.parse(dateStr, formatter);
    return Optional.of(date);
} catch (DateTimeParseException e) {
    // Try next formatter
}

优雅地处理异常,并在解析失败时提供清晰的反馈。

8. 智能复合解析

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

将复杂的解析任务分解为更简单的组件通常可以产生更好的结果。

通过实施这些最佳实践,你可以创建健壮的日期和时间解析逻辑,该逻辑可以优雅地处理各种输入,并在解析失败时提供清晰的反馈。

总结

在这个实验中,你已经学会了如何在 Java 应用中有效地处理 java.time.format.DateTimeParseException。你获得了以下方面的实践经验:

  1. 理解 DateTimeParseException:你了解了此异常的常见原因,包括格式不匹配和无效的日期值。

  2. 解决格式不匹配问题:你实现了一个灵活的日期解析器,它可以处理多种日期格式,并优雅地处理格式不匹配的情况。

  3. 处理无效的日期值:你创建了一个健壮的解决方案,用于检测和处理无效的日期值,例如 2 月 30 日或 4 月 31 日。

  4. 实施最佳实践:你构建了一个综合的日期时间解析器实用程序,该程序结合了行业最佳实践,包括:

    • 使用 STRICT 解析器样式
    • 指定默认的区域设置
    • 返回 Optional 值
    • 支持多种格式
    • 验证输入
    • 实现优雅的错误处理
    • 分解复杂的解析任务

通过遵循这些实践,你可以在处理来自各种来源的日期和时间数据时,使你的 Java 应用更加健壮,从而减少意外异常的可能性,并为你的用户提供更好的体验。

请记住,有效的日期和时间解析对于许多应用至关重要,尤其是那些处理用户输入、数据导入或与外部系统集成的应用。你在本实验中学习的技术将帮助你在 Java 项目中更有效地处理这些场景。