From 4ce3926c9c13607058202706f27f8481982f7b45 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 28 Apr 2024 23:56:36 +0800 Subject: [PATCH] add DateBuilder --- .../dromara/hutool/core/date/DateBuilder.java | 518 ++++++++++++++++++ .../hutool/core/date/DateException.java | 60 +- .../org/dromara/hutool/core/date/Month.java | 52 +- .../org/dromara/hutool/core/date/Week.java | 11 +- 4 files changed, 614 insertions(+), 27 deletions(-) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/date/DateBuilder.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/date/DateBuilder.java b/hutool-core/src/main/java/org/dromara/hutool/core/date/DateBuilder.java new file mode 100644 index 000000000..9f8a4d3eb --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/date/DateBuilder.java @@ -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}类型。此方法根据是否设置了时区偏移量使用不同的转换策略。 + * + * 。 + * + * @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; + } + } + +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/date/DateException.java b/hutool-core/src/main/java/org/dromara/hutool/core/date/DateException.java index 892cbcaef..58ee5af55 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/date/DateException.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/date/DateException.java @@ -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); } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/date/Month.java b/hutool-core/src/main/java/org/dromara/hutool/core/date/Month.java index 4fb154a9e..e6cbda417 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/date/Month.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/date/Month.java @@ -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 + } + 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 + } } - return of; + + throw new IllegalArgumentException("Invalid Month name: " + name); } /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/date/Week.java b/hutool-core/src/main/java/org/dromara/hutool/core/date/Week.java index 57e6984b0..ac0c45ab9 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/date/Week.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/date/Week.java @@ -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); } /**