Java8日期类

315 阅读11分钟

前言

Java8之前的日期类有很多不足之处:

  1. java.util.Datejava.util.Calendar中的所有属性都是可变的,代码中需要new很多Calendar;
  2. SimpleDateTimeFormat是非线程安全的,SimpleDateFormat是继承自DateFormat类,DateFormat类中维护了一个全局的Calendar变量,DateFormat类中的Calendar对象被多线程共享,而Calendar对象本身不支持线程安全;
  3. 设置日期难操作,各种枚举等,各种隐含的含义,使用困难。

1、概述

1.1、类

Java 8延用了ISO的日历体系,不同的是,java.time包中的类是不可变且线程安全的。API位于java.time包中,里面的一些关键的类:

  1. Instant:代表时间戳;
  2. LocalDate:不包含具体时间的日期,比如2022-08-14;
  3. LocalTime:代表的是不含日期的时间;
  4. LocalDateTime:包含了日期及时间,不过还是没有偏移信息或者说时区;
  5. ZonedDateTime:一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的;
  6. ZoneOffset、Zoned:为时区提供更好的支持;
  7. DateTimeFormatter:日期的解析及格式化,它是线程安全的,可以在高并发场景下直接使用DateTimeFormatter类来处理日期的格式化操作。使用DateTimeFormatter类来处理日期的格式化操作运行效率比较高。

1.2、方法

API还提供了相关的方法:

  • of: 静态工厂方法。
  • parse: 静态工厂方法,关注于解析。
  • get: 获取某些东西的值。
  • is: 检查某些东西的是否是true。
  • with: 不可变的setter等价物。
  • plus: 加一些量到某个对象。
  • minus: 从某个对象减去一些量。
  • to: 转换到另一个类型。
  • at: 把这个对象与另一个对象组合起来,例如: date.atTime(time)。

2、例子

public class DateUtils {

    public static final String YYYY_MM_DD = "yyyy-MM-dd";
    public static final String ZH_TO_DAY = "yyyy年MM月dd日";
    public static final String YYYYMMDD = "yyyyMMdd";
    public static final String YYYY_MM_DD_POINT = "yyyy.MM.dd";
    public static final String YYYY_MM_DD_SLASH = "yyyy/MM/dd";
    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS";

