Java 8通过发布新的日期API 来进一步加强对日期与时间的处理。

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全java.util.Date是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差 − Java的日期/时间类的定义并不一致,在java.utiljava.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendarjava.util.TimeZone类,但他们同样存在上述所有的问题。

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • ZoneId(时区) − 通过制定的时区处理日期时间。

新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对


LocalDate

只会获取年月日

  1. // 取当前日期
  2. LocalDate today = LocalDate.now();// 2018-12-26
  3. // 根据年月日取日期
  4. LocalDate crischristmas = LocalDate.of(2018, 12, 26);
  5. System.out.println(crischristmas.getClass()); // class java.time.LocalDate
  6. System.out.println(crischristmas); // 2018-12-26
  7. // 根据字符串取
  8. // 严格按照ISO yyyy-MM-dd验证,02写成2都不行,当然也有一个重载方法允许自己定义格式
  9. LocalDate endOfFeb = LocalDate.parse("2018-12-26");
  10. System.out.println(endOfFeb); // 2018-12-26
  11. // 无效日期无法通过:DateTimeParseException: Invalid date
  12. LocalDate.parse("2018-02-29"); // 报错提示不是闰年: Invalid date 'February 29' as '2018' is not a leap year
  13. //获取年、月、日、星期几
  14. int year = localDate.getYear();
  15. int year1 = localDate.get(ChronoField.YEAR);
  16. Month month = localDate.getMonth();
  17. int month1 = localDate.get(ChronoField.MONTH_OF_YEAR);
  18. int day = localDate.getDayOfMonth();
  19. int day1 = localDate.get(ChronoField.DAY_OF_MONTH);
  20. DayOfWeek dayOfWeek = localDate.getDayOfWeek();
  21. int dayOfWeek1 = localDate.get(ChronoField.DAY_OF_WEEK);
  22. // 取本月第1天:
  23. LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 2018-12-01
  24. System.out.println(firstDayOfThisMonth);
  25. // 取本月第2天:
  26. LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2018-12-02
  27. System.out.println(secondDayOfThisMonth);
  28. // 取本月最后一天,再也不用计算是28,29,30还是31:
  29. LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2018-12-31
  30. System.out.println(lastDayOfThisMonth);
  31. // 取下一天:
  32. LocalDate nextDayOfToDay = lastDayOfThisMonth.plusDays(1); // 变成了2019-01-01
  33. System.out.println(lastDayOfThisMonth);
  34. // 取2015年1月第一个周一,这个计算用Calendar要死掉很多脑细胞:
  35. LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2015-01-05
  36. //检查闰年
  37. LocalDate.now().isLeapYear()
  38. //如何用Java判断日期是早于还是晚于另一个日期
  39. //LocalDate中使用isBefore/isAfter
  40. //Date中使用befor/after

LocalTime

只会获取几点几分几秒

  1. 创建LocalTime
  2. LocalTime localTime = LocalTime.of(13, 51, 10);//13:51:10
  3. LocalTime mid = LocalTime.parse("12:00:00"); // 12:00:00
  4. LocalTime localTime1 = LocalTime.now();
  5. //获取时分秒
  6. //获取小时
  7. int hour = localTime.getHour();
  8. int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
  9. //获取分
  10. int minute = localTime.getMinute();
  11. int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
  12. //获取秒
  13. int second = localTime.getSecond();
  14. int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);
  15. //无效时间
  16. LocalTime mid1 = LocalTime.parse("25:00:00"); // 报错:Invalid value for HourOfDay (valid values 0 - 23): 25

LocalTime包含毫秒

  1. LocalTime now = LocalTime.now(); // 10:48:44.592
  2. //清除毫秒数
  3. LocalTime now = LocalTime.now().withNano(0)); // 10:49:13

LocalDateTime

