Реализация лучших практик преобразования даты и времени
На этом заключительном шаге мы создадим комплексный парсер даты и времени, который реализует лучшие отраслевые практики для обработки значений даты и времени. Мы также рассмотрим дополнительные методы, которые профессиональные Java-разработчики используют для избежания и обработки DateTimeParseException.
Создание готового к производству парсера даты и времени
Давайте создадим служебный класс, который включает в себя все изученные нами лучшие практики:
- В WebIDE щелкните правой кнопкой мыши по папке
/home/labex/project
- Выберите "New File" (Новый файл) и назовите его
DateTimeParserUtil.java
- Скопируйте и вставьте следующий код:
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;
}
}
Теперь давайте создадим тестовый класс, чтобы продемонстрировать утилиту:
- В WebIDE создайте новый файл с именем
DateTimeParsingDemo.java
- Скопируйте и вставьте следующий код:
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 гарантирует, что принимаются только допустимые даты и время. Это предотвращает такие проблемы, как преобразование "February 31" в 3 марта.
2. Укажите локаль по умолчанию
.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()));
}
}
Разбиение сложных задач преобразования на более простые компоненты часто может дать лучшие результаты.
Реализовав эти лучшие практики, вы можете создать надежную логику преобразования даты и времени, которая корректно обрабатывает широкий спектр входных данных и предоставляет четкую обратную связь в случае сбоя преобразования.