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.LocalDateSystem.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 dateLocalDate.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-01System.out.println(firstDayOfThisMonth);// 取本月第2天:LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2018-12-02System.out.println(secondDayOfThisMonth);// 取本月最后一天,再也不用计算是28,29,30还是31:LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2018-12-31System.out.println(lastDayOfThisMonth);// 取下一天:LocalDate nextDayOfToDay = lastDayOfThisMonth.plusDays(1); // 变成了2019-01-01System.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
只会获取几点几分几秒
创建LocalTimeLocalTime localTime = LocalTime.of(13, 51, 10);//13:51:10LocalTime mid = LocalTime.parse("12:00:00"); // 12:00:00LocalTime 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
创建LocalDateTimeLocalDateTime 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);获取LocalDateLocalDate localDate2 = localDateTime.toLocalDate();获取LocalTimeLocalTime 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();//转化老的时区类TimeZoneZoneId 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修改某些值//修改年为2019localDateTime = localDateTime.withYear(2020);//修改为2022localDateTime = 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 转化成 LocalDateTimepublic static LocalDateTime dateToLocalDate(Date date) {Instant instant = date.toInstant();ZoneId zoneId = ZoneId.systemDefault();return instant.atZone(zoneId).toLocalDateTime();}// LocalDateTime 转化成 Datepublic static Date localDateTimeToDate(LocalDateTime localDateTime) {ZoneId zoneId = ZoneId.systemDefault();ZonedDateTime zdt = localDateTime.atZone(zoneId);return Date.from(zdt.toInstant());}
由于 LocalDate、LocalTime 或者只含有日期,或者只含有时间,因此,不能和Date直接进行转化
