Skip to content

Commit

Permalink
Merge pull request #42 from pugwoo/develop-enhance-datetime-parse
Browse files Browse the repository at this point in the history
release 1.3.5
  • Loading branch information
pugwoo authored Nov 25, 2024
2 parents 9bad1b5 + 1e205be commit f5da4f9
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 22 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
2024年11月25日
v1.3.5 - [add] 重写了DateUtils对LocalDateTime和LocalDate、LocalTime的解析,覆盖ISO 8601所有格式

2024年10月18日
v1.3.4 - [add] ThreadPoolUtils增加等待所有线程结束并关闭的方法

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
<dependency>
<groupId>com.pugwoo</groupId>
<artifactId>woo-utils</artifactId>
<version>1.3.4</version>
<version>1.3.5</version>
</dependency>
```
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>com.pugwoo</groupId>
<artifactId>woo-utils</artifactId>
<packaging>jar</packaging>
<version>1.3.4</version>
<version>1.3.5</version>

<name>woo-utils</name>
<description>the common utils</description>
Expand Down
291 changes: 271 additions & 20 deletions src/main/java/com/pugwoo/wooutils/lang/DateUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.*;

/**
* 特别说明:Date是有时区的,默认使用操作系统的时区
* 特别说明:Date是有时区的,默认使用操作系统的时区。建议使用LocalDateTime,LocalDate,LocalTime,代替Date
*/
public class DateUtils {

Expand Down Expand Up @@ -138,11 +137,6 @@ private static Date tryParseTimestamp(String date) {
}
}

/**失败返回null,不会抛异常*/
public static LocalDateTime parseLocalDateTime(String date) {
return toLocalDateTime(parse(date));
}

public static LocalDateTime toLocalDateTime(Date date) {
if(date == null) {return null;}
// java.sql.Date和java.sql.Time不支持date.toInstant()
Expand All @@ -152,11 +146,6 @@ public static LocalDateTime toLocalDateTime(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}

/**失败返回null,不会抛异常*/
public static LocalDate parseLocalDate(String date) {
return toLocalDate(parse(date));
}

public static LocalDate toLocalDate(Date date) {
if(date == null) {return null;}
// java.sql.Date和java.sql.Time不支持date.toInstant()
Expand All @@ -166,11 +155,6 @@ public static LocalDate toLocalDate(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}

/**失败返回null,不会抛异常*/
public static LocalTime parseLocalTime(String date) {
return toLocalTime(parse(date));
}

public static LocalTime toLocalTime(Date date) {
if(date == null) {return null;}
// java.sql.Date和java.sql.Time不支持date.toInstant()
Expand Down Expand Up @@ -648,4 +632,271 @@ public static LocalDate nextMonth() {
return today().plusMonths(1);
}


// ======================================= 新的LocalDateTime解析器 ===================== START =====================

public static final Map<String, DateTimeFormatter> LOCAL_TIME_FORMATTER = new LinkedHashMap<String, DateTimeFormatter>() {{
put("^\\d{1,2}:\\d{1,2}:\\d{1,2}$", DateTimeFormatter.ofPattern("H:m:s")); // 16:34:32
put("^\\d{1,2}:\\d{1,2}$", DateTimeFormatter.ofPattern("H:m")); // 16:34
put("^\\d{1,2}:\\d{1,2}Z$", DateTimeFormatter.ofPattern("H:mX")); // 16:34Z

// 时间带纳秒部分
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.optionalStart().appendPattern("H:m:s").optionalEnd()
.optionalStart().appendPattern("HHmmss").optionalEnd()
.optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).optionalEnd() // 毫秒 纳秒 0-9位
.optionalStart().appendPattern("XXX").optionalEnd() // 支持 +00:00 格式
.optionalStart().appendPattern("xxxx").optionalEnd() // 支持 +0000 格式
.optionalStart().appendPattern("XX").optionalEnd() // 支持 +00 格式
.optionalStart().appendPattern("X").optionalEnd() // 支持 Z 格式
.optionalStart().appendPattern(" XXX").optionalEnd() // 支持 " +00:00" 格式
.optionalStart().appendPattern(" xxxx").optionalEnd() // 支持 " +0000" 格式
.toFormatter();
// 16:00:00[.纳秒1-9位][+00:00或+0000或Z] 16:00:00[.纳秒1-9位][+00:00或+0000或Z]
// 16:00:00[.纳秒1-9位][+00:00或+0000或Z] 16:00:00[.纳秒1-9位][+00:00或+0000或Z]
put("^\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d{0,9})?(Z|( ?[+-]\\d{2}:\\d{2})|( ?[+-](\\d{4}|\\d{2})))?$", formatter);
put("^\\d{6}(\\.\\d{0,9})?(Z|( ?[+-]\\d{2}:\\d{2})|( ?[+-](\\d{4}|\\d{2})))?$", formatter);
}};

public static final Map<String, Boolean> LOCAL_DATE_IS_MONTH = new HashMap<String, Boolean>(){{
put("^\\d{6}$", true); // 201703
put("^\\d{4}-\\d{1,2}$", true); // 2017-03
put("^\\d{4}/\\d{1,2}$", true); // 2017/03
put("^\\d{4}年\\d{1,2}月$", true); // 2017年03月
}};

public static final Map<String, DateTimeFormatter> LOCAL_DATE_FORMATTER = new LinkedHashMap<String, DateTimeFormatter>() {{
put("^\\d{4}-\\d{1,2}-\\d{1,2}$", DateTimeFormatter.ofPattern("yyyy-M-d")); // 2017-03-06
put("^\\d{4}/\\d{1,2}/\\d{1,2}$", DateTimeFormatter.ofPattern("yyyy/M/d")); // 2017/03/06
put("^\\d{8}$", DateTimeFormatter.ofPattern("yyyyMMdd")); // 20170306
put("^\\d{4}年\\d{1,2}月\\d{1,2}日$", DateTimeFormatter.ofPattern("yyyy年M月d日")); // 2017年03月30日

put("^\\d{6}$", DateTimeFormatter.ofPattern("yyyyMM-d")); // 201703
put("^\\d{4}-\\d{1,2}$", DateTimeFormatter.ofPattern("yyyy-M-d")); // 2017-03
put("^\\d{4}/\\d{1,2}$", DateTimeFormatter.ofPattern("yyyy/M-d")); // 2017/03
put("^\\d{4}年\\d{1,2}月$", DateTimeFormatter.ofPattern("yyyy年M月-d")); // 2017年03月
}};

public static final Map<String, DateTimeFormatter> LOCAL_DATE_TIME_FORMATTER = new LinkedHashMap<String, DateTimeFormatter>() {{

// 最常用的放前面,提高性能
put("^\\d{4}-\\d{1,2}-\\d{1,2}\\s\\d{1,2}:\\d{1,2}:\\d{1,2}$", DateTimeFormatter.ofPattern("yyyy-M-d H:m:s")); // 2017-03-06 15:23:56

// 只到分钟:2017-03-06 15:23 2017/03/06 15:23 2017-03-06T15:23 2017/03/06T15:23
DateTimeFormatter formatterMinute = new DateTimeFormatterBuilder()
.optionalStart().appendPattern("yyyy-M-d").optionalEnd()
.optionalStart().appendPattern("yyyy/M/d").optionalEnd()
.optionalStart().appendLiteral('T').optionalEnd()
.optionalStart().appendLiteral(' ').optionalEnd()
.appendPattern("H:m").toFormatter();
put("^\\d{4}(/\\d{1,2}/|-\\d{1,2}-)\\d{1,2}[T ]\\d{1,2}:\\d{1,2}$", formatterMinute);

// 其它
put("^\\d{14}$", DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); // 20170306152356

// 带毫秒纳秒的时间格式
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.optionalStart().appendPattern("yyyy-M-d").optionalEnd()
.optionalStart().appendPattern("yyyy/M/d").optionalEnd()
.optionalStart().appendPattern("yyyyMMdd").optionalEnd()
.optionalStart().appendLiteral('T').optionalEnd()
.optionalStart().appendLiteral(' ').optionalEnd()
.optionalStart().appendPattern("H:m:s").optionalEnd()
.optionalStart().appendPattern("HHmmss").optionalEnd()
.optionalStart().appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).optionalEnd() // 毫秒 纳秒 0-9位
.optionalStart().appendPattern("XXX").optionalEnd() // 支持 +00:00 格式
.optionalStart().appendPattern("xxxx").optionalEnd() // 支持 +0000 格式
.optionalStart().appendPattern("XX").optionalEnd() // 支持 +00 格式
.optionalStart().appendPattern("X").optionalEnd() // 支持 Z 格式
.optionalStart().appendPattern(" XXX").optionalEnd() // 支持 " +00:00" 格式
.optionalStart().appendPattern(" xxxx").optionalEnd() // 支持 " +0000" 格式
.toFormatter();
// 2017-10-18T16:00:00[.纳秒1-9位][+00:00或+0000或Z] 2017-10-18 16:00:00[.纳秒1-9位][+00:00或+0000或Z]
// 2017/10/18T16:00:00[.纳秒1-9位][+00:00或+0000或Z] 2017/10/18 16:00:00[.纳秒1-9位][+00:00或+0000或Z]
put("^\\d{4}(/\\d{1,2}/|-\\d{1,2}-)\\d{1,2}[T ]\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d{0,9})?(Z|( ?[+-]\\d{2}:\\d{2})|( ?[+-](\\d{4}|\\d{2})))?$", formatter);
// 20171018T160000[.纳秒1-9位][+00:00或+0000或Z] 20171018 160000[.纳秒1-9位][+00:00或+0000或Z]
put("^\\d{8}[T ]\\d{6}(\\.\\d{0,9})?(Z|( ?[+-]\\d{2}:\\d{2})|( ?[+-](\\d{4}|\\d{2})))?$", formatter);
put("^\\d{8}[T ]\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d{0,9})?(Z|( ?[+-]\\d{2}:\\d{2})|( ?[+-](\\d{4}|\\d{2})))?$", formatter);
put("^\\d{4}(/\\d{1,2}/|-\\d{1,2}-)\\d{1,2}[T ]\\d{6}(\\.\\d{0,9})?(Z|( ?[+-]\\d{2}:\\d{2})|( ?[+-](\\d{4}|\\d{2})))?$", formatter);
}};

/**解析失败抛异常*/
public static LocalDateTime parseLocalDateTimeThrowException(String dateString) throws ParseException {
if (StringTools.isBlank(dateString)) {
return null;
}
dateString = dateString.trim();
for (Map.Entry<String, DateTimeFormatter> formatter : LOCAL_DATE_TIME_FORMATTER.entrySet()) {
if (dateString.matches(formatter.getKey())) {
return LocalDateTime.parse(dateString, formatter.getValue());
}
}

// 尝试用LocalDate解析,再转成LocalDateTime
for (Map.Entry<String, DateTimeFormatter> formatter : LOCAL_DATE_FORMATTER.entrySet()) {
if (dateString.matches(formatter.getKey())) {
Boolean isMonth = LOCAL_DATE_IS_MONTH.get(formatter.getKey());
if (isMonth != null && isMonth) {
dateString = dateString + "-1";
}
LocalDate localDate = LocalDate.parse(dateString, formatter.getValue());
return localDate.atStartOfDay();
}
}

// 尝试用LocalTime解析,再转成LocalDateTime
for (Map.Entry<String, DateTimeFormatter> formatter : LOCAL_TIME_FORMATTER.entrySet()) {
if (dateString.matches(formatter.getKey())) {
LocalTime localTime = LocalTime.parse(dateString, formatter.getValue());
LocalDate localDate = LocalDate.of(0, 1, 1);
return LocalDateTime.of(localDate, localTime);
}
}

throw new ParseException("Parse failed. Unsupported pattern:" + dateString, 0);
}


/**解析失败抛异常*/
public static LocalDate parseLocalDateThrowException(String dateString) throws ParseException {
if (StringTools.isBlank(dateString)) {
return null;
}
dateString = dateString.trim();
for (Map.Entry<String, DateTimeFormatter> formatter : LOCAL_DATE_FORMATTER.entrySet()) {
if (dateString.matches(formatter.getKey())) {
Boolean isMonth = LOCAL_DATE_IS_MONTH.get(formatter.getKey());
if (isMonth != null && isMonth) {
dateString = dateString + "-1";
}
return LocalDate.parse(dateString, formatter.getValue());
}
}

// 尝试解析成LocalDateTime,再转LocalDate
for (Map.Entry<String, DateTimeFormatter> formatter : LOCAL_DATE_TIME_FORMATTER.entrySet()) {
if (dateString.matches(formatter.getKey())) {
LocalDateTime localDateTime = LocalDateTime.parse(dateString, formatter.getValue());
return localDateTime.toLocalDate();
}
}

throw new ParseException("Parse failed. Unsupported pattern:" + dateString, 0);
}

/**
* 解析失败抛异常<br>
* 特别说明,即便时间带有时区,也会被忽略,这符合LocalTime语义,如果需要时区,请使用OffsetTime类型
*/
public static LocalTime parseLocalTimeThrowException(String dateString) throws ParseException {
if (StringTools.isBlank(dateString)) {
return null;
}
dateString = dateString.trim();
for (Map.Entry<String, DateTimeFormatter> formatter : LOCAL_TIME_FORMATTER.entrySet()) {
if (dateString.matches(formatter.getKey())) {
return LocalTime.parse(dateString, formatter.getValue());
}
}

// 尝试解析成LocalDateTime,再转LocalTime
for (Map.Entry<String, DateTimeFormatter> formatter : LOCAL_DATE_TIME_FORMATTER.entrySet()) {
if (dateString.matches(formatter.getKey())) {
LocalDateTime localDateTime = LocalDateTime.parse(dateString, formatter.getValue());
return localDateTime.toLocalTime();
}
}

throw new ParseException("Parse failed. Unsupported pattern:" + dateString, 0);
}

/**解析失败抛异常*/
public static LocalDate parseLocalDateThrowException(String dateString, String pattern) throws ParseException {
if (StringTools.isBlank(dateString)) {
return null;
}
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(pattern));
}

/**解析失败不抛异常,返回null*/
public static LocalDate parseLocalDate(String dateString) {
try {
return parseLocalDateThrowException(dateString);
} catch (ParseException e) {
LOGGER.error("Parse LocalDate:{} failed", dateString, e);
return null;
}
}

/**解析失败不抛异常,返回null*/
public static LocalDate parseLocalDate(String dateString, String pattern) {
try {
return parseLocalDateThrowException(dateString, pattern);
} catch (ParseException e) {
LOGGER.error("Parse LocalDate:{} failed", dateString, e);
return null;
}
}

/**解析失败抛异常<br>
* 特别说明,即便时间带有时区,也会被忽略,这符合LocalDateTime语义,如果需要时区,请使用OffsetDateTime类型*/
public static LocalDateTime parseLocalDateTimeThrowException(String dateString, String pattern) throws ParseException {
if (StringTools.isBlank(dateString)) {
return null;
}
return LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(pattern));
}

/**解析失败不抛异常,返回null<br>
* 特别说明,即便时间带有时区,也会被忽略,这符合LocalDateTime语义,如果需要时区,请使用OffsetDateTime类型*/
public static LocalDateTime parseLocalDateTime(String dateString) {
try {
return parseLocalDateTimeThrowException(dateString);
} catch (ParseException e) {
LOGGER.error("Parse LocalDateTime:{} failed", dateString, e);
return null;
}
}

/**解析失败不抛异常,返回null<br>
* 特别说明,即便时间带有时区,也会被忽略,这符合LocalDateTime语义,如果需要时区,请使用OffsetDateTime类型*/
public static LocalDateTime parseLocalDateTime(String dateString, String pattern) {
try {
return parseLocalDateTimeThrowException(dateString, pattern);
} catch (ParseException e) {
LOGGER.error("Parse LocalDateTime:{} failed", dateString, e);
return null;
}
}

/**解析失败抛异常<br>
* 特别说明,即便时间带有时区,也会被忽略,这符合LocalTime语义,如果需要时区,请使用OffsetTime类型*/
public static LocalTime parseLocalTimeThrowException(String dateString, String pattern) throws ParseException {
if (StringTools.isBlank(dateString)) {
return null;
}
return LocalTime.parse(dateString, DateTimeFormatter.ofPattern(pattern));
}

/**解析失败不抛异常,返回null<br>
* 特别说明,即便时间带有时区,也会被忽略,这符合LocalTime语义,如果需要时区,请使用OffsetTime类型*/
public static LocalTime parseLocalTime(String dateString) {
try {
return parseLocalTimeThrowException(dateString);
} catch (ParseException e) {
LOGGER.error("Parse LocaTime:{} failed", dateString, e);
return null;
}
}

/**解析失败不抛异常,返回null<br>
* 特别说明,即便时间带有时区,也会被忽略,这符合LocalTime语义,如果需要时区,请使用OOffsetTime类型*/
public static LocalTime parseLocalTime(String dateString, String pattern) {
try {
return parseLocalTimeThrowException(dateString, pattern);
} catch (ParseException e) {
LOGGER.error("Parse LocalTime:{} failed", dateString, e);
return null;
}
}
}

0 comments on commit f5da4f9

Please sign in to comment.