diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5bd8729..4ba3fcd7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,14 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.9 (2020-07-06) +## 5.3.9 (2020-07-07) ### 新特性 * 【core 】 DateUtil增加formatChineseDate(pr#932@Github) * 【core 】 ArrayUtil.isEmpty修改逻辑(pr#948@Github) * 【core 】 增强StrUtil中空判断后返回数据性能(pr#949@Github) -* 【core 】 deprecate掉millsecond,改为millisecond(issue#I1M9P8@Github) +* 【core 】 deprecate掉millsecond,改为millisecond(issue#I1M9P8@Gitee) +* 【core 】 增加LocalDateTimeUtil(issue#I1KUVC@Gitee) ### Bug修复 * 【core 】 修复NumberUtil.partValue有余数问题(issue#I1KX66@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java b/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java index ff0b3ae40..7f9cd1a41 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java @@ -76,6 +76,7 @@ public class DatePattern { * ISO8601日期时间格式,精确到毫秒:yyyy-MM-dd HH:mm:ss,SSS */ public static final String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS"; + /** * ISO8601日期时间格式,精确到毫秒 {@link FastDateFormat}:yyyy-MM-dd HH:mm:ss,SSS */ @@ -155,6 +156,15 @@ public class DatePattern { */ public static final FastDateFormat JDK_DATETIME_FORMAT = FastDateFormat.getInstance(JDK_DATETIME_PATTERN, Locale.US); + /** + * UTC时间:yyyy-MM-dd'T'HH:mm:ss + */ + public static final String UTC_SIMPLE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; + /** + * UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss + */ + public static final FastDateFormat UTC_SIMPLE_FORMAT = FastDateFormat.getInstance(UTC_SIMPLE_PATTERN, TimeZone.getTimeZone("UTC")); + /** * UTC时间:yyyy-MM-dd'T'HH:mm:ss'Z' */ diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 4e49267c1..e9cf32749 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -834,6 +834,9 @@ public class DateUtil extends CalendarUtil { } else if (length == DatePattern.UTC_MS_WITH_ZONE_OFFSET_PATTERN.length() + 2 || length == DatePattern.UTC_MS_WITH_ZONE_OFFSET_PATTERN.length() + 3) { // 格式类似:2018-09-13T05:34:31.999+0800 或 2018-09-13T05:34:31.999+08:00 return parse(utcString, DatePattern.UTC_MS_WITH_ZONE_OFFSET_FORMAT); + } else if(length == DatePattern.UTC_SIMPLE_PATTERN.length()-2){ + // 格式类似:2018-09-13T05:34:31 + return parse(utcString, DatePattern.UTC_SIMPLE_FORMAT); } } // 没有更多匹配的时间格式 @@ -1868,9 +1871,10 @@ public class DateUtil extends CalendarUtil { * @param instant {@link Instant} * @return {@link LocalDateTime} * @since 5.0.5 + * @see LocalDateTimeUtil#of(Instant) */ public static LocalDateTime toLocalDateTime(Instant instant) { - return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + return LocalDateTimeUtil.of(instant); } /** @@ -1879,10 +1883,10 @@ public class DateUtil extends CalendarUtil { * @param date {@link Date} * @return {@link LocalDateTime} * @since 5.0.5 + * @see LocalDateTimeUtil#of(Date) */ public static LocalDateTime toLocalDateTime(Date date) { - final DateTime dateTime = date(date); - return LocalDateTime.ofInstant(dateTime.toInstant(), dateTime.getZoneId()); + return LocalDateTimeUtil.of(date); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java new file mode 100644 index 000000000..f452fb9e7 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java @@ -0,0 +1,305 @@ +package cn.hutool.core.date; + +import cn.hutool.core.util.ObjectUtil; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalUnit; +import java.util.Date; +import java.util.TimeZone; + +/** + * JDK8+中的{@link LocalDateTime} 工具类封装 + * + * @author looly + * @since 5.3.9 + */ +public class LocalDateTimeUtil { + + /** + * 当前时间,默认时区 + * + * @return {@link LocalDateTime} + */ + public static LocalDateTime now() { + return LocalDateTime.now(); + } + + /** + * {@link Instant}转{@link LocalDateTime},使用默认时区 + * + * @param instant {@link Instant} + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(Instant instant) { + return of(instant, ZoneId.systemDefault()); + } + + /** + * {@link Instant}转{@link LocalDateTime},使用UTC时区 + * + * @param instant {@link Instant} + * @return {@link LocalDateTime} + */ + public static LocalDateTime ofUTC(Instant instant) { + return of(instant, ZoneId.of("UTC")); + } + + /** + * {@link ZonedDateTime}转{@link LocalDateTime} + * + * @param zonedDateTime {@link ZonedDateTime} + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(ZonedDateTime zonedDateTime) { + if (null == zonedDateTime) { + return null; + } + return zonedDateTime.toLocalDateTime(); + } + + /** + * {@link Instant}转{@link LocalDateTime} + * + * @param instant {@link Instant} + * @param zoneId 时区 + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(Instant instant, ZoneId zoneId) { + if (null == instant) { + return null; + } + + return LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault())); + } + + /** + * {@link Instant}转{@link LocalDateTime} + * + * @param instant {@link Instant} + * @param timeZone 时区 + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(Instant instant, TimeZone timeZone) { + if (null == instant) { + return null; + } + + return of(instant, ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()).toZoneId()); + } + + /** + * 毫秒转{@link LocalDateTime},使用默认时区 + * + *
注意:此方法使用默认时区,如果非UTC,会产生时间偏移
+ * + * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(long epochMilli) { + return of(Instant.ofEpochMilli(epochMilli)); + } + + /** + * 毫秒转{@link LocalDateTime},使用UTC时区 + * + * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 + * @return {@link LocalDateTime} + */ + public static LocalDateTime ofUTC(long epochMilli) { + return ofUTC(Instant.ofEpochMilli(epochMilli)); + } + + /** + * 毫秒转{@link LocalDateTime},根据时区不同,结果会产生时间偏移 + * + * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 + * @param zoneId 时区 + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(long epochMilli, ZoneId zoneId) { + return of(Instant.ofEpochMilli(epochMilli), zoneId); + } + + /** + * 毫秒转{@link LocalDateTime},结果会产生时间偏移 + * + * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 + * @param timeZone 时区 + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(long epochMilli, TimeZone timeZone) { + return of(Instant.ofEpochMilli(epochMilli), timeZone); + } + + /** + * {@link Date}转{@link LocalDateTime},使用默认时区 + * + * @param date Date对象 + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(Date date) { + if (null == date) { + return null; + } + + if (date instanceof DateTime) { + return of(date.toInstant(), ((DateTime) date).getZoneId()); + } + return of(date.toInstant()); + } + + /** + * {@link Date}转{@link LocalDateTime},使用默认时区 + * + * @param temporalAccessor {@link TemporalAccessor} + * @return {@link LocalDateTime} + */ + public static LocalDateTime of(TemporalAccessor temporalAccessor) { + if (null == temporalAccessor) { + return null; + } + + if(temporalAccessor instanceof LocalDate){ + return ((LocalDate)temporalAccessor).atStartOfDay(); + } + + return LocalDateTime.of( + TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR), + TemporalAccessorUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR), + TemporalAccessorUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH), + TemporalAccessorUtil.get(temporalAccessor, ChronoField.HOUR_OF_DAY), + TemporalAccessorUtil.get(temporalAccessor, ChronoField.MINUTE_OF_HOUR), + TemporalAccessorUtil.get(temporalAccessor, ChronoField.SECOND_OF_MINUTE), + TemporalAccessorUtil.get(temporalAccessor, ChronoField.NANO_OF_SECOND) + ); + } + + /** + * 解析日期时间字符串为{@link LocalDateTime},仅支持yyyy-MM-dd'T'HH:mm:ss格式,例如:2007-12-03T10:15:30 + * + * @param text 日期时间字符串 + * @return {@link LocalDateTime} + */ + public static LocalDateTime parse(CharSequence text) { + return parse(text, (DateTimeFormatter)null); + } + + /** + * 解析日期时间字符串为{@link LocalDateTime},格式支持日期时间、日期、时间 + * + * @param text 日期时间字符串 + * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter} + * @return {@link LocalDateTime} + */ + public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) { + if (null == text) { + return null; + } + if (null == formatter) { + return LocalDateTime.parse(text); + } + + return of(formatter.parse(text)); + } + + /** + * 解析日期时间字符串为{@link LocalDateTime} + * + * @param text 日期时间字符串 + * @param format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS + * @return {@link LocalDateTime} + */ + public static LocalDateTime parse(CharSequence text, String format) { + if (null == text) { + return null; + } + return parse(text, DateTimeFormatter.ofPattern(format)); + } + + /** + * 格式化日期时间为指定格式 + * + * @param time {@link LocalDateTime} + * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter} + * @return 格式化后的字符串 + */ + public static String format(LocalDateTime time, DateTimeFormatter formatter) { + if (null == time) { + return null; + } + return time.format(formatter); + } + + /** + * 格式化日期时间为指定格式 + * + * @param time {@link LocalDateTime} + * @param format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS + * @return 格式化后的字符串 + */ + public static String format(LocalDateTime time, String format) { + if (null == time) { + return null; + } + return format(time, DateTimeFormatter.ofPattern(format)); + } + + /** + * 日期偏移,根据field不同加不同值(偏移会修改传入的对象) + * + * @param time {@link LocalDateTime} + * @param number 偏移量,正数为向后偏移,负数为向前偏移 + * @param field 偏移单位,见{@link ChronoField},不能为null + * @return 偏移后的日期时间 + */ + public static LocalDateTime offset(LocalDateTime time, long number, TemporalUnit field) { + if (null == time) { + return null; + } + + return time.plus(number, field); + } + + /** + * 获取两个日期的差,如果结束时间早于开始时间,获取结果为负。 + *+ * 返回结果为{@link Duration}对象,通过调用toXXX方法返回相差单位 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 时间差 {@link Duration}对象 + */ + public static Duration between(LocalDateTime startTime, LocalDateTime endTime) { + return Duration.between(startTime, endTime); + } + + + /** + * 修改为一天的开始时间,例如:2020-02-02 00:00:00,000 + * + * @param time 日期时间 + * @return 一天的开始时间 + */ + public static LocalDateTime beginOfDay(LocalDateTime time) { + return time.with(LocalTime.of(0, 0, 0, 0)); + } + + /** + * 修改为一天的结束时间,例如:2020-02-02 23:59:59,999 + * + * @param time 日期时间 + * @return 一天的结束时间 + */ + public static LocalDateTime endOfDay(LocalDateTime time) { + return time.with(LocalTime.of(23, 59, 59, 999_999_999)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java new file mode 100644 index 000000000..8dc0a0d73 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java @@ -0,0 +1,28 @@ +package cn.hutool.core.date; + +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; + +/** + * {@link TemporalAccessor} 工具类封装 + * + * @author looly + * @since 5.3.9 + */ +public class TemporalAccessorUtil { + + /** + * 安全获取时间的某个属性,属性不存在返回0 + * + * @param temporalAccessor 需要获取的时间对象 + * @param field 需要获取的属性 + * @return 时间的值,如果无法获取则默认为 0 + */ + public static int get(TemporalAccessor temporalAccessor, TemporalField field) { + if (temporalAccessor.isSupported(field)) { + return temporalAccessor.get(field); + } + + return (int)field.range().getMinimum(); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java new file mode 100644 index 000000000..9fdcc59f9 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -0,0 +1,99 @@ +package cn.hutool.core.date; + +import org.junit.Assert; +import org.junit.Test; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +public class LocalDateTimeUtilTest { + + @Test + public void nowTest() { + Assert.assertNotNull(LocalDateTimeUtil.now()); + } + + @Test + public void ofTest() { + String dateStr = "2020-01-23T12:23:56"; + final DateTime dt = DateUtil.parse(dateStr); + + LocalDateTime of = LocalDateTimeUtil.of(dt); + Assert.assertNotNull(of); + Assert.assertEquals(dateStr, of.toString()); + + of = LocalDateTimeUtil.ofUTC(dt.getTime()); + Assert.assertEquals(dateStr, of.toString()); + } + + @Test + public void parseTest() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56", DateTimeFormatter.ISO_DATE_TIME); + Assert.assertEquals("2020-01-23T12:23:56", localDateTime.toString()); + } + + @Test + public void parseTest2() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN); + Assert.assertEquals("2020-01-23T00:00", localDateTime.toString()); + } + + @Test + public void parseTest3() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("12:23:56", DatePattern.NORM_TIME_PATTERN); + Assert.assertEquals("12:23:56", localDateTime.toLocalTime().toString()); + } + + @Test + public void parseTest4() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56"); + Assert.assertEquals("2020-01-23T12:23:56", localDateTime.toString()); + } + + @Test + public void formatTest() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56"); + String format = LocalDateTimeUtil.format(localDateTime, DatePattern.NORM_DATETIME_PATTERN); + Assert.assertEquals("2020-01-23 12:23:56", format); + + format = LocalDateTimeUtil.format(localDateTime, DatePattern.NORM_DATE_PATTERN); + Assert.assertEquals("2020-01-23", format); + } + + @Test + public void offset() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56"); + LocalDateTime offset = LocalDateTimeUtil.offset(localDateTime, 1, ChronoUnit.DAYS); + // 非同一对象 + Assert.assertNotSame(localDateTime, offset); + + Assert.assertEquals("2020-01-24T12:23:56", offset.toString()); + + offset = LocalDateTimeUtil.offset(localDateTime, -1, ChronoUnit.DAYS); + Assert.assertEquals("2020-01-22T12:23:56", offset.toString()); + } + + @Test + public void between() { + final Duration between = LocalDateTimeUtil.between( + LocalDateTimeUtil.parse("2019-02-02T00:00:00"), + LocalDateTimeUtil.parse("2020-02-02T00:00:00")); + Assert.assertEquals(365, between.toDays()); + } + + @Test + public void beginOfDayTest() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56"); + final LocalDateTime beginOfDay = LocalDateTimeUtil.beginOfDay(localDateTime); + Assert.assertEquals("2020-01-23T00:00", beginOfDay.toString()); + } + + @Test + public void endOfDayTest() { + final LocalDateTime localDateTime = LocalDateTimeUtil.parse("2020-01-23T12:23:56"); + final LocalDateTime endOfDay = LocalDateTimeUtil.endOfDay(localDateTime); + Assert.assertEquals("2020-01-23T23:59:59.999999999", endOfDay.toString()); + } +} \ No newline at end of file diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/redis/RedisDS.java b/hutool-db/src/main/java/cn/hutool/db/nosql/redis/RedisDS.java index 0f1779243..d32603fd1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/redis/RedisDS.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/redis/RedisDS.java @@ -1,8 +1,5 @@ package cn.hutool.db.nosql.redis; -import java.io.Closeable; -import java.io.IOException; - import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.setting.Setting; @@ -11,6 +8,8 @@ import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Protocol; +import java.io.Closeable; + /** * Jedis数据源 * @@ -174,7 +173,7 @@ public class RedisDS implements Closeable{ } @Override - public void close() throws IOException { + public void close() { IoUtil.close(pool); } }