在java8中,java.time
包下主要包含下面几个主要的类:
Instant:时间戳,相当于java.util的Date
LocalDate:只包含日期,比如:2016-10-20
LocalTime:只包含时间,比如:23:12:10
LocalDateTime:包含日期和时间,比如:2016-10-20 23:14:21
Duration:计算两个“时间”的间隔
Period:用于计算两个“日期”的间隔
ZoneOffset:时区偏移量,比如:+8:00
ZonedDateTime:可以得到特定时区的日期/时间
Clock:时钟,比如获取目前美国纽约的时间
时间格式化以DateTimeFormatter
代替SimpleDateFormat
DateTimeFormatter:时间格式化
方法前缀的含义,统一了api:
of:静态工厂方法(用类名去调用)。
parse:静态工厂方法,关注于解析(用类名去调用)。
now: 静态工厂方法,用当前时间创建实例(用类名去调用)
get:获取某些东西的值。
is:检查某些东西的是否是true。
with:返回一个部分状态改变了的时间日期对象拷贝(单独一个with方法,参数为TemporalAdjusters类型)。
plus:返回一个时间增加了的、时间日期对象拷贝(如果参数是负数也能够有minus方法的效果)。
minus:返回一个时间减少了的、时间日期对象拷贝。
to:把当前时间日期对象转换成另外一个,可能会损失部分状态。
at:把这个对象与另一个对象组合起来,例如: date.atTime(time)。
format :根据某一个DateTimeFormatter格式化为字符串。
通过以上一系列方法,java.time
完成了加、减、格式化、解析、从日期/时间中提取单独部分等任务。
java.time
包里面的类实例如果用了上面的方法而被修改了,那么会返回一个新的实例过来,而不像Calendar那样可以在同一个实例进行不同的修改,体现了不可变。
下面讲解具体的类
Instant :时间戳,相当于java.util的Date
Instant
用于表示一个时间戳,它与我们常使用的System.currentTimeMillis()
有些类似,不过Instant
可以精确到纳秒(Nano-Second
),System.currentTimeMillis()
方法只精确到毫秒(Milli-Second)。如果查看Instant
源码,发现它的内部使用了两个常量,seconds
表示从1970-01-01 00:00:00
开始到现在的秒数,nanos
表示纳秒部分(nanos
的值不会超过999,999,999
)。Instant
除了使用now()
方法创建外,还可以通过ofEpochSecond
方法创建:
Instant instant = Instant.ofEpochSecond(120, 100000);
ofEpochSecond()
方法的第一个参数为秒,第二个参数为纳秒,上面的代码表示从1970-01-01 00:00:00开始后两分钟的10万纳秒的时刻,控制台上的输出为:
1970-01-01T00:02:00.000100Z
Duration : 计算两个“时间”的间隔
这个很好理解,看下面的了栗子就懂了
LocalDateTime from = LocalDateTime.of(2019, Month.JANUARY, 21, 15, 56, 0); // 2019-01-21 15:56:00
LocalDateTime to = LocalDateTime.of(2019, Month.FEBRUARY, 21, 15, 56, 0); // 2019-02-21 15:56:00
Duration duration = Duration.between(from, to); // 表示从 2019-01-21 15:56:00 到 2019-02-21 15:56:00
long days = duration.toDays(); // 这段时间的总天数
long hours = duration.toHours(); // 这段时间的小时数
long minutes = duration.toMinutes(); // 这段时间的分钟数
long seconds = duration.getSeconds(); // 这段时间的秒数
long milliSeconds = duration.toMillis(); // 这段时间的毫秒数
long nanoSeconds = duration.toNanos(); // 这段时间的纳秒数
Duration
对象还可以通过of()
方法创建,该方法接受一个时间段长度,和一个时间单位作为参数
Duration duration1 = Duration.of(5, ChronoUnit.DAYS); // 5天
Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS); // 1000毫秒
Period : 用于计算两个“日期”的间隔
Period
在概念上和Duration
类似,区别在于Period
是以年月日来衡量一个时间段,比如2年3个月6天
Period period = Period.of(2, 3, 6);
由于Period
是以年月日衡量时间段,所以between()方法只能接收LocalDate
类型的参数:
// 2019-01-21 到 2019-02-21 这段时间
Period period = Period.between(
LocalDate.of(2019, 1, 21),
LocalDate.of(2019, 2, 21));
ZoneId : 时区
获取所有合法的“区域/城市”字符串 :
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
获取系统默认时区 :
ZoneId systemZoneId = ZoneId.systemDefault();
创建时区 :
ZoneId shanghaiZoneId = ZoneId.of("Africa/Bangui");
LocalDateTime:包含日期和时间,比如:2016-10-20 23:14:21
获取当前时间 :
LocalDateTime localDateTime = LocalDateTime.now();//2019-01-21T16:15:52.863
创建特定日期 (多种自定义):
LocalDateTime localDateTime = LocalDateTime.of(2019,01,21,16,22,34);
获取获取年、月、日信息 :
LocalDateTime.now().getYear();//2019
LocalDateTime.now().getMonth();//JANUARY
LocalDateTime.now().getDayOfYear();//21
LocalDateTime.now().getDayOfMonth();//21
LocalDateTime.now().getDayOfWeek();//MONDAY
LocalDateTime.now().getHour();//16
能够自定义时间 :
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
System.out.println(time); //2017-01-01T01:01:01
//使用plus方法增加年份
//改变时间后会返回一个新的实例nextYearTime
LocalDateTime nextYearTime = time.plusYears(1);
System.out.println(nextYearTime); //2018-01-01T01:01:01
//使用minus方法减年份
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
LocalDateTime lastYearTime = time.minusYears(1);
System.out.println(lastYearTime); //2016-01-01T01:01:01
//使用with方法设置月份
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
LocalDateTime changeTime = time.withMonth(12);
System.out.println(changeTime); //2017-12-01T01:01:01
//判断当前年份是否闰年
System.out.println("isLeapYear :" + time.isLeapYear());
//判断当前日期属于星期几
LocalDateTime time = LocalDateTime.now();
DayOfWeek dayOfWeek = time.getDayOfWeek();
System.out.println(dayOfWeek); //WEDNESDAY
LocalDate
和LocalTime
与LocalDateTime
类似,不多说
其他使用场景:
判断两个日期是否相等
LocalDate date1 = LocalDate.of(2019, 01, 21);
if(date1.equals(LocalDate.now())){
System.out.printf("Today %s and date1 %s are same date %n", LocalDate.now(), date1);
}
//输出:Today 2019-01-21 and date1 2019-01-21 are same date
检查像生日这种周期性事件
类似每月账单、结婚纪念日、保险缴费日这些周期性事件。使用MonthDay
类。这个类组合了月份和日,去掉 了年,这意味着你可以用它判断每年都会发生事件。
LocalDate dateOfBirth = LocalDate.of(1993, 01, 21);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(LocalDate.now());
if(currentMonthDay.equals(birthday)){
System.out.println("Many Many happy returns of the day !!");
}else{
System.out.println("Sorry, today is not your birthday");
}
//输出: Many Many happy returns of the day !!
判断日期是早于还是晚于另一个日期
LocalDate tomorrow = LocalDate.of(2019, 1, 22);
if(tomorrow.isAfter(LocalDate.now())){
System.out.println("Tomorrow comes after today");//Tomorrow comes after today
}
LocalDate yesterday = LocalDate.now().minus(1, ChronoUnit.DAYS);
if(yesterday.isBefore(LocalDate.now())){
System.out.println("Yesterday is day before today");//Yesterday is day before today
}
java8 时间类与Date类的相互转化
//Date与Instant的相互转化
Instant instant = Instant.now();
Date date = Date.from(instant);
Instant instant2 = date.toInstant();
//Date转为LocalDateTime
Date date2 = new Date();
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date2.toInstant(), ZoneId.systemDefault());
//LocalDateTime转Date
LocalDateTime localDateTime3 = LocalDateTime.now();
Instant instant3 = localDateTime3.atZone(ZoneId.systemDefault()).toInstant();
Date date3 = Date.from(instant);
//LocalDate转Date
//因为LocalDate不包含时间,所以转Date时,会默认转为当天的起始时间,00:00:00
LocalDate localDate4 = LocalDate.now();
Instant instant4 = localDate4.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
Date date4 = Date.from(instant);
日期的格式化和解析DateTimeFormatter
Java8
的DateTimeFormatter
也是线程安全的,而SimpleDateFormat
并不是线程安全。
*DateTimeFormatter
和SimpleDateFormat
对比 *
-
Date转String
//使用Date和SimpleDateFormat SimpleDateFormat simpleDateFormat = new SimpleDateFormat("G yyyy年MM月dd号 E a hh时mm分ss秒"); String format = simpleDateFormat.format(new Date()); System.out.println(format); //打印: 公元 2017年03月21号 星期二 下午 06时38分20秒
//使用jdk1.8 LocalDateTime和DateTimeFormatter
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("G yyyy年MM月dd号 E a hh时mm分ss秒");
String format = now.format(pattern);
System.out.println(format);
//打印: 公元 2017年03月21号 星期二 下午 06时38分20秒
-
String转Date
//使用Date和SimpleDateFormat SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Date date = simpleDateFormat.parse("2017-12-03 10:15:30"); System.out.println(simpleDateFormat.format(date)); //打印 2017-12-03 10:15:30
//使用jdk1.8 LocalDateTime和DateTimeFormatter DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); //严格按照ISO yyyy-MM-dd验证,03写成3都不行 LocalDateTime dt = LocalDateTime.parse("2017-12-03 10:15:30",pattern); System.out.println(dt.format(pattern));
使用SimpleDateFormat
的正确姿势
方法一
在需要执行格式化的地方都新建SimpleDateFormat实例,使用局部变量来存放SimpleDateFormat实例
public static String formatDate(Date date)throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
这种方法可能会导致短期内创建大量的SimpleDateFormat实例,如解析一个excel表格里的字符串日期。
方法二
为了避免创建大量的SimpleDateFormat实例,往往会考虑把SimpleDateFormat实例设为静态成员变量,共享SimpleDateFormat对象。这种情况下就得对SimpleDateFormat添加同步。
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
}
这种方法的缺点也很明显,就是在高并发的环境下会导致解析被阻塞。
方法三(推荐)
要在高并发环境下能有比较好的体验,可以使用ThreadLocal来限制SimpleDateFormat只能在线程内共享,这样就避免了多线程导致的线程安全问题。
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String format(Date date) {
return threadLocal.get().format(date);
}
//打印
public static void main(String[] args) {
System.out.println(format(new Date()));//2019-01-21
}
工具类
/**
* @author jinxm
* @date 2022-01-20
* @description 日期工具类
*/
public class DateTimeUtils {
public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* 返回当前的日期
*
* @return
*/
public static LocalDate getCurrentLocalDate() {
return LocalDate.now();
}
/**
* 返回当前日期时间
*
* @return
*/
public static LocalDateTime getCurrentLocalDateTime() {
return LocalDateTime.now();
}
/**
* 返回当前日期字符串 yyyyMMdd
*
* @return
*/
public static String getCurrentDateStr() {
return LocalDate.now().format(DATE_FORMATTER);
}
/**
* 返回当前日期时间字符串 yyyyMMddHHmmss
*
* @return
*/
public static String getCurrentDateTimeStr() {
return LocalDateTime.now().format(DATETIME_FORMATTER);
}
/**
* LocalDateTime
* @param dateTime 字符串时间
* @param pattern 格式
* @return
*/
public static LocalDateTime parseLocalDateTime(String dateTime, String pattern) {
return LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern(pattern));
}
/**
* 字符串转LocalDate
* @param date 字符串日期
* @param pattern 格式
* @return LocalDate
*/
public static LocalDate parseLocalDate(String date, String pattern) {
return LocalDate.parse(date, DateTimeFormatter.ofPattern(pattern));
}
/**
* Date转LocalDate
* @param date Date日期
* @return LocalDate日期
*/
public static LocalDate date2LocalDate(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
/**
* 日期相隔天数
*
* @param start
* @param end
* @return
*/
public static int periodDays(LocalDate start, LocalDate end) {
return Period.between(start, end).getDays();
}
/**
* 间隔多少天
* @param start 开始日期
* @param end 结束日期
* @return 间隔多少天
*/
public static long durationDays(LocalDate start, LocalDate end) {
return Duration.between(start, end).toDays();
}
/**
* 间隔多少小时
* @param start
* @param end
* @return
*/
public static long durationHours(LocalDateTime start, LocalDateTime end) {
return Duration.between(start, end).toHours();
}
/**
* 间隔多少分钟
* @param start 开始时间
* @param end 结束时间
* @return 间隔多少分钟
*/
public static long durationMinutes(LocalDateTime start, LocalDateTime end) {
return Duration.between(start, end).toMinutes();
}
/**
* 日期相隔毫秒数
*
* @param start
* @param end
* @return
*/
public static long durationMillis(LocalDateTime start, LocalDateTime end) {
return Duration.between(start, end).toMillis();
}
/**
* 是否当天
*
* @param date
* @return
*/
public static boolean isToday(LocalDate date) {
return getCurrentLocalDate().equals(date);
}
/**
* 获取月份第一天
*
* @param date
* @return
*/
public static LocalDate getFirstDay(LocalDate date) {
return date.with(TemporalAdjusters.firstDayOfMonth());
}
/**
* 获取月份最后一天
*
* @param date
* @return
*/
public static LocalDate getLastDay(LocalDate date) {
return date.with(TemporalAdjusters.lastDayOfMonth());
}
/**
* 获取2017-01的第一个周一
*
* @return
*/
public static String getFirstMonday() {
return LocalDate.parse("2017-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))
.format(DATE_FORMATTER);
}
/**
* 获取当前日期的后两周
*
* @return
*/
public static String getCurDateAfterTwoWeek() {
return getCurrentLocalDate().plus(2, ChronoUnit.WEEKS).format(DATE_FORMATTER);
}
/**
* 获取当前日期的6个月后的日期
*
* @return
*/
public static String getCurDateAfterSixMonth() {
return getCurrentLocalDate().plus(6, ChronoUnit.MONTHS).format(DATE_FORMATTER);
}
/**
* 获取当前日期的5年后的日期
*
* @return
*/
public static String getCurDateAfterFiveYear() {
return getCurrentLocalDate().plus(5, ChronoUnit.YEARS).format(DATE_FORMATTER);
}
/**
* 获取当前日期的20年后的日期
*
* @return
*/
public static String getCurDateAfterTwentyYear() {
return getCurrentLocalDate().plus(2, ChronoUnit.DECADES).format(DATE_FORMATTER);
}
}