add DateBuilder

This commit is contained in:
Looly 2024-04-28 23:56:36 +08:00
parent 0790ba3d36
commit 4ce3926c9c
4 changed files with 614 additions and 27 deletions

View File

@ -0,0 +1,518 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.date;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* DateBuilder类用于构建和操作日期
* 该类提供了多个方法来设置年日等日期字段
* 以及获取构建的日期对象它是不可变的因此每个设置方法都会返回一个新的DateBuilder实例
*/
public final class DateBuilder {
private static final ZoneOffset DEFAULT_OFFSET = OffsetDateTime.now().getOffset();
/**
* 创建并返回一个DateBuilder实例
*
* @return 一个新的DateBuilder实例
*/
public static DateBuilder of() {
return new DateBuilder();
}
// region ----- fields
// 年份
private int year;
// 月份
private int month;
// 周数
private int week;
//
private int day;
// 小时
private int hour;
// 分钟
private int minute;
//
private int second;
// 纳秒
private int ns;
// Unix时间戳
private long unixsecond;
// 时区偏移量是否已设置
private boolean zoneOffsetSetted;
// 时区偏移量分钟
private int zoneOffset;
// 时区
private TimeZone zone;
// 上午标志
private boolean am;
// 下午标志
private boolean pm;
// endregion
// region ----- getters and setters
/**
* 获取年份
*
* @return 返回设置的年份
*/
public int getYear() {
return year;
}
/**
* 设置年份
*
* @param year 要设置的年份
* @return 返回DateBuilder实例支持链式调用
*/
public DateBuilder setYear(final int year) {
this.year = year;
return this;
}
/**
* 获取月份
*
* @return 返回设置的月份
*/
public int getMonth() {
return month;
}
/**
* 设置月份
*
* @param month 要设置的月份
* @return this
*/
public DateBuilder setMonth(final int month) {
this.month = month;
return this;
}
/**
* 获取当前周数的方法
*
* @return int 返回当前的周数
*/
public int getWeek() {
return week;
}
/**
* 设置日期构建器的周数
*
* @param week 指定的周数通常用于构建具体的日期对象
* @return this
*/
public DateBuilder setWeek(final int week) {
this.week = week;
return this;
}
/**
* 获取当前日期对象中的日部分
*
* @return 返回一个整数表示当前日期中的日
*/
public int getDay() {
return day;
}
/**
* 设置日期对象中的日部分
*
* @param day 指定要设置的日必须为整数
* @return this
*/
public DateBuilder setDay(final int day) {
this.day = day;
return this;
}
/**
* 获取当前日期对象中的小时数
*
* @return 小时数返回值类型为int
*/
public int getHour() {
return hour;
}
/**
* 设置日期对象中的小时数
*
* @param hour 要设置的小时数必须为整数
* @return this
*/
public DateBuilder setHour(final int hour) {
this.hour = hour;
return this;
}
/**
* 获取当前日期构建器中的分钟数
*
* @return 返回设置的分钟数类型为int
*/
public int getMinute() {
return minute;
}
/**
* 设置日期构建器中的分钟数
*
* @param minute 要设置的分钟数必须为整数
* @return this
*/
public DateBuilder setMinute(final int minute) {
this.minute = minute;
return this;
}
/**
* 获取当前日期时间对象中的秒数
*
* @return 返回当前日期时间对象中的秒数
*/
public int getSecond() {
return second;
}
/**
* 设置日期时间对象中的秒数
*
* @param second 指定要设置的秒数
* @return this
*/
public DateBuilder setSecond(final int second) {
this.second = second;
return this;
}
/**
* 获取纳秒数
*
* @return 当前对象的纳秒数
*/
public int getNs() {
return ns;
}
/**
* 设置纳秒数
*
* @param ns 要设置的纳秒数
* @return this
*/
public DateBuilder setNs(final int ns) {
this.ns = ns;
return this;
}
/**
* 获取Unix时间戳
*
* @return 当前对象的Unix时间戳以秒为单位
*/
public long getUnixsecond() {
return unixsecond;
}
/**
* 设置Unix时间戳
*
* @param unixsecond 要设置的Unix时间戳以秒为单位
* @return this
*/
public DateBuilder setUnixsecond(final long unixsecond) {
this.unixsecond = unixsecond;
return this;
}
/**
* 检查时区偏移量是否已设置
*
* @return 如果时区偏移量已设置则返回true否则返回false
*/
public boolean isZoneOffsetSetted() {
return zoneOffsetSetted;
}
/**
* 设置时区偏移量是否已设置的状态
*
* @param zoneOffsetSetted 指定时区偏移量是否已设置的状态
* @return this
*/
public DateBuilder setZoneOffsetSetted(final boolean zoneOffsetSetted) {
this.zoneOffsetSetted = zoneOffsetSetted;
return this;
}
/**
* 获取时区偏移量
*
* @return 返回设置的时区偏移量
*/
public int getZoneOffset() {
return zoneOffset;
}
/**
* 设置时区偏移量
*
* @param zoneOffset 要设置的时区偏移量
* @return this
*/
public DateBuilder setZoneOffset(final int zoneOffset) {
this.zoneOffset = zoneOffset;
return this;
}
/**
* 获取时区
*
* @return 返回设置的时区
*/
public TimeZone getZone() {
return zone;
}
/**
* 设置时区
*
* @param zone 要设置的时区
* @return this
*/
public DateBuilder setZone(final TimeZone zone) {
this.zone = zone;
return this;
}
/**
* 检查当前是否为上午
*
* @return 如果当前设置为上午则返回true否则返回false
*/
public boolean isAm() {
return am;
}
/**
* 设置是否为上午的状态
*
* @param am 指定是否为上午的状态
* @return this
*/
public DateBuilder setAm(final boolean am) {
this.am = am;
return this;
}
/**
* 检查当前是否为下午
*
* @return 如果当前设置为下午则返回true否则返回false
*/
public boolean isPm() {
return pm;
}
/**
* 设置是否为下午的状态
*
* @param pm 指定是否为下午的状态
* @return this
*/
public DateBuilder setPm(final boolean pm) {
this.pm = pm;
return this;
}
// endregion
/**
* 重置所有值为默认值
*
* @return this
*/
public DateBuilder reset() {
this.week = 1;
this.year = 0;
this.month = 1;
this.day = 1;
this.hour = 0;
this.minute = 0;
this.second = 0;
this.ns = 0;
this.unixsecond = 0;
this.am = false;
this.pm = false;
this.zoneOffsetSetted = false;
this.zoneOffset = 0;
this.zone = null;
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 Calendar}实例
* 如果`unixsecond`不为0将根据unix时间戳和纳秒偏移量构造Calendar
* 否则根据提供的时区信息`zone``zoneOffset`来设置Calendar的时区
* 最后设置年秒和毫秒信息
*
* @return Calendar 根据当前日期时间信息构建的Calendar实例
* @throws DateTimeException 如果时区偏移量无法转换为有效的时区ID则抛出异常
*/
public Calendar toCalendar() {
this.prepare();
final Calendar calendar = Calendar.getInstance(); // 获取一个Calendar实例
// 如果有unix时间戳则据此设置时间
if (unixsecond != 0) {
calendar.setTimeInMillis(unixsecond * 1000 + ns / 1_000_000); // 设置时间戳对应的毫秒数
return calendar;
}
// 设置时区
if (zone != null) {
calendar.setTimeZone(zone); // 使用指定的时区
} else if (zoneOffsetSetted) { // 如果设置了时区偏移量
final String[] ids = TimeZone.getAvailableIDs(zoneOffset * 60_000); // 尝试根据偏移量获取时区ID
if (ids.length == 0) { // 如果没有找到有效的时区ID
throw new DateException("Can't build Calendar, " +
"because the zoneOffset[{}] can't be converted to an valid TimeZone.", this.zoneOffset);
}
calendar.setTimeZone(TimeZone.getTimeZone(ids[0])); // 设置第一个找到的时区
}
// 设置日期和时间字段
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1); // Calendar的月份从0开始
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, ns / 1_000_000); // 纳秒转换为毫秒
return calendar;
}
/**
* 将当前对象的日期时间信息转换为{@link LocalDateTime}
* 此方法根据对象中的时间信息纳秒和时区信息如果存在
* 创建并返回一个LocalDateTime实例时区信息可以是Unix时间戳中的秒数unixsecond
* 也可以是显式设置的时区偏移量zoneOffsetSetted或者使用默认时区zone != null
*
* @return LocalDateTime 表示当前对象日期时间的LocalDateTime实例
*/
LocalDateTime toLocalDateTime() {
this.prepare();
// 如果unixsecond大于0使用unix时间戳创建LocalDateTime
if (unixsecond > 0) {
return LocalDateTime.ofEpochSecond(unixsecond, ns, DEFAULT_OFFSET);
}
// 创建LocalDateTime实例使用年月日时分秒纳秒信息
final LocalDateTime dateTime = LocalDateTime.of(year, month, day, hour, minute, second, ns);
int zoneSecond = 0; // 用于存储时区偏移秒数
// 如果设置了时区计算时区偏移量
if (zone != null) {
zoneSecond = (TimeZone.getDefault().getRawOffset() - zone.getRawOffset()) / 1000;
}
// 如果设置了显式的时区偏移量计算时区偏移量
if (zoneOffsetSetted) {
zoneSecond = TimeZone.getDefault().getRawOffset() / 1000 - zoneOffset * 60;
}
// 如果存在时区偏移对LocalDateTime进行调整后返回否则直接返回原始的LocalDateTime实例
return zoneSecond == 0 ? dateTime : dateTime.plusSeconds(zoneSecond);
}
/**
* 将当前对象转换为 {@link OffsetDateTime}
* 此方法根据 unixsecond时区偏移量或时区来构建 OffsetDateTime
* 如果 unixsecond 大于 0将使用 unixsecond 和纳秒来创建 UTC 时间
* 如果设置了时区偏移量将使用该偏移量构造 {@link OffsetDateTime}
* 如果设置了时区将使用该时区构造 {@link OffsetDateTime}
* 如果以上信息均未设置则默认使用 UTC 时间戳 0 创建 OffsetDateTime
*
* @return OffsetDateTime 表示当前时间的 OffsetDateTime 对象
*/
OffsetDateTime toOffsetDateTime() {
this.prepare(); // 准备工作可能涉及一些初始化或数据处理
if (unixsecond > 0) {
// 如果设置了 unix 时间戳则使用它和纳秒创建 UTC 时间
return OffsetDateTime.ofInstant(Instant.ofEpochSecond(unixsecond, ns), ZoneUtil.ZONE_ID_UTC);
}
final LocalDateTime dateTime = LocalDateTime.of(year, month, day, hour, minute, second, ns); // 创建 LocalDateTime 对象
// 检查是否设置了时区偏移量
if (zoneOffsetSetted) {
final ZoneOffset offset = ZoneOffset.ofHoursMinutes(zoneOffset / 60, zoneOffset % 60); // 根据偏移量创建 ZoneOffset
return dateTime.atOffset(offset); // 使用时区偏移量构造 OffsetDateTime
}
// 检查是否设置了时区
if (zone != null) {
return dateTime.atZone(zone.toZoneId()).toOffsetDateTime(); // 使用时区构造 OffsetDateTime
}
// 默认情况下使用 UTC 时间戳 0 创建 OffsetDateTime
return dateTime.atZone(ZoneOffset.ofHoursMinutes(0, 0)).toOffsetDateTime();
}
/**
* 根据上午am或下午pm的设置转换12小时制为24小时制
*/
private void prepare() {
// 如果设置为上午且小时为12将小时调整为0
if (am && hour == 12) {
this.hour = 0;
}
// 如果设置为下午且小时不是12将小时增加12
if (pm && hour != 12) {
this.hour += 12;
}
}
}

