diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java index e976a3a..42d360d 100644 --- a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java +++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java @@ -27,11 +27,11 @@ import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.chrono.IsoChronology; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; +import com.google.common.collect.BoundType; import com.google.common.collect.Range; import xyz.zhouxy.plusone.commons.time.Quarter; @@ -673,6 +673,44 @@ public class DateTimeTools { return Range.closedOpen(date.atStartOfDay(zone), startOfNextDate(date, zone)); } + /** + * 将指定日期范围转为日期时间范围 + * + * @param dateRange 日期范围 + * @return 对应的日期时间范围 + */ + public static Range toDateTimeRange(Range dateRange) { + BoundType lowerBoundType = dateRange.lowerBoundType(); + LocalDateTime lowerEndpoint = lowerBoundType == BoundType.CLOSED + ? dateRange.lowerEndpoint().atStartOfDay() + : dateRange.lowerEndpoint().plusDays(1).atStartOfDay(); + BoundType upperBoundType = dateRange.upperBoundType(); + LocalDateTime upperEndpoint = upperBoundType == BoundType.CLOSED + ? dateRange.upperEndpoint().plusDays(1).atStartOfDay() + : dateRange.upperEndpoint().atStartOfDay(); + + return Range.closedOpen(lowerEndpoint, upperEndpoint); + } + + /** + * 将指定日期范围转为日期时间范围 + * + * @param dateRange 日期范围 + * @return 对应的日期时间范围 + */ + public static Range toDateTimeRange(Range dateRange, ZoneId zone) { + BoundType lowerBoundType = dateRange.lowerBoundType(); + ZonedDateTime lowerEndpoint = lowerBoundType == BoundType.CLOSED + ? dateRange.lowerEndpoint().atStartOfDay(zone) + : dateRange.lowerEndpoint().plusDays(1).atStartOfDay(zone); + BoundType upperBoundType = dateRange.upperBoundType(); + ZonedDateTime upperEndpoint = upperBoundType == BoundType.CLOSED + ? dateRange.upperEndpoint().plusDays(1).atStartOfDay(zone) + : dateRange.upperEndpoint().atStartOfDay(zone); + + return Range.closedOpen(lowerEndpoint, upperEndpoint); + } + /** * 判断指定年份是否为闰年 * @@ -680,7 +718,7 @@ public class DateTimeTools { * @return 指定年份是否为闰年 */ public static boolean isLeapYear(int year) { - return IsoChronology.INSTANCE.isLeapYear(year); + return Year.isLeap(year); } // ================================ diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java index efca387..6d74b9c 100644 --- a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java +++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java @@ -43,6 +43,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.BoundType; import com.google.common.collect.Range; import xyz.zhouxy.plusone.commons.time.Quarter; @@ -83,24 +84,6 @@ class DateTimeToolsTests { CALENDAR.setTime(DATE); } - // Joda - static final org.joda.time.LocalDateTime JODA_LOCAL_DATE_TIME - = new org.joda.time.LocalDateTime(2024, 12, 29, 12, 58, 30, 333); - static final org.joda.time.LocalDate JODA_LOCAL_DATE = JODA_LOCAL_DATE_TIME.toLocalDate(); - static final org.joda.time.LocalTime JODA_LOCAL_TIME = JODA_LOCAL_DATE_TIME.toLocalTime(); - - // Joda - 2024-12-29 12:58:30.333 SystemDefaultZone - static final org.joda.time.DateTimeZone JODA_SYS_ZONE = org.joda.time.DateTimeZone.getDefault(); - static final org.joda.time.DateTime JODA_DATE_TIME_WITH_SYS_ZONE = JODA_LOCAL_DATE_TIME.toDateTime(JODA_SYS_ZONE); - static final org.joda.time.Instant JODA_INSTANT_WITH_SYS_ZONE = JODA_DATE_TIME_WITH_SYS_ZONE.toInstant(); - static final long JODA_INSTANT_MILLIS = JODA_INSTANT_WITH_SYS_ZONE.getMillis(); - - // Joda - 2024-12-29 12:58:30.333 GMT+04:00 - static final org.joda.time.DateTimeZone JODA_ZONE = org.joda.time.DateTimeZone.forID("GMT+04:00"); - static final org.joda.time.DateTime JODA_DATE_TIME = JODA_LOCAL_DATE_TIME.toDateTime(JODA_ZONE); - static final org.joda.time.Instant JODA_INSTANT = JODA_DATE_TIME.toInstant(); - static final long JODA_MILLIS = JODA_INSTANT.getMillis(); - // ================================ // #region - toDate // ================================ @@ -109,9 +92,7 @@ class DateTimeToolsTests { void toDate_timeMillis() { assertNotEquals(SYS_DATE, DATE); log.info("SYS_DATE: {}, DATE: {}", SYS_DATE, DATE); - assertEquals(SYS_DATE, JODA_DATE_TIME_WITH_SYS_ZONE.toDate()); assertEquals(SYS_DATE, DateTimeTools.toDate(INSTANT_MILLIS)); - assertEquals(DATE, JODA_DATE_TIME.toDate()); assertEquals(DATE, DateTimeTools.toDate(MILLIS)); } @@ -397,7 +378,7 @@ class DateTimeToolsTests { // ================================ @Test - void startDateTimeRange() { + void toDateTimeRange_specifiedDate() { Range localDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE); assertEquals(LOCAL_DATE.atStartOfDay(), localDateTimeRange.lowerEndpoint()); assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint()); @@ -411,6 +392,94 @@ class DateTimeToolsTests { assertFalse(zonedDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID))); } + @Test + void toDateTimeRange_dateRange_openRange() { + // (2000-01-01..2025-10-01) -> [2000-01-02T00:00..2025-10-01T00:00) + Range dateRange = Range.open( + LocalDate.of(2000, 1, 1), + LocalDate.of(2025, 10, 1)); + + Range localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange); + assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0), localDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0), localDateTimeRange.upperEndpoint()); + log.info(localDateTimeRange.toString()); + + Range zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID); + assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint()); + log.info(zonedDateTimeRange.toString()); + } + + @Test + void toDateTimeRange_dateRange_openClosedRange() { + // (2000-01-01..2025-10-01] -> [2025-01-02T00-00..2025-10-02 00:00) + Range dateRange = Range.openClosed( + LocalDate.of(2000, 1, 1), + LocalDate.of(2025, 10, 1)); + + Range localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange); + assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0), localDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0), localDateTimeRange.upperEndpoint()); + log.info(localDateTimeRange.toString()); + + Range zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID); + assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint()); + log.info(zonedDateTimeRange.toString()); + } + + @Test + void toDateTimeRange_dateRange_closedRange() { + // [2000-01-01..2025-10-01] -> [2025-01-01T00-00..2025-10-02 00:00) + Range dateRange = Range.closed( + LocalDate.of(2000, 1, 1), + LocalDate.of(2025, 10, 1)); + + Range localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange); + assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0), localDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0), localDateTimeRange.upperEndpoint()); + log.info(localDateTimeRange.toString()); + + Range zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID); + assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 2, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint()); + log.info(zonedDateTimeRange.toString()); + } + + @Test + void toDateTimeRange_dateRange_closedOpenRange() { + // [2025-01-01..2025-10-01) -> [2025-01-01T00-00..2025-10-01 00:00) + Range dateRange = Range.closedOpen( + LocalDate.of(2000, 1, 1), + LocalDate.of(2025, 10, 1)); + + Range localDateTimeRange = DateTimeTools.toDateTimeRange(dateRange); + assertEquals(BoundType.CLOSED, localDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0), localDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, localDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0), localDateTimeRange.upperEndpoint()); + log.info(localDateTimeRange.toString()); + + Range zonedDateTimeRange = DateTimeTools.toDateTimeRange(dateRange, ZONE_ID); + assertEquals(BoundType.CLOSED, zonedDateTimeRange.lowerBoundType()); + assertEquals(LocalDateTime.of(2000, 1, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.lowerEndpoint()); + assertEquals(BoundType.OPEN, zonedDateTimeRange.upperBoundType()); + assertEquals(LocalDateTime.of(2025, 10, 1, 0, 0).atZone(ZONE_ID), zonedDateTimeRange.upperEndpoint()); + log.info(zonedDateTimeRange.toString()); + } + // ================================ // #endregion - others // ================================