Java 8通过发布新的日期API 来进一步加强对日期与时间的处理。
在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
- 非线程安全 −
java.util.Date
是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。 - 设计很差 − Java的日期/时间类的定义并不一致,在
java.util
和java.sql
的包中都有日期类,此外用于格式化和解析的类在java.text
包中定义。java.util.Date
同时包含日期和时间,而java.sql.Date
仅包含日期,将其纳入java.sql
包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。 - 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了
java.util.Calendar
和java.util.TimeZone
类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
- Local(本地) − 简化了日期时间的处理,没有时区的问题。
- ZoneId(时区) − 通过制定的时区处理日期时间。
新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date
配合Calendar要写好多代码,而且一般的开发人员还不一定能写对
LocalDate
只会获取年月日
// 取当前日期
LocalDate today = LocalDate.now();// 2018-12-26
// 根据年月日取日期
LocalDate crischristmas = LocalDate.of(2018, 12, 26);
System.out.println(crischristmas.getClass()); // class java.time.LocalDate
System.out.println(crischristmas); // 2018-12-26
// 根据字符串取
// 严格按照ISO yyyy-MM-dd验证,02写成2都不行,当然也有一个重载方法允许自己定义格式
LocalDate endOfFeb = LocalDate.parse("2018-12-26");
System.out.println(endOfFeb); // 2018-12-26
// 无效日期无法通过:DateTimeParseException: Invalid date
LocalDate.parse("2018-02-29"); // 报错提示不是闰年: Invalid date 'February 29' as '2018' is not a leap year
//获取年、月、日、星期几
int year = localDate.getYear();
int year1 = localDate.get(ChronoField.YEAR);
Month month = localDate.getMonth();
int month1 = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.getDayOfMonth();
int day1 = localDate.get(ChronoField.DAY_OF_MONTH);
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
int dayOfWeek1 = localDate.get(ChronoField.DAY_OF_WEEK);
// 取本月第1天:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 2018-12-01
System.out.println(firstDayOfThisMonth);
// 取本月第2天:
LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2018-12-02
System.out.println(secondDayOfThisMonth);
// 取本月最后一天,再也不用计算是28,29,30还是31:
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2018-12-31
System.out.println(lastDayOfThisMonth);
// 取下一天:
LocalDate nextDayOfToDay = lastDayOfThisMonth.plusDays(1); // 变成了2019-01-01
System.out.println(lastDayOfThisMonth);
// 取2015年1月第一个周一,这个计算用Calendar要死掉很多脑细胞:
LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2015-01-05
//检查闰年
LocalDate.now().isLeapYear()
//如何用Java判断日期是早于还是晚于另一个日期
//LocalDate中使用isBefore/isAfter
//Date中使用befor/after
LocalTime
只会获取几点几分几秒
创建LocalTime
LocalTime localTime = LocalTime.of(13, 51, 10);//13:51:10
LocalTime mid = LocalTime.parse("12:00:00"); // 12:00:00
LocalTime localTime1 = LocalTime.now();
//获取时分秒
//获取小时
int hour = localTime.getHour();
int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
//获取分
int minute = localTime.getMinute();
int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
//获取秒
int second = localTime.getSecond();
int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);
//无效时间
LocalTime mid1 = LocalTime.parse("25:00:00"); // 报错:Invalid value for HourOfDay (valid values 0 - 23): 25
LocalTime包含毫秒
LocalTime now = LocalTime.now(); // 10:48:44.592
//清除毫秒数
LocalTime now = LocalTime.now().withNano(0)); // 10:49:13
LocalDateTime
获取年月日时分秒,等于LocalDate+LocalTime
创建LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
LocalDateTime localDateTime2 = LocalDateTime.of(localDate, localTime);
LocalDateTime localDateTime3 = localDate.atTime(localTime);
LocalDateTime localDateTime4 = localTime.atDate(localDate);
获取LocalDate
LocalDate localDate2 = localDateTime.toLocalDate();
获取LocalTime
LocalTime localTime2 = localDateTime.toLocalTime();
新推出来的这三个类,与MySQL中的日期时间类型正好对应,如果我们使用MySQL数据库的话,在插入相应字段的时候,都不需要再进行任何的转化了。对应关系如下:
LocalTime 对应 time LocalDate 对应 date LocalDateTime 对应 datetime(timestamp)
Instant
Instant是时间线上的一个点,表示一个时间戳。Instant可以精确到纳秒,这超过了long的最大表示范围,所以在Instant的实现中是分成了两部分来表示,一部分是seconds,表示从1970-01-01 00:00:00开始到现在的秒数,另一个部分是nanos,表示纳秒部分
创建Instant对象
Instant instant = Instant.now();
获取秒数
long currentSecond = instant.getEpochSecond();
获取毫秒数
long currentMilli = instant.toEpochMilli();
个人觉得如果只是为了获取秒数或者毫秒数,使用System.currentTimeMillis()来得更为方便
ZoneId
对时区处理的优化也是Java8中日期时间API的一大亮点。之前在业务中是真的遇到过一些奇葩的时区问题,在旧的java.util.TimeZone
提供的时区不全不说,操作还非常繁琐。
新的时区类java.time.ZoneId
是原有的java.util.TimeZone
类的替代品。
//创建时区,of()方法接收一个“区域/城市”的字符串作为参数
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
//获取系统默认时区
ZoneId systemZoneId = ZoneId.systemDefault();
//获取所有合法的“区域/城市”字符串
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
//转化老的时区类TimeZone
ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();
//将一个LocalDate、LocalTime或LocalDateTime对象转化为ZonedDateTime对象
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);
//打印:2020-02-22T16:50:54.658+08:00[Asia/Shanghai]
/*
*ZonedDateTime对象由两部分构成,LocalDateTime和ZoneId,
*其中2020-02-22T16:50:54.658部分为LocalDateTime,
*+08:00Asia/Shanghai部分为ZoneId
*/
日期API常用操作
LocalDate、LocalTime、LocalDateTime、Instant为不可变对象,修改这些对象对象会返回一个副本
//增加、减少年数、月数、天数等
//以LocalDateTime为例
LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 10,
14, 46, 56);
//增加一年
localDateTime = localDateTime.plusYears(1);
localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
//减少一个月
localDateTime = localDateTime.minusMonths(1);
localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);
//通过with修改某些值
//修改年为2019
localDateTime = localDateTime.withYear(2020);
//修改为2022
localDateTime = localDateTime.with(ChronoField.YEAR, 2022);
//还可以修改月、日
格式化时间
LocalDate localDate = LocalDate.of(2019, 9, 10);
String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
//自定义格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s3 = localDate.format(dateTimeFormatter);
DateTimeFormatter默认提供了多种格式化方式,如果默认提供的不能满足要求,可以通过DateTimeFormatter的ofPattern方法创建自定义格式化方式
解析时间
LocalDate localDate1 = LocalDate.parse("20190910", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate localDate2 = LocalDate.parse("2019-09-10", DateTimeFormatter.ISO_LOCAL_DATE);
和SimpleDateFormat相比,DateTimeFormatter是线程安全的
LocalDateTime 与 Date 的相互转化
// Date 转化成 LocalDateTime
public static LocalDateTime dateToLocalDate(Date date) {
Instant instant = date.toInstant();
ZoneId zoneId = ZoneId.systemDefault();
return instant.atZone(zoneId).toLocalDateTime();
}
// LocalDateTime 转化成 Date
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
ZoneId zoneId = ZoneId.systemDefault();
ZonedDateTime zdt = localDateTime.atZone(zoneId);
return Date.from(zdt.toInstant());
}
由于 LocalDate、LocalTime 或者只含有日期,或者只含有时间,因此,不能和Date直接进行转化