获取年月日时分秒,等于LocalDate+LocalTime

  1. 创建LocalDateTime
  2. LocalDateTime localDateTime = LocalDateTime.now();
  3. LocalDateTime localDateTime1 = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
  4. LocalDateTime localDateTime2 = LocalDateTime.of(localDate, localTime);
  5. LocalDateTime localDateTime3 = localDate.atTime(localTime);
  6. LocalDateTime localDateTime4 = localTime.atDate(localDate);
  7. 获取LocalDate
  8. LocalDate localDate2 = localDateTime.toLocalDate();
  9. 获取LocalTime
  10. 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,表示纳秒部分

  1. 创建Instant对象
  2. Instant instant = Instant.now();
  3. 获取秒数
  4. long currentSecond = instant.getEpochSecond();
  5. 获取毫秒数
  6. long currentMilli = instant.toEpochMilli();

个人觉得如果只是为了获取秒数或者毫秒数,使用System.currentTimeMillis()来得更为方便

ZoneId

对时区处理的优化也是Java8中日期时间API的一大亮点。之前在业务中是真的遇到过一些奇葩的时区问题,在旧的java.util.TimeZone提供的时区不全不说,操作还非常繁琐。
新的时区类java.time.ZoneId是原有的java.util.TimeZone类的替代品。

  1. //创建时区,of()方法接收一个“区域/城市”的字符串作为参数
  2. ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
  3. //获取系统默认时区
  4. ZoneId systemZoneId = ZoneId.systemDefault();
  5. //获取所有合法的“区域/城市”字符串
  6. Set<String> zoneIds = ZoneId.getAvailableZoneIds();
  7. //转化老的时区类TimeZone
  8. ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();
  9. //将一个LocalDate、LocalTime或LocalDateTime对象转化为ZonedDateTime对象
  10. LocalDateTime localDateTime = LocalDateTime.now();
  11. ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);
  12. //打印:2020-02-22T16:50:54.658+08:00[Asia/Shanghai]
  13. /*
  14. *ZonedDateTime对象由两部分构成,LocalDateTime和ZoneId,
  15. *其中2020-02-22T16:50:54.658部分为LocalDateTime,
  16. *+08:00Asia/Shanghai部分为ZoneId
  17. */

日期API常用操作

LocalDate、LocalTime、LocalDateTime、Instant为不可变对象,修改这些对象对象会返回一个副本

  1. //增加、减少年数、月数、天数等
  2. //以LocalDateTime为例
  3. LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 10,
  4. 14, 46, 56);
  5. //增加一年
  6. localDateTime = localDateTime.plusYears(1);
  7. localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
  8. //减少一个月
  9. localDateTime = localDateTime.minusMonths(1);
  10. localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);
  11. //通过with修改某些值
  12. //修改年为2019
  13. localDateTime = localDateTime.withYear(2020);
  14. //修改为2022
  15. localDateTime = localDateTime.with(ChronoField.YEAR, 2022);
  16. //还可以修改月、日

格式化时间

  1. LocalDate localDate = LocalDate.of(2019, 9, 10);
  2. String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
  3. String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
  4. //自定义格式化
  5. DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
  6. String s3 = localDate.format(dateTimeFormatter);

DateTimeFormatter默认提供了多种格式化方式,如果默认提供的不能满足要求,可以通过DateTimeFormatter的ofPattern方法创建自定义格式化方式

解析时间

  1. LocalDate localDate1 = LocalDate.parse("20190910", DateTimeFormatter.BASIC_ISO_DATE);
  2. LocalDate localDate2 = LocalDate.parse("2019-09-10", DateTimeFormatter.ISO_LOCAL_DATE);

和SimpleDateFormat相比,DateTimeFormatter是线程安全的

LocalDateTime 与 Date 的相互转化

  1. // Date 转化成 LocalDateTime
  2. public static LocalDateTime dateToLocalDate(Date date) {
  3. Instant instant = date.toInstant();
  4. ZoneId zoneId = ZoneId.systemDefault();
  5. return instant.atZone(zoneId).toLocalDateTime();
  6. }
  7. // LocalDateTime 转化成 Date
  8. public static Date localDateTimeToDate(LocalDateTime localDateTime) {
  9. ZoneId zoneId = ZoneId.systemDefault();
  10. ZonedDateTime zdt = localDateTime.atZone(zoneId);
  11. return Date.from(zdt.toInstant());
  12. }

由于 LocalDate、LocalTime 或者只含有日期,或者只含有时间,因此,不能和Date直接进行转化