前言
Java8之前的日期类有很多不足之处:
java.util.Date与java.util.Calendar中的所有属性都是可变的,代码中需要new很多Calendar;SimpleDateTimeFormat是非线程安全的,SimpleDateFormat是继承自DateFormat类,DateFormat类中维护了一个全局的Calendar变量,DateFormat类中的Calendar对象被多线程共享,而Calendar对象本身不支持线程安全;- 设置日期难操作,各种枚举等,各种隐含的含义,使用困难。
1、概述
1.1、类
Java 8延用了ISO的日历体系,不同的是,java.time包中的类是不可变且线程安全的。API位于java.time包中,里面的一些关键的类:
- Instant:代表时间戳;
- LocalDate:不包含具体时间的日期,比如2022-08-14;
- LocalTime:代表的是不含日期的时间;
- LocalDateTime:包含了日期及时间,不过还是没有偏移信息或者说时区;
- ZonedDateTime:一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的;
- ZoneOffset、Zoned:为时区提供更好的支持;
- 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类实际上包含了LocalDateTime与ZoneOffset。它用来表示一个包含格林威治时间偏移量(+/-小时: 分,比如+06:00或者 -08: 00)的完整的日期(年月日)及时间(时分秒,纳秒)。 -
DateTimeFormatter类用于在Java中进行日期的格式化与解析。与SimpleDateFormat不同,它是不可变且线程安全的,如果需要的话,可以赋值给一个静态变量。DateTimeFormatter类提供了许多预定义的格式器,你也可以自定义自己想要的格式。它还有一个parse()方法是用于将字符串转换成日期的,如果转换期间出现任何错误,它会抛出DateTimeParseException异常。 -
DateFormatter类也有一个用于格式化日期的format()方法,它出错的话则会抛出DateTimeException异常。 -
提供了
LocalDate与LocalTime类 Java 8中新的时间与日期API中的所有类都是不可变且线程安全的,这与之前的Date与Calendar API中的恰好相反,那里面像java.util.Date以及SimpleDateFormat这些关键的类都不是线程安全的。 -
新的时间与日期API中很重要的一点是它定义清楚了基本的时间与日期的概念,比方说,瞬时时间,持续时间,日期,时间,时区以及时间段。它们都是基于ISO日历体系的。