View File

@ -12,33 +12,73 @@
package org.dromara.hutool.core.date;
import org.dromara.hutool.core.exception.ExceptionUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.exception.HutoolException;
/**
* 工具类异常
* @author Looly
*/
public class DateException extends RuntimeException{
private static final long serialVersionUID = 8247610319171014183L;
public class DateException extends HutoolException {
private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param e 异常
*/
public DateException(final Throwable e) {
super(ExceptionUtil.getMessage(e), e);
super(e);
}
/**
* 构造
*
* @param message 消息
*/
public DateException(final String message) {
super(message);
}
/**
* 构造
*
* @param messageTemplate 消息模板
* @param params 参数
*/
public DateException(final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params));
super(messageTemplate, params);
}
public DateException(final String message, final Throwable throwable) {
super(message, throwable);
/**
* 构造
*
* @param message 消息
* @param cause 被包装的子异常
*/
public DateException(final String message, final Throwable cause) {
super(message, cause);
}
public DateException(final Throwable throwable, final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
/**
* 构造
*
* @param message 消息
* @param cause 被包装的子异常
* @param enableSuppression 是否启用抑制
* @param writableStackTrace 堆栈跟踪是否应该是可写的
*/
public DateException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
/**
* 构造
*
* @param cause 被包装的子异常
* @param messageTemplate 消息模板
* @param params 参数
*/
public DateException(final Throwable cause, final String messageTemplate, final Object... params) {
super(cause, messageTemplate, params);
}
}

View File

@ -13,7 +13,6 @@
package org.dromara.hutool.core.date;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.array.ArrayUtil;
import java.time.format.TextStyle;
import java.util.Calendar;
@ -93,10 +92,6 @@ public enum Month {
UNDECIMBER(Calendar.UNDECIMBER);
// ---------------------------------------------------------------
/**
* Months aliases.
*/
private static final String[] ALIASES = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
private static final Month[] ENUMS = Month.values();
/**
@ -192,12 +187,49 @@ public enum Month {
* @since 5.8.0
*/
public static Month of(final String name) throws IllegalArgumentException {
Assert.notBlank(name);
Month of = of(ArrayUtil.indexOfIgnoreCase(ALIASES, name));
if (null == of) {
of = Month.valueOf(name.toUpperCase());
if (null != name && name.length() > 2) {
switch (Character.toLowerCase(name.charAt(0))) {
case 'a':
switch (Character.toLowerCase(name.charAt(1))) {
case 'p':
return APRIL; // april
case 'u':
return AUGUST; // august
}
return of;
break;
case 'j':
if (Character.toLowerCase(name.charAt(1)) == 'a') {
return JANUARY; // january
}
switch (Character.toLowerCase(name.charAt(2))) {
case 'n':
return JUNE; // june
case 'l':
return JULY; // july
}
break;
case 'f':
return FEBRUARY; // february
case 'm':
switch (Character.toLowerCase(name.charAt(2))) {
case 'r':
return MARCH; // march
case 'y':
return MAY; // may
}
break;
case 's':
return SEPTEMBER; // september
case 'o':
return OCTOBER; // october
case 'n':
return NOVEMBER; // november
case 'd':
return DECEMBER; // december
}
}
throw new IllegalArgumentException("Invalid Month name: " + name);
}
/**

View File

@ -180,8 +180,7 @@ public enum Week {
*/
public static Week of(final String name) throws IllegalArgumentException {
if (null != name && name.length() > 1) {
final char firstChar = Character.toLowerCase(name.charAt(0));
switch (firstChar) {
switch (Character.toLowerCase(name.charAt(0))) {
case 'm':
return MONDAY; // monday
case 'w':
@ -189,8 +188,7 @@ public enum Week {
case 'f':
return FRIDAY; // friday
case 't':
final char secondChar = Character.toLowerCase(name.charAt(1));
switch (secondChar) {
switch (Character.toLowerCase(name.charAt(1))) {
case 'u':
return TUESDAY; // tuesday
case 'h':
@ -198,8 +196,7 @@ public enum Week {
}
break;
case 's':
final char secondChar2 = Character.toLowerCase(name.charAt(1));
switch (secondChar2) {
switch (Character.toLowerCase(name.charAt(1))) {
case 'a':
return SATURDAY; // saturday
case 'u':
@ -209,7 +206,7 @@ public enum Week {
}
}
throw new IllegalArgumentException("Invalid week name: " + name);
throw new IllegalArgumentException("Invalid Week name: " + name);
}
/**