    public static ThreadLocal<DateFormat> shortFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat(YYYY_MM_DD));
    public static ThreadLocal<DateFormat> defaultFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS));

    /**
     * @return 当前时间毫秒数
     */
    public static long nowMill() {
        return LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
    }

    /**
     * @return 当前时间秒数
     */
    public static long nowSecond() {
        return LocalDateTime.now().toInstant(ZoneOffset.of("+8")).getEpochSecond();
    }

    /**
     * @return 今天零点
     */
    public static Date nowDayOfZero() {
        return specifiedDayOfZero(0);
    }

    /**
     * 当前时间字符串
     *
     * @param format 指定返回格式
     * @return 当前时间字符串
     */
    public static String nowDateFormat(String format) {
        return specifiedDaysByFormat(0, format);
    }

    /**
     * 今天零点字符串
     *
     * @param format 指定返回格式
     * @return 今天零点字符串
     */
    public static String nowDayOfZero(String format) {
        return specifiedDayOfZero(0, format);
    }

    /**
     * 距离今天指定天数的当天零点
     *
     * @param diffDays 距当天的差值(before < 0, now = 0, after > 0)
     * @return 距离今天指定天数的当天零点
     */
    public static Date specifiedDayOfZero(int diffDays) {
        return localDateTimeToDate(LocalDateTime.of(LocalDate.now().plusDays(diffDays), LocalTime.MIN));
    }

    /**
     * 距离今天指定天数的当天零点字符串
     *
     * @param diffDays 距当天的差值(before < 0, now = 0, after > 0)
     * @param format   指定返回格式
     * @return 距离今天指定天数的当天零点字符串
     */
    public static String specifiedDayOfZero(int diffDays, String format) {
        return dateFormat(LocalDateTime.of(LocalDate.now().plusDays(diffDays), LocalTime.MIN), format);
    }

    /**
     * 距离今天指定天数的当前时间点
     *
     * @param diffDays 距当前时间的差值(before < 0, now = 0, after > 0)
     * @return 指定格式的时间
     */
    public static Date specifiedDaysByFormat(int diffDays) {
        return localDateTimeToDate(LocalDateTime.now().plusDays(diffDays));
    }

    /**
     * 距离今天指定月数的当前时间点
     *
     * @param diffMonths 距当前时间的差值(before < 0, now = 0, after > 0)
     * @return 指定格式的时间
     */
    public static Date specifiedMonthsByFormat(int diffMonths) {
        return localDateTimeToDate(LocalDateTime.now().plusMonths(diffMonths));
    }

    /**
     * 距离今天指定年数的当前时间点
     *
     * @param diffYears 距当天的差值(before < 0, now = 0, after > 0)
     * @return 指定格式的时间
     */
    public static Date specifiedYearsByFormat(int diffYears) {
        return localDateTimeToDate(LocalDateTime.now().plusYears(diffYears));
    }

    /**
     * 距离今天指定天数的当前时间点字符串
     *
     * @param diffDays 距当前时间的差值(before < 0, now = 0, after > 0)
     * @param format   指定返回格式
     * @return 距离今天指定天数的当前时间点字符串
     */
    public static String specifiedDaysByFormat(int diffDays, String format) {
        return dateFormat(LocalDateTime.now().plusDays(diffDays), format);
    }

    /**
     * 距离今天指定月数的当前时间点字符串
     *
     * @param diffMonths 距当前时间的差值(before < 0, now = 0, after > 0)
     * @param format     指定返回格式
     * @return 距离今天指定月数的当前时间点字符串
     */
    public static String specifiedMonthsByFormat(int diffMonths, String format) {
        return dateFormat(LocalDateTime.now().plusMonths(diffMonths), format);
    }

    /**
     * 距离今天指定年数的当前时间点字符串
     *
     * @param diffYears 距当天的差值(before < 0, now = 0, after > 0)
     * @param format    指定返回格式
     * @return 距离今天指定年数的当前时间点字符串
     */
    public static String specifiedYearsByFormat(int diffYears, String format) {
        return dateFormat(LocalDateTime.now().plusYears(diffYears), format);
    }

    /**
     * 每月开始时间字符串
     *
     * @param date   指定日期字符串
     * @param format 指定返回格式
     * @return 每月开始时间字符串
     */
    public static String monthStart(String date, String format) {
        return monthStart(parseString(date, format), format);
    }

    /**
     * 每月开始时间字符串
     *
     * @param date   指定日期字符串
     * @param format 指定返回格式
     * @return 每月开始时间字符串
     */
    public static String monthEnd(String date, String format) {
        return monthEnd(parseString(date, format), format);
    }

    /**
     * 每月开始时间字符串
     *
     * @param date   指定日期
     * @param format 指定返回格式
     * @return 每月开始时间字符串
     */
    public static String monthStart(Date date, String format) {
        return dateFormat(monthStart(date), format);
    }

    /**
     * 每月结束时间字符串
     *
     * @param date   指定日期
     * @param format 指定返回格式
     * @return 每月结束时间
     */
    public static String monthEnd(Date date, String format) {
        return dateFormat(monthEnd(date), format);
    }

    /**
     * 每月开始时间
     *
     * @param date 指定日期
     * @return 每月开始时间
     */
    public static Date monthStart(Date date) {
        return localDateToDate(monthStart(dateToLocalDate(date)));
    }

    /**
     * 每月结束时间
     *
     * @param date 指定日期
     * @return 每月结束时间
     */
    public static Date monthEnd(Date date) {
        return localDateToDate(monthEnd(dateToLocalDate(date)));
    }

    /**
     * 每月开始时间
     *
     * @param localDate 指定日期
     * @return 每月开始时间
     */
    public static LocalDate monthStart(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.firstDayOfMonth());
    }

    /**
     * 每月结束时间
     *
     * @param localDate 指定日期
     * @return 每月结束时间
     */
    public static LocalDate monthEnd(LocalDate localDate) {
        return localDate.with(TemporalAdjusters.lastDayOfMonth());
    }

    /**
     * 计算两个日期相差天数
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差天数
     */
    public static long diffDays(String start, String end, String format) {
        return diffDays(parseString(start, format), parseString(end, format));
    }

    /**
     * 计算两个日期相差月数
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差月数
     */
    public static long diffMonths(String start, String end, String format) {
        return diffMonths(parseString(start, format), parseString(end, format));
    }

    /**
     * 计算两个日期相差年数
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差年数
     */
    public static long diffYears(String start, String end, String format) {
        return diffYears(parseString(start, format), parseString(end, format));
    }

    /**
     * 计算两个日期相差天数
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差天数
     */
    public static long diffDays(Date start, Date end) {
        return diffByUnit(start, end, ChronoUnit.DAYS);
    }

    /**
     * 计算两个日期相差月数
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差月数
     */
    public static long diffMonths(Date start, Date end) {
        return diffByUnit(start, end, ChronoUnit.MONTHS);
    }

    /**
     * 计算两个日期相差年数
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差年数
     */
    public static long diffYears(Date start, Date end) {
        return diffByUnit(start, end, ChronoUnit.YEARS);
    }

    /**
     * 计算两个日期相差天数
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差天数
     */
    public static long diffDays(LocalDate start, LocalDate end) {
        return diffByUnit(start, end, ChronoUnit.DAYS);
    }

    /**
     * 计算两个日期相差月数
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差月数
     */
    public static long diffMonths(LocalDate start, LocalDate end) {
        return diffByUnit(start, end, ChronoUnit.MONTHS);
    }

    /**
     * 计算两个日期相差年数
     *
     * @param start 开始日期
     * @param end   结束日期
     * @return 相差年数
     */
    public static long diffYears(LocalDate start, LocalDate end) {
        return diffByUnit(start, end, ChronoUnit.YEARS);
    }

    /**
     * 计算两个日期时间差
     *
     * @param start 开始日期
     * @param end   结束日期
     * @param unit  时间单位
     * @return 相差时间根据单位决定
     */
    public static long diffByUnit(Date start, Date end, ChronoUnit unit) {
        return diffByUnit(dateToLocalDate(start), dateToLocalDate(end), unit);
    }

    /**
     * 计算两个日期时间差
     *
     * @param start 开始日期
     * @param end   结束日期
     * @param unit  时间单位
     * @return 相差时间根据单位决定
     */
    public static long diffByUnit(LocalDate start, LocalDate end, ChronoUnit unit) {
        return unit.between(start, end);
    }

    /**
     * 返回默认格式化的日期字符串
     *
     * @param date 指定日期
     * @return 返回默认格式化的日期字符串
     */
    public static String dateFormat(Date date) {
        return dateFormat(date, YYYY_MM_DD_HH_MM_SS);
    }

    /**
     * 日期格式化字符串
     *
     * @param date   日期
     * @param format 指定返回格式
     * @return 格式化后的日期字符串
     */
    public static String dateFormat(Date date, ThreadLocal<DateFormat> format) {
        return format.get().format(date);
    }

    /**
     * 日期格式化字符串
     *
     * @param date   指定日期
     * @param format 指定返回格式
     * @return 格式化后的日期字符串
     */
    public static String dateFormat(Date date, String format) {
        return dateFormat(dateToLocalDateTime(date), format);
    }

    /**
     * 日期格式化字符串
     *
     * @param localDateTime 指定日期
     * @param format        指定返回格式
     * @return 格式化后的日期字符串
     */
    public static String dateFormat(LocalDateTime localDateTime, String format) {
        return localDateTime.format(DateTimeFormatter.ofPattern(format, Locale.CHINA));
    }

    /**
     * 字符串格式化日期
     *
     * @param date   日期字符串
     * @param format 指定返回格式
     * @return 格式化后的日期
     */
    public static Date parseString(String date, ThreadLocal<DateFormat> format) throws ParseException {
        return format.get().parse(date);
    }

    /**
     * 格式化字符串日期
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param date   字符串日期
     * @param format 指定返回格式
     * @return 格式化后的日期
     */
    public static Date parseString(String date, String format) {
        try {
            return localDateTimeToDate(parseStringToLocalDateTime(date, format));
        } catch (Exception e) {
            if (e.getMessage().contains("Unable to obtain LocalDateTime from TemporalAccessor")) {
                return localDateToDate(parseStringToLocalDate(date, format));
            }
            throw e;
        }
    }

    /**
     * 格式化字符串日期
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param date   字符串日期
     * @param format 指定返回格式
     * @return 格式化后的日期
     */
    public static LocalDateTime parseStringToLocalDateTime(String date, String format) {
        return parseStringToLocalDateTime(date, DateTimeFormatter.ofPattern(format, Locale.CHINA));
    }

    /**
     * 格式化字符串日期
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param date   字符串日期
     * @param format 指定返回格式
     * @return 格式化后的日期
     */
    public static LocalDate parseStringToLocalDate(String date, String format) {
        return parseStringToLocalDate(date, DateTimeFormatter.ofPattern(format, Locale.CHINA));
    }

    /**
     * 格式化字符串日期
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param date      字符串日期
     * @param formatter 指定返回格式
     * @return 格式化后的日期
     */
    public static LocalDateTime parseStringToLocalDateTime(String date, DateTimeFormatter formatter) {
        return LocalDateTime.parse(date, formatter);
    }

    /**
     * 格式化字符串日期
     * 注:字符串日期必须与format格式严格一致(如:date = '2019-01-01', format = yyyy-MM-dd,反例:date = '2019-1-1')
     *
     * @param date      字符串日期
     * @param formatter 指定返回格式
     * @return 格式化后的日期
     */
    public static LocalDate parseStringToLocalDate(String date, DateTimeFormatter formatter) {
        return LocalDate.parse(date, formatter);
    }

    /**
     * Date 转 LocalDateTime
     *
     * @param date 指定日期
     * @return LocalDateTime
     */
    public static LocalDateTime dateToLocalDateTime(Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * LocalDateTime 转 Date
     *
     * @param localDateTime 指定日期
     * @return Date
     */
    public static Date localDateTimeToDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * Date 转 LocalDate
     *
     * @param date 指定日期
     * @return LocalDate
     */
    public static LocalDate dateToLocalDate(Date date) {
        return dateToLocalDateTime(date).toLocalDate();
    }

    /**
     * LocalDate 转 Date
     *
     * @param localDate 指定日期
     * @return Date
     */
    public static Date localDateToDate(LocalDate localDate) {
        return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * Date 转 LocalTime
     *
     * @param date 指定日期
     * @return LocalTime
     */
    public static LocalTime dateToLocalTime(Date date) {
        return dateToLocalDateTime(date).toLocalTime();
    }

    /**
     * 当前日期加几天
     * @param date
     * @param day
     * @return
     */
    public static Date addAnyDay(Date date,Integer day) {
        return localDateTimeToDate(dateToLocalDateTime(date).plusDays(day));
    }
}

总结

  • 提供了javax.time.ZoneId用来处理时区。时区指的是地球上共享同一标准时间的地区。每个时区都有一个唯一标识符,同时还有一个地区/城市(Asia/Tokyo)的格式以及从格林威治时间开始的一个偏移时间。

  • OffsetDateTime类实际上包含了LocalDateTimeZoneOffset。它用来表示一个包含格林威治时间偏移量(+/-小时: 分,比如+06:00或者 -08: 00)的完整的日期(年月日)及时间(时分秒,纳秒)。

  • DateTimeFormatter类用于在Java中进行日期的格式化与解析。与SimpleDateFormat不同,它是不可变且线程安全的,如果需要的话,可以赋值给一个静态变量。DateTimeFormatter类提供了许多预定义的格式器,你也可以自定义自己想要的格式。它还有一个parse()方法是用于将字符串转换成日期的,如果转换期间出现任何错误,它会抛出DateTimeParseException异常。

  • DateFormatter类也有一个用于格式化日期的format()方法,它出错的话则会抛出DateTimeException异常。

  • 提供了LocalDateLocalTime类 Java 8中新的时间与日期API中的所有类都是不可变且线程安全的,这与之前的Date与Calendar API中的恰好相反,那里面像java.util.Date以及SimpleDateFormat这些关键的类都不是线程安全的。

  • 新的时间与日期API中很重要的一点是它定义清楚了基本的时间与日期的概念,比方说,瞬时时间,持续时间,日期,时间,时区以及时间段。它们都是基于ISO日历体系的。