This commit is contained in:
Looly
2024-07-03 02:00:25 +08:00
parent aa8d66b81a
commit 71a59b645b
16 changed files with 151 additions and 139 deletions

View File

@@ -14,7 +14,6 @@ package org.dromara.hutool.core.date;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -402,42 +401,23 @@ public final class DateBuilder {
return this;
}
/**
* 将当前时间对象转换为{@link Date}类型。此方法根据是否设置了时区偏移量使用不同的转换策略。
* <ul>
* <li>如果时区偏移量未设置,则将时间转换为 Calendar 对象后获取其 Date 表现形式</li>
* <li>如果时区偏移量已设置,则直接转换为 OffsetDateTime 对象,进一步转换为 Instant 对象,最后转换为 Date 对象返回。</li>
* </ul>
* 。
*
* @return Date 表示当前时间的 Date 对象。
*/
public Date toDate() {
if (!zoneOffsetSetted) {
// 时区偏移量未设置,使用 Calendar 进行转换
return toCalendar().getTime();
}
// 时区偏移量已设置,直接转换为 Date 对象返回
return Date.from(toOffsetDateTime().toInstant());
}
/**
* 将当前时间对象转换为{@link DateTime}类型。此方法根据是否设置了时区偏移量使用不同的转换策略。
* <ul>
* <li>如果时区偏移量未设置,则将时间转换为 Calendar 对象后获取其 DateTime 表现形式</li>
* <li>如果时区偏移量已设置,则直接转换为 OffsetDateTime 对象,进一步转换为 Instant 对象,最后转换为 DateTime 对象返回。</li>
* <li>如果时区偏移量未设置,则通过{@link Calendar}创建{@link DateTime},无需转换时区。</li>
* <li>如果时区偏移量已设置,则经过OffsetDateTime转换为本地时区的{@link DateTime}。</li>
* </ul>
* 。
*
* @return DateTime 表示当前时间的 DateTime 对象。
*/
public DateTime toDateTime() {
public DateTime toDate() {
if (!zoneOffsetSetted) {
// 时区偏移量未设置,使用 Calendar 进行转换
return new DateTime(toCalendar());
// 时区偏移量未设置,使用 Calendar 进行转换,无需读取时区
return new DateTime(toCalendar().getTimeInMillis());
}
// 时区偏移量已设置,直接转换为 Date 对象返回
return new DateTime(toOffsetDateTime().toInstant());
return new DateTime(toOffsetDateTime().toInstant().toEpochMilli());
}
/**
@@ -495,7 +475,7 @@ public final class DateBuilder {
*
* @return LocalDateTime 表示当前对象日期时间的LocalDateTime实例。
*/
LocalDateTime toLocalDateTime() {
public LocalDateTime toLocalDateTime() {
this.prepare();
if (millisecond > 0) {
@@ -537,7 +517,7 @@ public final class DateBuilder {
*
* @return OffsetDateTime 表示当前时间的 OffsetDateTime 对象。
*/
OffsetDateTime toOffsetDateTime() {
public OffsetDateTime toOffsetDateTime() {
this.prepare(); // 准备工作,可能涉及一些初始化或数据处理
if (millisecond > 0) {

View File

@@ -38,7 +38,12 @@ import java.util.TimeZone;
/**
* 包装{@link Date}<br>
* 此类继承了{@link Date},并提供扩展方法,如时区等。<br>
* 此类重写了父类的{@code toString()}方法,返回值为"yyyy-MM-dd HH:mm:ss"格式
* 此类重写了父类的{@code toString()}方法,返回值为"yyyy-MM-dd HH:mm:ss"格式<br>
* 相对于{@link Date},此类定义了时区,用于标识日期所在时区,默认为当前时区
* <ul>
* <li>当使用默认时区时与LocalDateTime类似标识本地时间</li>
* <li>当使用指定时区时与ZonedDateTime类似标识某个地区的时间</li>
* </ul>
*
* @author Looly
*/
@@ -79,7 +84,7 @@ public class DateTime extends Date {
private int minimalDaysInFirstWeek;
/**
* 转换时间戳为 DateTime
* 转换时间戳为 DateTime,默认时区
*
* @param timeMillis 时间戳,毫秒数
* @return DateTime
@@ -90,7 +95,7 @@ public class DateTime extends Date {
}
/**
* 转换JDK date为 DateTime
* 转换JDK date为 DateTime,如果传入为原生对象,使用默认时区
*
* @param date JDK Date
* @return DateTime
@@ -103,7 +108,7 @@ public class DateTime extends Date {
}
/**
* 转换 {@link Calendar} 为 DateTime
* 转换 {@link Calendar} 为 DateTime,使用{@link Calendar}中指定的时区
*
* @param calendar {@link Calendar}
* @return DateTime
@@ -181,7 +186,7 @@ public class DateTime extends Date {
* @param calendar {@link Calendar},不能为{@code null}
*/
public DateTime(final Calendar calendar) {
this(calendar.getTime(), calendar.getTimeZone());
this(calendar.getTimeInMillis(), calendar.getTimeZone());
this.setFirstDayOfWeek(Week.of(calendar.getFirstDayOfWeek()));
}

View File

@@ -46,6 +46,7 @@ import java.util.stream.Collectors;
public class DateUtil extends CalendarUtil {
// region ----- date
/**
* 当前时间,转换为{@link DateTime}对象
*
@@ -801,7 +802,9 @@ public class DateUtil extends CalendarUtil {
}
/**
* 将日期字符串转换为{@link DateTime}对象,格式:<br>
* 将日期字符串转换为{@link DateTime}对象,在转换过程中,如果字符串中有时区信息,表示是指定时区的时间<br>
* 此时此方法会将时区转换为当前时区,时间戳会根据时区变化。<br>
* 支持格式:<br>
* <ol>
* <li>yyyy-MM-dd HH:mm:ss</li>
* <li>yyyy/MM/dd HH:mm:ss</li>
@@ -1709,6 +1712,7 @@ public class DateUtil extends CalendarUtil {
}
// region ----- range
/**
* 创建日期范围生成器
*
@@ -2071,70 +2075,4 @@ public class DateUtil extends CalendarUtil {
public static int getLastDayOfMonth(final Date date) {
return date(date).getLastDayOfMonth();
}
// ------------------------------------------------------------------------ Private method start
/**
* 标准化日期,默认处理以空格区分的日期时间格式,空格前为日期,空格后为时间:<br>
* 将以下字符替换为"-"
*
* <pre>
* "."
* "/"
* "年"
* "月"
* </pre>
* <p>
* 将以下字符去除
*
* <pre>
* "日"
* </pre>
* <p>
* 将以下字符替换为":"
*
* <pre>
* "时"
* "分"
* "秒"
* </pre>
* <p>
* 当末位是":"时去除之(不存在毫秒时)
*
* @param dateStr 日期时间字符串
* @return 格式化后的日期字符串
*/
private static String normalize(final CharSequence dateStr) {
if (StrUtil.isBlank(dateStr)) {
return StrUtil.str(dateStr);
}
// 日期时间分开处理
final List<String> dateAndTime = SplitUtil.splitTrim(dateStr, StrUtil.SPACE);
final int size = dateAndTime.size();
if (size < 1 || size > 2) {
// 非可被标准处理的格式
return StrUtil.str(dateStr);
}
final StringBuilder builder = StrUtil.builder();
// 日期部分("\"、"/"、"."、"年"、"月"都替换为"-"
String datePart = dateAndTime.get(0).replaceAll("[/.年月]", "-");
datePart = StrUtil.removeSuffix(datePart, "");
builder.append(datePart);
// 时间部分
if (size == 2) {
builder.append(' ');
String timePart = dateAndTime.get(1).replaceAll("[时分秒]", ":");
timePart = StrUtil.removeSuffix(timePart, ":");
//将ISO8601中的逗号替换为.
timePart = timePart.replace(',', '.');
builder.append(timePart);
}
return builder.toString();
}
// ------------------------------------------------------------------------ Private method end
}

View File

@@ -187,7 +187,7 @@ public enum Month {
* @since 5.8.0
*/
public static Month of(final String name) throws IllegalArgumentException {
if (null != name && name.length() > 2) {
if (null != name && name.length() > 1) {
switch (Character.toLowerCase(name.charAt(0))) {
case 'a':
switch (Character.toLowerCase(name.charAt(1))) {
@@ -226,6 +226,32 @@ public enum Month {
return NOVEMBER; // november
case 'd':
return DECEMBER; // december
case '一':
return JANUARY;
case '二':
return FEBRUARY;
case '三':
return MARCH;
case '四':
return APRIL;
case '五':
return MAY;
case '六':
return JUNE;
case '七':
return JULY;
case '八':
return AUGUST;
case '九':
return SEPTEMBER;
case '十':
switch (Character.toLowerCase(name.charAt(1))){
case '一':
return NOVEMBER;
case '二':
return DECEMBER;
}
return OCTOBER;
}
}
@@ -234,6 +260,7 @@ public enum Month {
/**
* {@link java.time.Month}转换为Month对象
*
* @param month {@link java.time.Month}
* @return Month
* @since 5.8.0

View File

@@ -28,7 +28,11 @@ import java.util.TimeZone;
import java.util.function.Function;
/**
* JDK8+中的{@link java.time} 工具类封装
* JDK8+中的{@link java.time} 工具类封装<br>
*
* <ul>
* <li>{@link LocalDateTime}表示一个本地时间,他始终表示当前时区的时间。</li>
* </ul>
*
* @author looly
* @see DateUtil java7及其以下版本使用Date工具类
@@ -55,7 +59,8 @@ public class TimeUtil extends TemporalAccessorUtil {
}
/**
* {@link Instant}转{@link LocalDateTime}使用UTC时区
* {@link Instant}转{@link LocalDateTime}使用UTC时区<br>
* 此方法自动将一个UTC时间转换为本地时间如传入00:00则结果为08:00
*
* @param instant {@link Instant}
* @return {@link LocalDateTime}
@@ -65,10 +70,12 @@ public class TimeUtil extends TemporalAccessorUtil {
}
/**
* {@link Instant}转{@link LocalDateTime}
* {@link Instant}转{@link LocalDateTime}<br>
* instant是一个无时区的时间戳在转换为本地时间时需指定这个时间戳所在时区<br>
* 如果所在时区与当前时区不同,会转换时间
*
* @param instant {@link Instant}
* @param zoneId 时区
* @param zoneId 时区,如果给定的时区与当前时区不同,会转换时间
* @return {@link LocalDateTime}
*/
public static LocalDateTime of(final Instant instant, final ZoneId zoneId) {
@@ -80,10 +87,12 @@ public class TimeUtil extends TemporalAccessorUtil {
}
/**
* {@link Instant}转{@link LocalDateTime}
* {@link Instant}转{@link LocalDateTime}<br>
* instant是一个无时区的时间戳在转换为本地时间时需指定这个时间戳所在时区<br>
* 如果所在时区与当前时区不同,会转换时间
*
* @param instant {@link Instant}
* @param timeZone 时区
* @param timeZone 时区,如果给定的时区与当前时区不同,会转换时间
* @return {@link LocalDateTime}
*/
public static LocalDateTime of(final Instant instant, final TimeZone timeZone) {
@@ -139,7 +148,8 @@ public class TimeUtil extends TemporalAccessorUtil {
}
/**
* {@link Date}转{@link LocalDateTime},使用默认时区
* {@link Date}转{@link LocalDateTime},使用默认时区<br>
* 如果为{@link DateTime}且提供了时区信息,则按照给定的时区转换为默认时区
*
* @param date Date对象
* @return {@link LocalDateTime}

View File

@@ -13,6 +13,7 @@
package org.dromara.hutool.core.date;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.util.ObjUtil;
import java.time.ZoneId;
import java.util.TimeZone;
@@ -80,10 +81,11 @@ public class ZoneUtil {
*
* @param rawOffset 偏移量
* @param timeUnit 偏移量单位
* @return 时区ID
* @return 时区ID,未找到返回{@link null}
*/
public static String getAvailableID(final int rawOffset, final TimeUnit timeUnit) {
final String[] availableIDs = TimeZone.getAvailableIDs((int) timeUnit.toMillis(rawOffset));
final String[] availableIDs = TimeZone.getAvailableIDs(
(int) ObjUtil.defaultIfNull(timeUnit, TimeUnit.MILLISECONDS).toMillis(rawOffset));
return ArrayUtil.isEmpty(availableIDs) ? null : availableIDs[0];
}
}

View File

@@ -31,12 +31,12 @@ public class GlobalRegexDateParser {
final String yearRegex = "(?<year>\\d{2,4})";
// 月的正则匹配Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
// 或 January, February, March, April, May, June, July, August, September, October, November, December
final String monthRegex = "(?<month>[jfmaasond][aepucoe][nbrylgptvc]\\w{0,6})";
final String monthRegex = "(?<month>[jfmaasond][aepucoe][nbrylgptvc]\\w{0,6}|[一二三四五六七八九十]{1,2}月)";
final String dayRegex = "(?<day>\\d{1,2})(?:th)?";
// 周的正则匹配Mon, Tue, Wed, Thu, Fri, Sat, Sun
// 或 Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
// 周一般出现在日期头部,可选
final String weekRegexWithSuff = "(?<week>[mwfts][oeruha][ndieut](\\w{3,6})?\\W+)?";
final String weekRegexWithSuff = "((?<week>[mwfts][oeruha][ndieut](\\w{3,6})?|(星期|周)[一二三四五六日])\\W+)?";
// hh:mm:ss.SSSSZ hh:mm:ss.SSSS hh:mm:ss hh:mm
final String timeRegexWithPre = "(" +
"(\\W+|T)(at\\s)?(?<hour>\\d{1,2})" +

View File

@@ -25,6 +25,8 @@ import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.text.dfa.WordTree;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -115,7 +117,31 @@ public class RegexDateParser implements DateParser, Serializable {
@Override
public Date parse(final CharSequence source) throws DateException {
Assert.notBlank(source, "Date str must be not blank!");
return parseToBuilder(source).toDateTime();
return parseToBuilder(source).toDate();
}
/**
* 解析日期,结果为{@link LocalDateTime}
*
* @param source 日期字符串
* @return {@link LocalDateTime}
* @throws DateException 解析异常
*/
public LocalDateTime parseToLocalDateTime(final CharSequence source) throws DateException {
Assert.notBlank(source, "Date str must be not blank!");
return parseToBuilder(source).toLocalDateTime();
}
/**
* 解析日期,结果为{@link OffsetDateTime}
*
* @param source 日期字符串
* @return {@link OffsetDateTime}
* @throws DateException 解析异常
*/
public OffsetDateTime parseToOffsetDateTime(final CharSequence source) throws DateException {
Assert.notBlank(source, "Date str must be not blank!");
return parseToBuilder(source).toOffsetDateTime();
}
/**

View File

@@ -12,6 +12,7 @@
package org.dromara.hutool.core.date;
import org.dromara.hutool.core.lang.Console;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -40,4 +41,16 @@ public class CalendarUtilTest {
DateUtil.date(calendar);
});
}
@Test
void setTimeZoneTest() {
// Calendar中设置时区并不会变化时间戳
final Calendar instance = Calendar.getInstance();
Console.log(instance.getTimeInMillis());
final long timeInMillis = instance.getTimeInMillis();
instance.setTimeZone(ZoneUtil.ZONE_UTC);
final long timeInMillis2 = instance.getTimeInMillis();
Assertions.assertEquals(timeInMillis, timeInMillis2);
}
}

View File

@@ -16,7 +16,7 @@ public class DateBuilderTest {
builder.setDay(1);
final Date date = builder.toDate();
Assertions.assertEquals("2019-10-01", DateUtil.date(date).toDateStr());
Assertions.assertEquals("2019-10-01 00:00:00", date.toString());
}
@Test
@@ -32,7 +32,7 @@ public class DateBuilderTest {
.setZone(TimeZone.getDefault());
final LocalDateTime dateTime = builder.toLocalDateTime();
Assertions.assertEquals("2019-10-01T10:20:30.900", builder.toLocalDateTime().toString());
Assertions.assertEquals("2019-10-01T10:20:30.900", dateTime.toString());
}
@Test

View File

@@ -15,6 +15,8 @@ package org.dromara.hutool.core.date;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.TimeZone;
/**
* DateTime单元测试
*
@@ -128,6 +130,17 @@ public class DateTimeTest {
Assertions.assertEquals("2017-01-05T12:34:23+08:00", dateStr);
}
@Test
public void toStringWithTimeZoneTest() {
final DateTime dateTime = new DateTime("2017-01-05 12:34:23", DatePattern.NORM_DATETIME_FORMAT);
final String dateStr = dateTime.toString(TimeZone.getTimeZone("UTC"));
Assertions.assertEquals("2017-01-05 04:34:23", dateStr);
dateTime.setTimeZone(TimeZone.getTimeZone("UTC"));
Assertions.assertEquals("2017-01-05 04:34:23", dateTime.toString());
}
@Test
public void monthTest() {
//noinspection ConstantConditions
@@ -138,7 +151,7 @@ public class DateTimeTest {
@Test
public void weekOfYearTest() {
final DateTime date = DateUtil.date(DateUtil.parse("2016-12-27"));
//noinspection ConstantConditions
Assertions.assertEquals(2016, date.year());
//跨年的周返回的总是1
Assertions.assertEquals(1, date.weekOfYear());

View File

@@ -18,7 +18,7 @@ public class IssueI9C2D4Test {
public void parseHttpTest2() {
final String dateStr = "星期四, 28 三月 2024 14:33:49 GMT";
final Date parse = DateUtil.parse(dateStr);
Assertions.assertEquals("2024-03-28 14:33:49", Objects.requireNonNull(parse).toString());
Assertions.assertEquals("2024-03-28 22:33:49", Objects.requireNonNull(parse).toString());
}
@Test

View File

@@ -77,5 +77,14 @@ public class MonthTest {
month = Month.of(java.time.Month.FEBRUARY);
Assertions.assertEquals(Month.FEBRUARY, month);
month = Month.of("二月");
Assertions.assertEquals(Month.FEBRUARY, month);
month = Month.of("十月");
Assertions.assertEquals(Month.OCTOBER, month);
month = Month.of("十一月");
Assertions.assertEquals(Month.NOVEMBER, month);
month = Month.of("十二月");
Assertions.assertEquals(Month.DECEMBER, month);
}
}

View File

@@ -19,7 +19,6 @@ import org.junit.jupiter.api.Test;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -37,7 +36,8 @@ public class TimeUtilTest {
@Test
public void ofTest() {
final String dateStr = "2020-01-23T12:23:56";
final Date dt = DateUtil.parse(dateStr);
final DateTime dt = DateUtil.parse(dateStr);
Console.log(dt.getTimeZone());
final LocalDateTime of = TimeUtil.of(dt);
Assertions.assertNotNull(of);
@@ -47,9 +47,10 @@ public class TimeUtilTest {
@Test
public void ofUTCTest() {
final String dateStr = "2020-01-23T12:23:56Z";
final Date dt = DateUtil.parse(dateStr);
// 因为`Z`位于末尾表示为UTC时间parse后会自动转换为本地时间
final DateTime dt = DateUtil.parse(dateStr);
final LocalDateTime of = TimeUtil.of(dt);
final LocalDateTime of = TimeUtil.of(dt.setTimeZone(ZoneUtil.ZONE_UTC));
final LocalDateTime of2 = TimeUtil.ofUTC(dt.getTime());
Assertions.assertNotNull(of);
Assertions.assertNotNull(of2);

View File

@@ -19,7 +19,8 @@ import java.time.ZoneId;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class ZoneUtilTest {
@@ -55,21 +56,6 @@ public class ZoneUtilTest {
assertNull(result, "Expected null TimeZone for invalid offset");
}
@Test
public void testGetTimeZoneByOffsetWithNullTimeUnitThrowsException() {
// Arrange
final int rawOffset = 8;
final TimeUnit timeUnit = null; // Null unit to simulate error condition
// Act & Assert
final NullPointerException thrown = assertThrows(
NullPointerException.class,
() -> ZoneUtil.getTimeZoneByOffset(rawOffset, timeUnit),
"Expected NullPointerException for null TimeUnit"
);
assertTrue(thrown.getMessage().contains("timeUnit"), "Exception message should mention the null parameter");
}
@Test
public void testGetAvailableIDWithInvalidOffset() {
// Test with an invalid offset that should result in null or an exception.

View File

@@ -63,6 +63,8 @@ public class GlobalRegexDateParserTest {
assertParse("2014-04-08 00:00:00", "2014年04月08日");
assertParse("2017-02-01 12:23:45", "2017年02月01日 12时23分45秒");
assertParse("2017-02-01 12:23:45", "2017年02月01日 12:23:45");
assertParse("2024-03-28 22:33:49", "星期四, 28 三月 2024 14:33:49 GMT");
assertParse("2024-03-28 22:33:49", "周四, 28 三月 2024 14:33:49 GMT");
}
@Test