This commit is contained in:
Looly 2024-06-27 18:36:37 +08:00
parent e56e10b75c
commit 85fac17eac
5 changed files with 129 additions and 23 deletions

View File

@ -53,6 +53,8 @@ public final class DateBuilder {
private int ns; private int ns;
// Unix时间戳 // Unix时间戳
private long unixsecond; private long unixsecond;
// 时间戳毫秒
private long millisecond;
// 时区偏移量是否已设置 // 时区偏移量是否已设置
private boolean zoneOffsetSetted; private boolean zoneOffsetSetted;
// 时区偏移量分钟 // 时区偏移量分钟
@ -247,6 +249,26 @@ public final class DateBuilder {
return this; return this;
} }
/**
* 获取时间戳毫秒
*
* @return 当前对象的时间戳以毫秒为单位
*/
public long getMillisecond() {
return millisecond;
}
/**
* 设置时间戳毫秒
*
* @param millisecond 要设置的时间戳以毫秒为单位
* @return this
*/
public DateBuilder setMillisecond(final long millisecond) {
this.millisecond = millisecond;
return this;
}
/** /**
* 检查时区偏移量是否已设置 * 检查时区偏移量是否已设置
* *
@ -363,6 +385,7 @@ public final class DateBuilder {
this.second = 0; this.second = 0;
this.ns = 0; this.ns = 0;
this.unixsecond = 0; this.unixsecond = 0;
this.millisecond = 0;
this.am = false; this.am = false;
this.pm = false; this.pm = false;
this.zoneOffsetSetted = false; this.zoneOffsetSetted = false;
@ -403,12 +426,6 @@ public final class DateBuilder {
this.prepare(); this.prepare();
final Calendar calendar = Calendar.getInstance(); // 获取一个Calendar实例 final Calendar calendar = Calendar.getInstance(); // 获取一个Calendar实例
// 如果有unix时间戳则据此设置时间
if (unixsecond != 0) {
calendar.setTimeInMillis(unixsecond * 1000 + ns / 1_000_000); // 设置时间戳对应的毫秒数
return calendar;
}
// 设置时区 // 设置时区
if (zone != null) { if (zone != null) {
calendar.setTimeZone(zone); // 使用指定的时区 calendar.setTimeZone(zone); // 使用指定的时区
@ -421,6 +438,18 @@ public final class DateBuilder {
calendar.setTimeZone(TimeZone.getTimeZone(ids[0])); // 设置第一个找到的时区 calendar.setTimeZone(TimeZone.getTimeZone(ids[0])); // 设置第一个找到的时区
} }
// 如果毫秒数不为0则直接使用毫秒数设置时间
if(millisecond != 0){
calendar.setTimeInMillis(millisecond);
return calendar;
}
// 如果有unix时间戳则据此设置时间
if (unixsecond != 0) {
calendar.setTimeInMillis(unixsecond * 1000 + ns / 1_000_000); // 设置时间戳对应的毫秒数
return calendar;
}
// 设置日期和时间字段 // 设置日期和时间字段
calendar.set(Calendar.YEAR, year); calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1); // Calendar的月份从0开始 calendar.set(Calendar.MONTH, month - 1); // Calendar的月份从0开始
@ -443,6 +472,11 @@ public final class DateBuilder {
LocalDateTime toLocalDateTime() { LocalDateTime toLocalDateTime() {
this.prepare(); this.prepare();
if(millisecond > 0){
final Instant instant = Instant.ofEpochMilli(millisecond);
return LocalDateTime.ofEpochSecond(instant.getEpochSecond(), instant.getNano(), DEFAULT_OFFSET);
}
// 如果unixsecond大于0使用unix时间戳创建LocalDateTime // 如果unixsecond大于0使用unix时间戳创建LocalDateTime
if (unixsecond > 0) { if (unixsecond > 0) {
return LocalDateTime.ofEpochSecond(unixsecond, ns, DEFAULT_OFFSET); return LocalDateTime.ofEpochSecond(unixsecond, ns, DEFAULT_OFFSET);
@ -480,6 +514,10 @@ public final class DateBuilder {
OffsetDateTime toOffsetDateTime() { OffsetDateTime toOffsetDateTime() {
this.prepare(); // 准备工作可能涉及一些初始化或数据处理 this.prepare(); // 准备工作可能涉及一些初始化或数据处理
if(millisecond > 0){
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(millisecond), ZoneUtil.ZONE_ID_UTC);
}
if (unixsecond > 0) { if (unixsecond > 0) {
// 如果设置了 unix 时间戳则使用它和纳秒创建 UTC 时间 // 如果设置了 unix 时间戳则使用它和纳秒创建 UTC 时间
return OffsetDateTime.ofInstant(Instant.ofEpochSecond(unixsecond, ns), ZoneUtil.ZONE_ID_UTC); return OffsetDateTime.ofInstant(Instant.ofEpochSecond(unixsecond, ns), ZoneUtil.ZONE_ID_UTC);

View File

@ -24,33 +24,41 @@ import java.util.regex.Pattern;
*/ */
public class GlobalRegexDateParser { public class GlobalRegexDateParser {
private static final String yearRegex = "(?<year>\\d{2,4})";
private static final String monthRegex = "(?<month>\\w{3,9})";
private static final String dayRegex = "(?<day>\\d{1,2})(?:th)?";
// 周的正则匹配Mon, Tue, Wed, Thu, Fri, Sat, Sun Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
// 日期中一般出现在头部可选
private static final String weekRegex = "(?<week>[mwfts][oeruha][ndieut](\\w{3,6})?\\W+)?";
// hh:mm:ss.SSSSZ hh:mm:ss.SSSS hh:mm:ss hh:mm // hh:mm:ss.SSSSZ hh:mm:ss.SSSS hh:mm:ss hh:mm
private static final String timeRegex = "(" + private static final String timeRegex = "(" +
"\\s(?<hour>\\d{1,2})" + "\\W+(?<hour>\\d{1,2})" +
":(?<minute>\\d{1,2})" + ":(?<minute>\\d{1,2})" +
"(:(?<second>\\d{1,2}))?" + "(:(?<second>\\d{1,2}))?" +
"(?:[.,](?<ns>\\d{1,9}))?(?<zero>z)?" + "(?:[.,](?<ns>\\d{1,9}))?(?<zero>z)?" +
"(\\s?(?<m>am|pm))?" + "(\\s?(?<m>[ap]m))?" +
")?"; ")?";
// +08:00 +0800 +08 // 时区类似 +08:00 +0800 +08可选
private static final String zoneOffsetRegex = "(\\s?(?<zoneOffset>[-+]\\d{1,2}:?(?:\\d{2})?))?"; private static final String zoneOffsetRegex = "(\\s?(?<zoneOffset>[-+]\\d{1,2}:?(?:\\d{2})?))?";
// CST UTC (CST) // 时区名称类似 CST UTC (CST)可选
private static final String zoneNameRegex = "(\\s[(]?(?<zoneName>[a-z ]+)[)]?)?"; private static final String zoneNameRegex = "(\\s[(]?(?<zoneName>[a-z ]+)[)]?)?";
private static final RegexDateParser PARSER; private static final RegexDateParser PARSER;
static { static {
final String dateRegexMonthFirst = "(?<month>\\w+{3,9})\\W+(?<day>\\d{1,2})(?:th)?\\W+(?<year>\\d{2,4})"; // 月开头类似May 8
final String dateRegexMonthFirst = monthRegex + "\\W+" + dayRegex;
PARSER = RegexDateParser.of( PARSER = RegexDateParser.of(
// 年开头 // 年开头
//开头类似May 8, 2009 5:57:51 //在前类似May 8, 2009时间部分可选类似5:57:515:57:51 +08:00
dateRegexMonthFirst + timeRegex + zoneOffsetRegex weekRegex + dateRegexMonthFirst + "\\W+" + yearRegex + timeRegex + zoneOffsetRegex,
// 年在最后类似Mon Jan 2 15:04:05 MST 2006
// 周开头 weekRegex + dateRegexMonthFirst + timeRegex + zoneNameRegex + zoneOffsetRegex + "\\W+" + yearRegex
// 日开头 // 日开头
); );
} }

View File

@ -16,6 +16,7 @@ import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.date.*; import org.dromara.hutool.core.date.*;
import org.dromara.hutool.core.lang.Opt; import org.dromara.hutool.core.lang.Opt;
import org.dromara.hutool.core.regex.ReUtil; import org.dromara.hutool.core.regex.ReUtil;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import java.io.Serializable; import java.io.Serializable;
@ -95,11 +96,13 @@ public class RegexDateParser implements DateParser, Serializable {
@Override @Override
public Date parse(final CharSequence source) throws DateException { public Date parse(final CharSequence source) throws DateException {
final DateBuilder dateBuilder = DateBuilder.of();
Matcher matcher; Matcher matcher;
for (final Pattern pattern : this.patterns) { for (final Pattern pattern : this.patterns) {
matcher = pattern.matcher(source); matcher = pattern.matcher(source);
if (matcher.matches()) { if (matcher.matches()) {
return parse(matcher); parse(matcher, dateBuilder);
return dateBuilder.toDate();
} }
} }
@ -110,17 +113,16 @@ public class RegexDateParser implements DateParser, Serializable {
* 解析日期 * 解析日期
* *
* @param matcher 正则匹配器 * @param matcher 正则匹配器
* @return 日期
* @throws DateException 日期解析异常 * @throws DateException 日期解析异常
*/ */
private static Date parse(final Matcher matcher) throws DateException { private static void parse(final Matcher matcher, final DateBuilder dateBuilder) throws DateException {
// 毫秒时间戳 // 毫秒时间戳
final String millisecond = ReUtil.group(matcher, "millisecond"); final String millisecond = ReUtil.group(matcher, "millisecond");
if (StrUtil.isNotEmpty(millisecond)) { if (StrUtil.isNotEmpty(millisecond)) {
return DateUtil.date(parseLong(millisecond)); dateBuilder.setMillisecond(parseLong(millisecond));
return;
} }
final DateBuilder dateBuilder = DateBuilder.of();
// year // year
Opt.ofNullable(ReUtil.group(matcher, "year")).ifPresent((year) -> dateBuilder.setYear(parseYear(year))); Opt.ofNullable(ReUtil.group(matcher, "year")).ifPresent((year) -> dateBuilder.setYear(parseYear(year)));
// month // month
@ -139,7 +141,7 @@ public class RegexDateParser implements DateParser, Serializable {
Opt.ofNullable(ReUtil.group(matcher, "ns")).ifPresent((ns) -> dateBuilder.setNs(parseNano(ns))); Opt.ofNullable(ReUtil.group(matcher, "ns")).ifPresent((ns) -> dateBuilder.setNs(parseNano(ns)));
// am or pm // am or pm
Opt.ofNullable(ReUtil.group(matcher, "m")).ifPresent((m) -> { Opt.ofNullable(ReUtil.group(matcher, "m")).ifPresent((m) -> {
if ('p' == m.charAt(0)) { if (CharUtil.equals('p', m.charAt(0), true)) {
dateBuilder.setPm(true); dateBuilder.setPm(true);
} else { } else {
dateBuilder.setAm(true); dateBuilder.setAm(true);
@ -158,12 +160,15 @@ public class RegexDateParser implements DateParser, Serializable {
dateBuilder.setZoneOffset(parseZoneOffset(zoneOffset)); dateBuilder.setZoneOffset(parseZoneOffset(zoneOffset));
}); });
// zone name
Opt.ofNullable(ReUtil.group(matcher, "zoneName")).ifPresent((zoneOffset) -> {
// 暂时不支持解析
});
// unix时间戳 // unix时间戳
Opt.ofNullable(ReUtil.group(matcher, "unixsecond")).ifPresent((unixsecond) -> { Opt.ofNullable(ReUtil.group(matcher, "unixsecond")).ifPresent((unixsecond) -> {
dateBuilder.setUnixsecond(parseLong(unixsecond)); dateBuilder.setUnixsecond(parseLong(unixsecond));
}); });
return dateBuilder.toDate();
} }
private static int parseYear(final String year) { private static int parseYear(final String year) {

View File

@ -0,0 +1,45 @@
package org.dromara.hutool.core.date.format.parser;
import org.dromara.hutool.core.date.DateUtil;
import org.junit.jupiter.api.Test;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class GlobalRegexDateParserTest {
@Test
void parseMonthFirstTest() {
assertParse("2009-05-08 05:57:51", "May 8, 2009 5:57:51");
assertParse("2009-05-08 17:57:51", "May 8, 2009 5:57:51 PM");
assertParse("2009-05-08 17:57:51", "May 8, 2009 5:57:51 pm");
assertParse("2009-05-08 17:57:51", "May 8, 2009 5:57:51pm");
assertParse("2009-05-08 05:57:51", "May 8, 2009 5:57:51 +08:00");
assertParse("2009-05-08 05:57:51", "May 8, 2009 5:57:51 +0800");
assertParse("2009-05-08 05:57:51", "May 8, 2009 5:57:51 +08");
assertParse("2009-05-08 00:00:00", "May 8, 2009");
assertParse("2009-05-08 00:00:00", "May 8th, 2009");
assertParse("2009-05-08 00:00:00", "May 8th, 09");
assertParse("2009-05-08 00:00:00", "may. 8th, 09");
}
@Test
void parseWeekFirstTest() {
assertParse("2006-01-02 15:04:05", "Mon Jan 2, 2006 15:04:05");
assertParse("2006-01-02 15:04:05", "Mon Jan 2 2006 15:04:05");
}
@Test
void parseWeekFirstYearLastTest() {
assertParse("2006-01-02 15:04:05", "Mon Jan 2 15:04:05 2006");
assertParse("2006-01-02 15:04:05", "Mon Jan 2 15:04:05 MST 2006");
assertParse("2006-01-03 06:04:05", "Mon Jan 02 15:04:05 -0700 2006");
//assertParse("2006-01-03 06:04:05", "Monday, 02-Jan-06 15:04:05 MST");
}
private static void assertParse(final String dateStr, final String dateStrToParse) {
final Date date = GlobalRegexDateParser.parse(dateStrToParse);
assertEquals(dateStr, DateUtil.date(date).toString());
}
}

View File

@ -1,6 +1,7 @@
package org.dromara.hutool.core.date.format.parser; package org.dromara.hutool.core.date.format.parser;
import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.date.Week;
import org.dromara.hutool.core.regex.ReUtil; import org.dromara.hutool.core.regex.ReUtil;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -59,6 +60,15 @@ public class RegexDateParserTest {
assertMatch(zoneNameRegex, " (GMT Daylight Time)"); assertMatch(zoneNameRegex, " (GMT Daylight Time)");
} }
@Test
void weekMatchTest() {
final String weekRegex = "(?<week>[mwfts][oeruha][ndieut](\\w{3,6})?)";
for (final Week week : Week.values()) {
assertMatch(weekRegex, week.name());
assertMatch(weekRegex, week.name().substring(0, 3));
}
}
@Test @Test
void parsePureTest() { void parsePureTest() {
// yyyyMMdd // yyyyMMdd