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; + } + } }