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 日
如果觉得我的文章对你有用,请随意赞赏