package com.tudicloud.framework.common.core.util.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 日期工具类,提供常用的日期解析、格式化、计算等操作。
* 支持传统 {@link Date} 和 Java 8 新时间 API(如 {@link LocalDateTime}, {@link LocalDate})的操作。
*
* @Author 孙达
* @Date 2025/11/14 14:08
* @Wechat sundaain
* @Email 18211102099@163.com
* @Copyright <a href="https://www.sundablog.com">孙达博客</a>
*/
public class DateUtils {
private static Logger logger = LoggerFactory.getLogger(DateUtils.class);
// ---------------------- pattern ----------------------
/**
* 标准日期时间格式:例如 "2020-12-28 10:00:00"
*/
public static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss";
/**
* 标准日期格式:例如 "2020-12-28"
*/
public static final String DATE = "yyyy-MM-dd";
/**
* 时间格式(含秒):例如 "10:00:00"
*/
public static final String TIME = "HH:mm:ss";
/**
* 时间格式(不含秒):例如 "10:00"
*/
public static final String TIME_WITHOUT_SECOND = "HH:mm";
// ---------------------- parse / format date ----------------------
/**
* 使用线程本地变量缓存不同模式的 DateFormat 实例,避免频繁创建对象以提升性能。
*/
private static final ThreadLocal<ConcurrentHashMap<String, DateFormat>> dateFormatThreadLocal = ThreadLocal.withInitial(ConcurrentHashMap::new);
/**
* 获取指定模式的 DateFormat 对象。如果该模式尚未存在,则会新建并放入缓存中。
*
* @param pattern 日期格式字符串,不能为空或空白字符
* @return 返回对应的 DateFormat 实例
* @throws IllegalArgumentException 当 pattern 为空时抛出异常
*/
private static DateFormat loadDateFormat(String pattern) {
if (pattern == null || pattern.trim().isEmpty()) {
throw new IllegalArgumentException("pattern cannot be empty.");
}
return dateFormatThreadLocal.get().computeIfAbsent(
pattern.trim(), (String k) -> {
return new SimpleDateFormat(pattern.trim(), Locale.getDefault());
}
);
}
/**
* 将字符串按照给定格式转换为 Date 类型。
*
* @param dateString 待解析的时间字符串
* @param pattern 解析使用的日期格式
* @return 转换后的 Date 对象
* @throws RuntimeException 如果解析失败则抛出运行时异常
*/
public static Date parse(String dateString, String pattern) {
try {
return loadDateFormat(pattern).parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("parse error.", e);
}
}
/**
* 按照标准日期时间格式 ("yyyy-MM-dd HH:mm:ss") 解析字符串为 Date。
*
* @param dateString 待解析的时间字符串
* @return 转换后的 Date 对象
*/
public static Date parseDateTime(String dateString) {
return parse(dateString, DATE_TIME);
}
/**
* 按照标准日期格式 ("yyyy-MM-dd") 解析字符串为 Date。
*
* @param dateString 待解析的日期字符串
* @return 转换后的 Date 对象
*/
public static Date parseDate(String dateString){
return parse(dateString, DATE);
}
/**
* 将 Date 对象按指定格式进行格式化输出。
*
* @param date 需要格式化的日期对象
* @param pattern 输出格式
* @return 格式化后的时间字符串
*/
public static String format(Date date, String pattern) {
return loadDateFormat(pattern).format(date);
}
/**
* 将 Date 对象格式化成标准日期格式 ("yyyy-MM-dd") 字符串。
*
* @param date 需要格式化的日期对象
* @return 格式化后的日期字符串
*/
public static String formatDate(Date date) {
return format(date, DATE);
}
/**
* 将 Date 对象格式化成标准日期时间格式 ("yyyy-MM-dd HH:mm:ss") 字符串。
*
* @param date 需要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String formatDateTime(Date date) {
return format(date, DATE_TIME);
}
// ---------------------- parse / format with DateTimeFormatter ----------------------
/**
* 将字符串按照指定格式解析为 LocalDateTime。
*
* @param dateString 待解析的日期时间字符串
* @param pattern 解析所用的格式
* @return 解析得到的 LocalDateTime 对象
*/
public static LocalDateTime parseLocalDateTime(String dateString, String pattern) {
return LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(pattern));
}
/**
* 将字符串按照指定格式解析为 LocalDate。
*
* @param dateString 待解析的日期字符串
* @param pattern 解析所用的格式
* @return 解析得到的 LocalDate 对象
*/
public static LocalDate parseLocalDate(String dateString, String pattern) {
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(pattern));
}
/**
* 将字符串按照指定格式解析为 LocalTime。
*
* @param dateString 待解析的时间字符串
* @param pattern 解析所用的格式
* @return 解析得到的 LocalTime 对象
*/
public static LocalTime parseLocalTime(String dateString, String pattern) {
return LocalTime.parse(dateString, DateTimeFormatter.ofPattern(pattern));
}
/**
* 将字符串按照指定格式解析为 YearMonth。
*
* @param dateString 待解析的年月字符串
* @param pattern 解析所用的格式
* @return 解析得到的 YearMonth 对象
*/
public static YearMonth parseYearMonth(String dateString, String pattern) {
return YearMonth.parse(dateString, DateTimeFormatter.ofPattern(pattern));
}
/**
* 将 Temporal 对象根据指定格式格式化为字符串。
*
* @param temporal 需要格式化的 Temporal 对象(支持 LocalDateTime、LocalDate、LocalTime、YearMonth)
* @param pattern 输出格式
* @return 格式化后的字符串
* @throws IllegalArgumentException 若传入不支持的类型将抛出异常
*/
public static String formatTemporal(Temporal temporal, String pattern) {
if (temporal instanceof LocalDateTime) {
LocalDateTime localDateTime = (LocalDateTime) temporal;
return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
} else if (temporal instanceof LocalDate) {
LocalDate localDate = (LocalDate) temporal;
return localDate.format(DateTimeFormatter.ofPattern(pattern));
} else if (temporal instanceof LocalTime) {
LocalTime localTime = (LocalTime) temporal;
return localTime.format(DateTimeFormatter.ofPattern(pattern));
} else if (temporal instanceof YearMonth) {
YearMonth yearMonth = (YearMonth) temporal;
return yearMonth.format(DateTimeFormatter.ofPattern(pattern));
} else {
throw new IllegalArgumentException("Unsupported temporal type: " + temporal.getClass().getName());
}
}
// ---------------------- plus by calendar field ----------------------
/**
* 在原日期基础上增加若干年份,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的年数
* @return 新的日期对象
*/
public static Date addYears(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusYears(amount);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 在原日期基础上增加若干月份,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的月份数量
* @return 新的日期对象
*/
public static Date addMonths(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusMonths(amount);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 在原日期基础上增加若干天数,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的天数
* @return 新的日期对象
*/
public static Date addDays(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusDays(amount);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 在原日期基础上增加若干小时,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的小时数
* @return 新的日期对象
*/
public static Date addHours(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusHours(amount);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 在原日期基础上增加若干分钟,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的分钟数
* @return 新的日期对象
*/
public static Date addMinutes(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusMinutes(amount);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 在原日期基础上增加若干秒,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的秒数
* @return 新的日期对象
*/
public static Date addSeconds(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusSeconds(amount);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 在原日期基础上增加若干周,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的周数
* @return 新的日期对象
*/
public static Date addWeeks(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusWeeks(amount);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 在原日期基础上增加若干毫秒,并返回新的 Date 对象。
*
* @param date 原始日期
* @param amount 增加的毫秒数
* @return 新的日期对象
*/
public static Date addMilliseconds(final Date date, final long amount) {
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime newDateTime = dateTime.plusNanos(amount * 1000000);
return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
// ---------------------- set by calendar field ----------------------
/**
* 设置日期中的某个字段值,并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @param calendarField 日历字段,如 Calendar.YEAR 等
* @param amount 要设置的新值
* @return 修改后的新日期对象
*/
public static Date set(final Date date, final int calendarField, final int amount) {
final Calendar c = Calendar.getInstance();
c.setLenient(false);
c.setTime(date);
c.set(calendarField, amount);
return c.getTime();
}
/**
* 设置年份,并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @param amount 年份值
* @return 修改后的新日期对象
*/
public static Date setYears(final Date date, final int amount) {
return set(date, Calendar.YEAR, amount);
}
/**
* 设置月份,并返回一个新的 Date 对象。
* 注意:月份从 0 开始计数(即一月是 0)。
*
* @param date 原始日期对象(不会被修改)
* @param amount 月份值(0 表示一月)
* @return 修改后的新日期对象
*/
public static Date setMonths(final Date date, final int amount) {
return set(date, Calendar.MONTH, amount);
}
/**
* 设置日,并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @param amount 日值
* @return 修改后的新日期对象
*/
public static Date setDays(final Date date, final int amount) {
return set(date, Calendar.DAY_OF_MONTH, amount);
}
/**
* 设置小时(24小时制),并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @param amount 小时值(范围 0~23)
* @return 修改后的新日期对象
*/
public static Date setHours(final Date date, final int amount) {
return set(date, Calendar.HOUR_OF_DAY, amount);
}
/**
* 设置分钟,并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @param amount 分钟值(范围 0~59)
* @return 修改后的新日期对象
*/
public static Date setMinutes(final Date date, final int amount) {
return set(date, Calendar.MINUTE, amount);
}
/**
* 设置秒,并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @param amount 秒值(范围 0~59)
* @return 修改后的新日期对象
*/
public static Date setSeconds(final Date date, final int amount) {
return set(date, Calendar.SECOND, amount);
}
/**
* 设置毫秒,并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @param amount 毫秒值(范围 0~999)
* @return 修改后的新日期对象
*/
public static Date setMilliseconds(final Date date, final int amount) {
return set(date, Calendar.MILLISECOND, amount);
}
/**
* 设置时间为当天开始时刻(00:00:00.000),并返回一个新的 Date 对象。
*
* @param date 原始日期对象(不会被修改)
* @return 当天起始时间的 Date 对象
*/
public static Date setStartOfDay(final Date date) {
final Calendar c = Calendar.getInstance();
c.setLenient(false);
c.setTime(date);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND,0);
return c.getTime();
}
// ---------------------- between ----------------------
/**
* 1 毫秒
*/
public static final long MILLIS_PER_MS = 1;
/**
* 每秒钟的毫秒数
*/
public static final long MILLIS_PER_SECOND = 1000;
/**
* 每分钟的毫秒数
*/
public static final long MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60;
/**
* 每小时的毫秒数
*/
public static final long MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60;
/**
* 每天的毫秒数
*/
public static final long MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;
/**
* 每周的毫秒数
*/
public static final long MILLIS_PER_WEEK = MILLIS_PER_DAY * 7;
/**
* 每个月(按30天计算)的毫秒数
*/
public static final long MILLIS_PER_MONTH_30 = MILLIS_PER_DAY * 30;
/**
* 每年(按365天计算)的毫秒数
*/
public static final long MILLIS_PER_YEAR_365 = MILLIS_PER_DAY * 365;
/**
* 计算两个日期之间的差值(单位由 calendarField 决定)。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @param calendarField 时间单位(如 Calendar.YEAR、Calendar.MONTH 等)
* @return 差值结果(单位取决于 calendarField)
* @throws IllegalArgumentException 如果任一日期为 null 或者 calendarField 不受支持
*/
private static long between(Date beginDate, Date endDate, int calendarField) {
if (beginDate == null || endDate == null) {
throw new IllegalArgumentException("date must not be null");
}
long unitMillis;
switch (calendarField) {
case Calendar.YEAR:
unitMillis = MILLIS_PER_YEAR_365;
break;
case Calendar.MONTH:
unitMillis = MILLIS_PER_MONTH_30;
break;
case Calendar.DAY_OF_MONTH:
case Calendar.DAY_OF_WEEK:
case Calendar.DAY_OF_YEAR:
unitMillis = MILLIS_PER_DAY;
break;
case Calendar.HOUR:
case Calendar.HOUR_OF_DAY:
unitMillis = MILLIS_PER_HOUR;
break;
case Calendar.MINUTE:
unitMillis = MILLIS_PER_MINUTE;
break;
case Calendar.SECOND:
unitMillis = MILLIS_PER_SECOND;
break;
case Calendar.MILLISECOND:
unitMillis = MILLIS_PER_MS;
break;
case Calendar.WEEK_OF_YEAR:
case Calendar.WEEK_OF_MONTH:
unitMillis = MILLIS_PER_WEEK;
break;
default:
throw new IllegalArgumentException("Unsupported time unit: " + calendarField);
}
long diffMillis = endDate.getTime() - beginDate.getTime();
return diffMillis / unitMillis;
}
/**
* 计算两个日期之间相差多少年。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @return 相差年数
*/
public static long betweenYear(Date beginDate, Date endDate) {
return between(beginDate, endDate, Calendar.YEAR);
}
/**
* 计算两个日期之间相差多少月。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @return 相差月数
*/
public static long betweenMonth(Date beginDate, Date endDate) {
return between(beginDate, endDate, Calendar.MONTH);
}
/**
* 计算两个日期之间相差多少天。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @return 相差天数
*/
public static long betweenDay(Date beginDate, Date endDate) {
return between(beginDate, endDate, Calendar.DAY_OF_MONTH);
}
/**
* 计算两个日期之间相差多少小时。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @return 相差小时数
*/
public static long betweenHour(Date beginDate, Date endDate) {
return between(beginDate, endDate, Calendar.HOUR_OF_DAY);
}
/**
* 计算两个日期之间相差多少分钟。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @return 相差分钟数
*/
public static long betweenMinute(Date beginDate, Date endDate) {
return between(beginDate, endDate, Calendar.MINUTE);
}
/**
* 计算两个日期之间相差多少秒。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @return 相差秒数
*/
public static long betweenSecond(Date beginDate, Date endDate) {
return between(beginDate, endDate, Calendar.SECOND);
}
/**
* 计算两个日期之间相差多少周。
*
* @param beginDate 起始日期
* @param endDate 结束日期
* @return 相差周数
*/
public static long betweenWeek(Date beginDate, Date endDate) {
return between(beginDate, endDate, Calendar.WEEK_OF_YEAR);
}
// ---------------------- judge ----------------------
/**
* 判断两个 Calendar 是否表示同一天。
*
* @param cal1 第一个 Calendar 对象
* @param cal2 第二个 Calendar 对象
* @return true 表示是同一天,false 否则
* @throws NullPointerException 如果任意一个参数为 null 则抛出空指针异常
*/
public static boolean isSameDay(final Calendar cal1, final Calendar cal2) {
Objects.requireNonNull(cal1, "cal1");
Objects.requireNonNull(cal2, "cal2");
return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
}
}
最后修改:2025 年 11 月 14 日
© 允许规范转载