diff --git a/CHANGELOG b/CHANGELOG
index 19d64d7..7c343e5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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增加等待所有线程结束并关闭的方法
diff --git a/README.md b/README.md
index 61af02d..09fd15e 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,6 @@
com.pugwoo
woo-utils
- 1.3.4
+ 1.3.5
```
diff --git a/pom.xml b/pom.xml
index 88c9938..f53ac52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.pugwoo
woo-utils
jar
- 1.3.4
+ 1.3.5
woo-utils
the common utils
diff --git a/src/main/java/com/pugwoo/wooutils/lang/DateUtils.java b/src/main/java/com/pugwoo/wooutils/lang/DateUtils.java
index 943ee7d..fa019f8 100644
--- a/src/main/java/com/pugwoo/wooutils/lang/DateUtils.java
+++ b/src/main/java/com/pugwoo/wooutils/lang/DateUtils.java
@@ -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 {
@@ -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()
@@ -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()
@@ -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()
@@ -648,4 +632,271 @@ public static LocalDate nextMonth() {
return today().plusMonths(1);
}
+
+ // ======================================= 新的LocalDateTime解析器 ===================== START =====================
+
+ public static final Map LOCAL_TIME_FORMATTER = new LinkedHashMap() {{
+ 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 LOCAL_DATE_IS_MONTH = new HashMap(){{
+ 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 LOCAL_DATE_FORMATTER = new LinkedHashMap() {{
+ 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 LOCAL_DATE_TIME_FORMATTER = new LinkedHashMap() {{
+
+ // 最常用的放前面,提高性能
+ 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 formatter : LOCAL_DATE_TIME_FORMATTER.entrySet()) {
+ if (dateString.matches(formatter.getKey())) {
+ return LocalDateTime.parse(dateString, formatter.getValue());
+ }
+ }
+
+ // 尝试用LocalDate解析,再转成LocalDateTime
+ for (Map.Entry 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 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 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 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);
+ }
+
+ /**
+ * 解析失败抛异常
+ * 特别说明,即便时间带有时区,也会被忽略,这符合LocalTime语义,如果需要时区,请使用OffsetTime类型
+ */
+ public static LocalTime parseLocalTimeThrowException(String dateString) throws ParseException {
+ if (StringTools.isBlank(dateString)) {
+ return null;
+ }
+ dateString = dateString.trim();
+ for (Map.Entry formatter : LOCAL_TIME_FORMATTER.entrySet()) {
+ if (dateString.matches(formatter.getKey())) {
+ return LocalTime.parse(dateString, formatter.getValue());
+ }
+ }
+
+ // 尝试解析成LocalDateTime,再转LocalTime
+ for (Map.Entry 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;
+ }
+ }
+
+ /**解析失败抛异常
+ * 特别说明,即便时间带有时区,也会被忽略,这符合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
+ * 特别说明,即便时间带有时区,也会被忽略,这符合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
+ * 特别说明,即便时间带有时区,也会被忽略,这符合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;
+ }
+ }
+
+ /**解析失败抛异常
+ * 特别说明,即便时间带有时区,也会被忽略,这符合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
+ * 特别说明,即便时间带有时区,也会被忽略,这符合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
+ * 特别说明,即便时间带有时区,也会被忽略,这符合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;
+ }
+ }
}