DateTimeUtils

package com.common.util;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>
 * <code>DateTimeUtils</code>
 * </p>
 * Description: 时间工具类
 */
public class DateTimeUtils {

    /**
     * 系统默认时区
     */
    public static final ZoneId DEFAULT = ZoneId.systemDefault();
    /**
     * UTC时区
     */
    public static final ZoneId UTC = ZoneId.of("UTC");
    /**
     * 东8区
     */
    public static final ZoneId GMT8 = ZoneId.of("GMT+8");
    /**
     * UTC时区
     */
    public static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC");
    /**
     * 东8区
     */
    public static final TimeZone GMT8_ZONE = TimeZone.getTimeZone("GMT+8");
    /**
     * 默认时区
     */
    public static final TimeZone DEFAULT_ZONE = TimeZone.getDefault();
    /**
     * 格式化器
     */
    private static final Map<String, DateTimeFormatter> FORMATTER_MAP = new ConcurrentHashMap<>();

    static {
        FORMATTER_MAP.put("yyyy-MM-dd'T'HH:mm:ss.sss", DateTimeFormatter.ISO_LOCAL_DATE_TIME);

        FORMATTER_MAP.put("yyyy-MM-dd HH:mm:ss", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        FORMATTER_MAP.put("yyyy-MM-dd HH:mm", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        FORMATTER_MAP.put("yyyy-MM-dd HH", DateTimeFormatter.ofPattern("yyyy-MM-dd HH"));
        FORMATTER_MAP.put("yyyy-MM-dd", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        FORMATTER_MAP.put("yyyy-MM", DateTimeFormatter.ofPattern("yyyy-MM"));
        FORMATTER_MAP.put("yyyy", DateTimeFormatter.ofPattern("yyyy"));
        FORMATTER_MAP.put("yyyyMMddHHmmss", DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        FORMATTER_MAP.put("yyyyMMddHHmm", DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
        FORMATTER_MAP.put("yyyyMMddHH", DateTimeFormatter.ofPattern("yyyyMMddHH"));
        FORMATTER_MAP.put("yyyyMMdd", DateTimeFormatter.ofPattern("yyyyMMdd"));
        FORMATTER_MAP.put("yyyyMM", DateTimeFormatter.ofPattern("yyyyMM"));
    }

    /**
     * 根据给定的模式字符串获取DateTimeFormatter实例。
     * 此方法通过模式字符串从一个全局缓存中获取DateTimeFormatter。如果缓存中不存在对应的DateTimeFormatter,
     * 则会根据模式字符串创建一个新的实例并添加到缓存中。
     * 这种做法避免了对相同模式字符串多次创建DateTimeFormatter实例,提高了性能。
     *
     * @param pattern 日期时间格式模式字符串,用于创建DateTimeFormatter。
     * @return 根据给定模式字符串对应的DateTimeFormatter实例。
     */
    public static DateTimeFormatter getFormatter(String pattern) {
        return FORMATTER_MAP.computeIfAbsent(pattern, k -> DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 获取当前时间的长整型值。
     * <p>
     * 本方法通过调用System.currentTimeMillis()获取自1970年1月1日00:00:00 GMT以来的毫秒数。
     * 这是一个常用的操作,用于获取当前时间点或作为时间戳。
     *
     * @return 当前时间的长整型值,单位为毫秒。
     */
    public static long currentTime() {
        return System.currentTimeMillis();
    }

    /**
     * 根据指定的时间戳、日期格式和时区,格式化日期时间。
     * <p>
     * 该方法将长期时间戳转换为指定时区的日期时间字符串。它使用提供的模式来定义日期时间的格式化方式。
     *
     * @param timestamp 时间戳,以毫秒为单位自1970年1月1日00:00:00 GMT以来的持续时间。
     * @param pattern   日期时间的格式模式,例如"yyyy-MM-dd HH:mm:ss"。
     * @param zoneId    时区标识,用于将时间戳转换为指定时区的日期时间。
     * @return 格式化后的日期时间字符串。
     */
    public static String format(long timestamp, String pattern, ZoneId zoneId) {
        return format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId), pattern);
    }

    /**
     * 根据指定的模式格式化给定的ZonedDateTime对象。
     * <p>
     * 该方法通过解析模式字符串,创建一个DateTimeFormatter实例,然后使用这个实例对ZonedDateTime对象进行格式化。
     * 这种方式允许灵活地以各种格式表示日期和时间。
     *
     * @param zonedDateTime 需要被格式化的ZonedDateTime对象。
     * @param pattern       用于格式化的模式字符串,遵循DateTimeFormatter的模式规则。
     * @return 格式化后的字符串。
     */
    public static String format(ZonedDateTime zonedDateTime, String pattern) {
        return zonedDateTime.format(getFormatter(pattern));
    }

    /**
     * 将指定格式的日期字符串解析为Date对象。
     * 该方法首先使用提供的模式解析日期字符串,然后将解析得到的LocalDate与最小的LocalTime和指定的时区结合,
     * 创建一个ZonedDateTime对象,最后从中提取Instant,转换为Date对象返回。
     * 这种方法适用于需要精确控制日期解析格式和时区的场景。
     *
     * @param parse   待解析的日期字符串。只能包含完整的日期信息(年月日),不能包含时间信息(时分秒)。
     * @param pattern 日期字符串的解析模式。
     * @param zoneId  指定时区,用于将解析的日期时间转换为对应的Instant。
     * @return 解析后的Date对象。
     */
    public static ZonedDateTime parseDate(String parse, String pattern, ZoneId zoneId) {
        return LocalDate.parse(parse, getFormatter(pattern)).atTime(LocalTime.MIN).atZone(zoneId);
    }

    /**
     * 根据指定的格式解析时间字符串为Date对象。
     * 该方法通过LocalTime.parse解析时间字符串,然后将其与1年1月1日的日期合并,
     * 最后根据指定的时区转换为Date对象。
     *
     * @param parse   需要解析的时间字符串。只能包含完整的时间信息(时分秒),不能包含日期信息(年月日)。
     * @param pattern 时间字符串的格式。
     * @param zoneId  指定时区的ID,用于将时间转换为特定时区的Date对象。
     * @return 解析后的Date对象。
     */
    public static ZonedDateTime parseTime(String parse, String pattern, ZoneId zoneId) {
        return LocalTime.parse(parse, getFormatter(pattern))
                .atDate(LocalDate.of(1, 1, 1)).atZone(zoneId);
    }

    /**
     * 将指定格式的日期时间字符串解析为Date对象。
     * <p>
     * 此方法通过提供的字符串和模式来创建一个Date对象,这允许灵活地处理不同格式的日期时间输入。
     * 使用ZoneId来处理时区问题,确保日期时间的解析是基于指定时区的。
     *
     * @param parse   待解析的日期时间字符串。只能包含完整的日期和时间信息(年月日时分秒)
     * @param pattern 日期时间字符串的格式模式。
     * @param zoneId  时区标识,用于将解析的日期时间转换为指定时区的Date对象。
     * @return 解析后的Date对象。
     */
    public static ZonedDateTime parseDateTime(String parse, String pattern, ZoneId zoneId) {
        return LocalDateTime.parse(parse, getFormatter(pattern)).atZone(zoneId);
    }

    /**
     * 将ZonedDateTime对象转换为Date对象。
     * <p>
     * 该方法通过提取ZonedDateTime对象中的瞬间时间信息,并将其转换为Date对象,从而实现了类型转换。
     * 这种转换保持了时间的精确性,不涉及时区的变更,仅是数据类型的转换。
     *
     * @param date 表示一个特定时刻和时区的时间,用于转换的源对象。
     * @return 返回一个Date对象,表示与输入的ZonedDateTime对象相对应的瞬间时间。
     */
    public static Date toDate(ZonedDateTime date) {
        return Date.from(date.toInstant());
    }

    /**
     * 将java.util.Date转换为java.time.ZonedDateTime。
     * 这个方法提供了从旧的日期时间API到新的日期时间API的转换,使得能够利用新的API的功能和便利。
     *
     * @param date   一个java.util.Date对象,代表要转换的日期时间。
     * @param zoneId 一个ZoneId对象,代表要应用于转换后的ZonedDateTime的时区。
     * @return 返回一个ZonedDateTime对象,表示给定的日期时间在指定时区中的时间。
     */
    public static ZonedDateTime toZonedDateTime(Date date, ZoneId zoneId) {
        return date.toInstant().atZone(zoneId);
    }

    /**
     * 根据指定的模式和时区,解析字符串为日期对象。
     * <p>
     * 此方法提供了一个灵活的方式来解析日期字符串,通过自定义的模式和指定的时区。
     * 它封装了SimpleDateFormat的使用,简化了日期解析的过程。
     *
     * @param parse    待解析的日期字符串。
     * @param pattern  日期字符串的模式,用于解析日期。
     * @param timeZone 解析日期时使用的时区。
     * @return 解析后的Date对象。
     * @throws RuntimeException 如果解析过程中发生异常。
     */
    public static Date parse(String parse, String pattern, TimeZone timeZone) {
        try {
            SimpleDateFormat format = new SimpleDateFormat(pattern);
            format.setTimeZone(timeZone);
            return format.parse(parse);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 在给定的日期上添加指定的时间单位数量。
     * <p>
     * 如果指定的数量为0,则直接返回原始日期。
     * 使用Java 8的日期时间API来执行日期的加法操作。
     *
     * @param date   要进行加法操作的原始日期。
     * @param number 要添加的时间单位的数量。未来时间为正数,过去时间为负数。
     * @param unit   要添加的时间单位类型。
     * @return 添加指定时间单位后的日期。
     */
    public static ZonedDateTime add(ZonedDateTime date, int number, ChronoUnit unit) {
        if (number == 0) {
            return date;
        }
        return date.plus(number, unit);
    }

    /**
     * 根据指定的时间单位,获取给定日期的开始时间。
     * 该方法将根据不同的时间单位,返回从年、月、日、小时到分钟的开始时间。
     *
     * @param date 输入的日期
     * @param unit 时间单位,用于确定返回的开始时间的精度。
     * @return 返回给定日期根据指定时间单位的开始时间。
     */
    public static ZonedDateTime getStart(ZonedDateTime date, ChronoUnit unit) {
        LocalDateTime local = date.toLocalDateTime();
        switch (unit) {
            case YEARS:
                return LocalDateTime.of(LocalDate.of(local.getYear(), 1, 1), LocalTime.MIN)
                        .atZone(date.getZone());
            case MONTHS:
                return LocalDateTime.of(LocalDate.of(local.getYear(), local.getMonth(), 1), LocalTime.MIN)
                        .atZone(date.getZone());
            case DAYS:
                return LocalDateTime.of(local.toLocalDate(), LocalTime.MIN).atZone(date.getZone());
            case HOURS:
                return LocalDateTime.of(local.toLocalDate(), LocalTime.of(local.getHour(), 0, 0, 0))
                        .atZone(date.getZone());
            case MINUTES:
                return LocalDateTime.of(local.toLocalDate(), LocalTime.of(local.getHour(), local.getMinute(), 0, 0))
                        .atZone(date.getZone());
            default:
                return LocalDateTime.of(local.toLocalDate(), LocalTime.of(local.getHour(), local.getMinute(), local.getSecond(), 0))
                        .atZone(date.getZone());
        }
    }

    /**
     * 计算给定时间单位下的结束时间。
     * 该方法通过获取指定时间单位的开始时间,然后加上一个单位时间长度,再减去1纳秒,来得到该时间单位的结束时间。
     * 这样做的目的是为了确保结束时间严格大于开始时间,且属于同一个时间单位的最后一个瞬间。
     *
     * @param date 开始时间点,用于计算时间单位的结束时间。
     * @param unit 时间单位,用于计算结束时间。
     * @return 返回指定时间单位的结束时间。
     */
    public static ZonedDateTime getEnd(ZonedDateTime date, ChronoUnit unit) {
        return add(getStart(date, unit), 1, unit).minusNanos(1);
    }

    /**
     * 将时间截断到指定的时间单位后进行比较。
     *
     * @param a    第一个Date对象
     * @param b    第二个Date对象
     * @param unit 截断的时间单位。
     * @return 返回比较的结果,当a大于b时返回true,其他返回false。
     */
    public static boolean isAfter(ZonedDateTime a, ZonedDateTime b, ChronoUnit unit) {
        if (a == null || b == null) {
            if (a == null && b == null) {
                return false;
            } else {
                return a != null;
            }
        }

        LocalDateTime localA = a.toLocalDateTime();
        LocalDateTime localB = b.toLocalDateTime();
        if (unit == null) {
            return localA.isAfter(localB);
        }
        switch (unit) {
            case YEARS:
                return LocalDate.of(localA.getYear(), 1, 1)
                        .isAfter(LocalDate.of(localB.getYear(), 1, 1));
            case MONTHS:
                return LocalDate.of(localA.getYear(), localA.getMonth(), 1)
                        .isAfter(LocalDate.of(localB.getYear(), localB.getMonth(), 1));
            case DAYS:
                return localA.toLocalDate().isAfter(localB.toLocalDate());
            case HOURS:
                return LocalDateTime.of(localA.toLocalDate(), localA.toLocalTime().truncatedTo(ChronoUnit.HOURS))
                        .isAfter(LocalDateTime.of(localB.toLocalDate(), localB.toLocalTime().truncatedTo(ChronoUnit.HOURS)));
            case MINUTES:
                return LocalDateTime.of(localA.toLocalDate(), localA.toLocalTime().truncatedTo(ChronoUnit.MINUTES))
                        .isAfter(LocalDateTime.of(localB.toLocalDate(), localB.toLocalTime().truncatedTo(ChronoUnit.MINUTES)));
            case SECONDS:
                return LocalDateTime.of(localA.toLocalDate(), localA.toLocalTime().truncatedTo(ChronoUnit.SECONDS))
                        .isAfter(LocalDateTime.of(localB.toLocalDate(), localB.toLocalTime().truncatedTo(ChronoUnit.SECONDS)));
            default:
                return localA.isAfter(localB);
        }
    }

    /**
     * 判断给定的Date对象是否在两个给定的Date对象之间。
     *
     * @param date      待比较Date对象
     * @param a         第一个Date对象
     * @param b         第二个Date对象
     * @param compModel 比较模式,1:只比较日期,2:只比较时间,其他:比较日期和时间。
     * @return 返回比较的结果,当date在a和b之间时返回true,其他返回false。
     */
    public static boolean isBetween(ZonedDateTime date, ZonedDateTime a, ZonedDateTime b, int compModel) {
        if (date == null || a == null || b == null) {
            return false;
        }
        if (a.toInstant().toEpochMilli() == b.toInstant().toEpochMilli()) {
            return false;
        }

        LocalDateTime localDate = LocalDateTime.ofInstant(date.toInstant(), DEFAULT);
        LocalDateTime localA = LocalDateTime.ofInstant(a.toInstant(), DEFAULT);
        LocalDateTime localB = LocalDateTime.ofInstant(b.toInstant(), DEFAULT);
        switch (compModel) {
            case 1:
                // 只比较日期
                if (a.toInstant().toEpochMilli() == b.toInstant().toEpochMilli()) {
                    return localDate.toLocalDate().isBefore(localA.toLocalDate())
                            && localDate.toLocalDate().isAfter(localB.toLocalDate());
                } else {
                    return localDate.toLocalDate().isAfter(localA.toLocalDate())
                            && localDate.toLocalDate().isBefore(localB.toLocalDate());
                }
            case 2:
                // 只比较时间
                if (a.toInstant().toEpochMilli() == b.toInstant().toEpochMilli()) {
                    return localDate.toLocalTime().isBefore(localA.toLocalTime())
                            && localDate.toLocalTime().isAfter(localB.toLocalTime());
                } else {
                    return localDate.toLocalTime().isAfter(localA.toLocalTime())
                            && localDate.toLocalTime().isBefore(localB.toLocalTime());
                }
            default:
                // 比较日期和时间
                if (a.toInstant().toEpochMilli() == b.toInstant().toEpochMilli()) {
                    return localDate.isBefore(localA) && localDate.isAfter(localB);
                } else {
                    return localDate.isAfter(localA) && localDate.isBefore(localB);
                }
        }
    }

    /**
     * 获取所有可用的时区ID集合。
     * <p>
     * 本方法通过调用Java标准库中的ZoneId.getAvailableZoneIds()方法,获取全球所有可用的时区ID。
     * 这些ID可以用于标识不同的地理位置对应的时区信息。
     *
     * @return 返回一个包含所有可用时区ID的Set集合。
     */
    private static Set<String> getAllZoneIds() {
        return ZoneId.getAvailableZoneIds();
    }

    /**
     * 获取所有可用的时间区域ID。
     *
     * @return 包含所有可用时间区域ID的字符串数组。
     * <p>
     * 此方法调用Java的TimeZone类的getAvailableIDs方法,该方法返回一个字符串数组,
     * 数组中的每个字符串代表一个可用的时间区域ID。这些ID可以用于识别全球不同的时间区域。
     */
    private static String[] getAllTimeZones() {
        return TimeZone.getAvailableIDs();
    }

    public static void main(String[] args) throws Exception {
        long currentTime = currentTime();

        System.out.println(format(currentTime, "yyyy-MM-dd HH:mm:ss", UTC));
        System.out.println(format(currentTime, "yyyy-MM-dd HH:mm:ss", GMT8));
        System.out.println("------------------------------------------");

        System.out.println(parseDate("2023-07-04", "yyyy-MM-dd", GMT8));
        System.out.println(parseDate("2023-07-04", "yyyy-MM-dd", UTC));
        System.out.println("------------------------------------------");
        System.out.println(parseTime("10:01:01", "HH:mm:ss", GMT8));
        System.out.println(parseDateTime("2023-07-04 10:01:01", "yyyy-MM-dd HH:mm:ss", GMT8));
        System.out.println("------------------------------------------");

        System.out.println(parse("2023-07", "yyyy-MM", GMT8_ZONE));
        System.out.println(parse("2023-07-04 10:01", "yyyy-MM-dd HH:mm", GMT8_ZONE));
        System.out.println(parse("2023-07-04 10:01:01", "yyyy-MM-dd HH:mm:ss", GMT8_ZONE));
        System.out.println("------------------------------------------");

        System.out.println(add(ZonedDateTime.now(), 1, ChronoUnit.DAYS));
        System.out.println(add(ZonedDateTime.now(), -1, ChronoUnit.DAYS));
        System.out.println("------------------------------------------");

        ZonedDateTime a = parseDateTime("2023-07-06 10:02:01", "yyyy-MM-dd HH:mm:ss", GMT8);
        ZonedDateTime b = parseDateTime("2023-07-04 11:01:03", "yyyy-MM-dd HH:mm:ss", GMT8);
        System.out.println(isAfter(a, b, ChronoUnit.YEARS));
        System.out.println(isAfter(a, b, ChronoUnit.MONTHS));
        System.out.println(isAfter(a, b, ChronoUnit.DAYS));
        System.out.println(isAfter(a, b, ChronoUnit.HOURS));
        System.out.println(isAfter(a, b, ChronoUnit.MINUTES));
        System.out.println(isAfter(a, b, ChronoUnit.SECONDS));
        System.out.println(isAfter(a, b, null));
        System.out.println("------------------------------------------");

        ZonedDateTime now = ZonedDateTime.now(GMT8);
        System.out.println(getStart(now, ChronoUnit.DAYS));
        System.out.println(getEnd(now, ChronoUnit.YEARS));
        System.out.println("------------------------------------------");

        System.out.println(isBetween(add(now, 2, ChronoUnit.DAYS),
                add(now, 1, ChronoUnit.DAYS),
                add(now, 3, ChronoUnit.DAYS), 1));
        System.out.println(isBetween(add(now, 2, ChronoUnit.DAYS),
                add(now, 1, ChronoUnit.DAYS),
                add(now, 3, ChronoUnit.DAYS), 2));
        System.out.println(isBetween(add(now, 2, ChronoUnit.DAYS),
                add(now, 1, ChronoUnit.DAYS),
                add(now, 3, ChronoUnit.DAYS), 3));
        System.out.println("------------------------------------------");

        System.out.println(LocalDateTime.now(UTC));
        System.out.println(LocalDate.now(UTC));
        System.out.println(LocalTime.now(UTC));
        System.out.println("------------------------------------------");

        // 2024-07-10
        System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE));
        // 2024-07-10+08:00
        System.out.println(now.format(DateTimeFormatter.ISO_OFFSET_DATE));
        // 2024-07-10+08:00
        System.out.println(now.format(DateTimeFormatter.ISO_DATE));
        // 14:10:16.746
        System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_TIME));
        // 14:10:16.746+08:00
        System.out.println(now.format(DateTimeFormatter.ISO_OFFSET_TIME));
        // 14:10:16.746+08:00
        System.out.println(now.format(DateTimeFormatter.ISO_TIME));
        // 2024-07-10T14:10:16.746
        System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        // 2024-07-10T14:10:16.746+08:00
        System.out.println(now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        // 2024-07-10T14:10:16.746+08:00[GMT+08:00]
        System.out.println(now.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
        // 2024-07-10T14:10:16.746+08:00[GMT+08:00]
        System.out.println(now.format(DateTimeFormatter.ISO_DATE_TIME));
        // 2024-192+08:00
        System.out.println(now.format(DateTimeFormatter.ISO_ORDINAL_DATE));
        // 2024-W28-3+08:00
        System.out.println(now.format(DateTimeFormatter.ISO_WEEK_DATE));
        // 2024-07-10T06:10:16.746Z
        System.out.println(now.format(DateTimeFormatter.ISO_INSTANT));
        // 20240710+0800
        System.out.println(now.format(DateTimeFormatter.BASIC_ISO_DATE));
        // Wed, 10 Jul 2024 14:10:16 +0800
        System.out.println(now.format(DateTimeFormatter.RFC_1123_DATE_TIME));


        System.out.println("------------------------------------------");
    }
}

相关推荐

  1. DateTimeUtils

    2024-07-10 17:28:05       8 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 17:28:05       5 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 17:28:05       5 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 17:28:05       4 阅读
  4. Python语言-面向对象

    2024-07-10 17:28:05       5 阅读

热门阅读

  1. CSS:选择器 / 14种类型

    2024-07-10 17:28:05       10 阅读
  2. css中文字书写方向

    2024-07-10 17:28:05       9 阅读
  3. 19.JWT

    19.JWT

    2024-07-10 17:28:05      11 阅读
  4. 实证Stata代码命令汇总

    2024-07-10 17:28:05       10 阅读
  5. 将 build.gradle 配置从 Groovy 迁移到 Kotlin

    2024-07-10 17:28:05       11 阅读
  6. MySQL数据库字符集utf8mb4的排序规则介绍

    2024-07-10 17:28:05       9 阅读
  7. 人形机器人强化学习控制分类

    2024-07-10 17:28:05       10 阅读
  8. 小抄 20240708

    2024-07-10 17:28:05       8 阅读
  9. sklearn基础教程

    2024-07-10 17:28:05       12 阅读
  10. 图形渲染基础-GPU驱动的渲染管线

    2024-07-10 17:28:05       11 阅读
  11. 数据库的基本概念

    2024-07-10 17:28:05       11 阅读