前言
这一节我们来讲讲JAVA8的日期类,源代码的作者其实就是Joda-Time,所以可以看到很多代码的API和Joda类比较像。日期类一直是一个比较难用的东西,但是JAVA8给日期类提供了一套新的API让日期类更加好用。
本文代码较多,建议亲自运行代码理解。
思维导图:
地址:www.mubucm.com/doc/ck5ZCrg…
内容概述:
- 关于JDK8日期的三个核心类:LocalDate、LocalTime、LocalDateTime的相关介绍
- 机器时间和日期格式
Instant
等关于细粒度的时间操作介绍 - TemporalAdjusters 用于更加复杂的日期计算,比如计算下一个工作日的时候这个类提供了一些实现
- DateTimeFormatter 格式化器,非常的灵活多变,属于
SimpleDateFormat
的替代品。 - 日期API的一些个人工具封装举例,以及在使用JDK8的时候一些个人的踩坑
最后希望通过本文能帮你摆脱new Date()
什么是ISO-8601?
日期离不开ISO-8601,下面对ISO-8601简单描述一下,参考自百度百科:
- ISO-8601: 国际标准化组织制定的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》,简称为ISO-8601。
- 日的表示:小时、分和秒都用2位数表示,对UTC时间最后加一个大写字母Z,其他时区用实际时间加时差表示。如UTC时间下午2点30分5秒表示为14:30:05Z或143005Z,当时的北京时间表示为22:30:05+08:00或223005+0800,也可以简化成223005+08。
- 日期和时间的组合表示:合并表示时,要在时间前面加一大写字母T,如要表示北京时间2004年5月3日下午5点30分8秒,可以写成2004-05-03T17:30:08+08:00或20040503T173008+08。
LocalDate、LocalTime、LocalDateTime
JDK8把时间拆分成了三个大部分,一个是时间,代表了年月日的信息,一个是日期,代表了时分秒的部分,最后是这两个对象总和具体的时间。
LocalDate
LocalDate
:类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过LocalDate
的静态方法of()
创建一个实例,LocalDate
也包含一些方法用来获取年份,月份,天,星期几等,下面是LocalDate
的常见使用方式:
@Test public void localDateTest() throws Exception { // 创建一个LocalDate: LocalDate of = LocalDate.of(2021, 8, 9); // 获取当前时间 LocalDate now = LocalDate.now(); // 格式化 LocalDate parse1 = LocalDate.parse("2021-05-11"); // 指定日期格式化 LocalDate parse2 = LocalDate.parse("2021-05-11", DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 下面的代码会出现格式化异常 // java.time.format.DateTimeParseException: Text '2021-05-11 11:53:53' could not be parsed, unparsed text found at index 10 // LocalDate parse3 = LocalDate.parse("2021-05-11 11:53:53", DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 正确的格式化方法 LocalDate parse3 = LocalDate.parse("2021-05-11 11:53:53", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // 当前时间 System.out.println("now() => "+ now); // 获取月份 int dayOfMonth = parse1.getDayOfMonth(); System.out.println("dayOfMonth => " + dayOfMonth); // 获取年份 int dayOfYear = parse1.getDayOfYear(); System.out.println("getDayOfYear => " + dayOfYear); // 获取那一周,注意这里获取的是对象 DayOfWeek dayOfWeek = parse1.getDayOfWeek(); System.out.println("getDayOfWeek => " + dayOfWeek); // 获取月份数据 int monthValue = parse3.getMonthValue(); System.out.println("getMonthValue => " + monthValue); // 获取年份 int year = parse3.getYear(); System.out.println("getYear => " + year); // getChronology 获取的是当前时间的排序,这里输出结果是 ISO System.out.println("getChronology => " + parse3.getChronology()); System.out.println("getEra => " + parse3.getEra()); // 使用timeField获取值:TemporalField 是一个接口,定义了如何访问 TemporalField 的值,ChronnoField 实现了这个接口 /* LocalDate 支持的格式如下: case DAY_OF_WEEK: return getDayOfWeek().getValue(); case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1; case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1; case DAY_OF_MONTH: return day; case DAY_OF_YEAR: return getDayOfYear(); case EPOCH_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'EpochDay' for get() method, use getLong() instead"); case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1; case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1; case MONTH_OF_YEAR: return month; case PROLEPTIC_MONTH: throw new UnsupportedTemporalTypeException("Invalid field 'ProlepticMonth' for get() method, use getLong() instead"); case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year); case YEAR: return year; case ERA: return (year >= 1 ? 1 : 0); * */ // Unsupported field: HourOfDay // System.out.println("ChronoField.HOUR_OF_DAY => " + parse1.get(ChronoField.HOUR_OF_DAY)); // Unsupported field: MinuteOfHour // System.out.println("ChronoField.MINUTE_OF_HOUR => " + parse1.get(ChronoField.MINUTE_OF_HOUR)); // Unsupported field: MinuteOfHour // System.out.println("ChronoField.SECOND_OF_MINUTE => " + parse1.get(ChronoField.SECOND_OF_MINUTE)); System.out.println("ChronoField.YEAR => " + parse1.get(ChronoField.YEAR)); // Unsupported field: MinuteOfHour // System.out.println("ChronoField.INSTANT_SECONDS => " + parse1.get(ChronoField.INSTANT_SECONDS)); }/*运行结果: now() => 2021-08-08 dayOfMonth => 11 getDayOfYear => 131 getDayOfWeek => TUESDAY getMonthValue => 5 getYear => 2021 getChronology => ISO getEra => CE ChronoField.YEAR => 2021 */ 复制代码
TemporalField 是一个接口,定义了如何访问 TemporalField 的值,ChronnoField 实现了这个接口
LocalTime
LocalTime
:和LocalDate
类似,区别在于包含具体时间,同时拥有更多操作具体时间时间的方法,下面是对应的方法以及测试:
@Test public void localTimeTest() throws Exception { LocalTime now = LocalTime.now(); System.out.println("LocalTime.now() => "+ now); System.out.println("getHour => "+ now.getHour()); System.out.println("getMinute => "+ now.getMinute()); System.out.println("getNano => "+ now.getNano()); System.out.println("getSecond => "+ now.getSecond()); LocalTime systemDefault = LocalTime.now(Clock.systemDefaultZone()); // ZoneName => java.time.format.ZoneName.zidMap 从这个map里面进行获取 LocalTime japan = LocalTime.now(Clock.system(ZoneId.of("Japan"))); // 或者直接更换时区 LocalTime japan2 = LocalTime.now(ZoneId.of("Japan")); // 格式化时间 LocalTime localTime = LocalTime.of(15, 22); // from 从另一个时间进行转化,只要他们接口兼容 LocalTime from = LocalTime.from(LocalDateTime.now()); // 范湖纳秒值 LocalTime localTime1 = LocalTime.ofNanoOfDay(1); LocalTime localTime2 = LocalTime.ofSecondOfDay(1); // 越界异常 Invalid value for MinuteOfHour (valid values 0 - 59): 77 // LocalTime.of(15, 77); // 获取本地的默认时间 System.out.println("LocalTime.now(Clock.systemDefaultZone()) => "+ systemDefault); // 获取日本时区的时间 System.out.println("LocalTime.now(Clock.system(ZoneId.of(\"Japan\"))) => "+ japan); System.out.println("LocalTime.now(ZoneId.of(\"Japan\")) => "+ japan2); System.out.println("LocalTime.of(15, 22) => "+ localTime); System.out.println("LocalTime.from(LocalDateTime.now()) => "+ from); System.out.println("LocalTime.ofNanoOfDay(1) => "+ localTime1); System.out.println("LocalTime.ofSecondOfDay(1) => "+ localTime2); }/*运行结果: LocalTime.now() => 12:58:13.553 getHour => 12 getMinute => 58 getNano => 553000000 getSecond => 13 LocalTime.now(Clock.systemDefaultZone()) => 12:58:13.553 LocalTime.now(Clock.system(ZoneId.of("Japan"))) => 13:58:13.553 LocalTime.now(ZoneId.of("Japan")) => 13:58:13.553 LocalTime.of(15, 22) => 15:22 LocalTime.from(LocalDateTime.now()) => 12:58:13.553 LocalTime.ofNanoOfDay(1) => 00:00:00.000000001 LocalTime.ofSecondOfDay(1) => 00:00:01 */ 复制代码
LocalDateTime
LocalDateTime
:LocalDateTime
类是LocalDate
和LocalTime
的结合体,可以通过of()
方法直接创建,也可以调用LocalDate
的atTime()
方法或LocalTime
的atDate()
方法将LocalDate
或LocalTime
合并成一个LocalDateTime
,下面是一些简单的方法测试,由于篇幅有限,后续会结合这些内容编写一个工具类的代码。
@Test public void localDateTimeTest() throws Exception { //Text '2021-11-11 15:30:11' could not be parsed at index 10 // LocalDateTime parse = LocalDateTime.parse("2021-11-11 15:30:11"); // 默认使用的是ISO的时间格式 LocalDateTime parse1 = LocalDateTime.parse("2011-12-03T10:15:30"); // 如果要自己的格式,需要手动格式化 LocalDateTime parse = LocalDateTime.parse("2021-11-11 15:30:11", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); System.out.println("LocalDateTime.parse(....) => "+ parse1); System.out.println("LocalDateTime.parse(....) => "+ parse); LocalDateTime of = LocalDateTime.of(LocalDate.now(), LocalTime.now()); LocalDateTime japan = LocalDateTime.now(ZoneId.of("Japan")); System.out.println("LocalDateTime.of(LocalDate.now(), LocalTime.now()) => "+ of); System.out.println("LocalDateTime.now(ZoneId.of(\"Japan\")) => "+ japan); }/*运行结果: LocalDateTime.parse(....) => 2011-12-03T10:15:30 LocalDateTime.parse(....) => 2021-11-11T15:30:11 LocalDateTime.of(LocalDate.now(), LocalTime.now()) => 2021-08-08T13:22:59.697 LocalDateTime.now(ZoneId.of("Japan")) => 2021-08-08T14:22:59.697 */ 复制代码
细粒度机器时间操作
JDK8还对机器的时间进行了分类,比如像下面这样
Instant
Instant
用于表示一个时间戳,它与我们常使用的System.currentTimeMillis()
有些类似,不过Instant
可以精确到纳秒(Nano-Second)
注意: 内部使用了两个常量,
seconds
表示从1970-01-01 00:00:00开始到现在的秒数,nanos
表示纳秒部分(nanos
的值不会超过999,999,999
)
下面是一些具体的测试用例:
@Test public void instantTest() throws Exception { Instant now = Instant.now(); // Unable to obtain Instant from TemporalAccessor: 2021-08-08T13:37:34.403 of type java.time.LocalDateTime // Instant from = Instant.from(LocalDateTime.now()); Instant instant = Instant.ofEpochSecond(3, 0); Instant instant1 = Instant.ofEpochSecond(5, 1_000_000_000); System.out.println("Instant.now() => "+ now); // System.out.println("Instant.from(LocalDateTime.now()) => "+ from); System.out.println("Instant.ofEpochSecond => "+ instant); System.out.println("Instant.ofEpochSecond => "+ instant1); System.out.println("Instant.get(ChronoField.NANO_OF_SECOND) => "+ now.get(ChronoField.NANO_OF_SECOND)); }/*运行结果: Instant.now() => 2021-08-08T05:42:42.465Z Instant.ofEpochSecond => 1970-01-01T00:00:03Z Instant.ofEpochSecond => 1970-01-01T00:00:06Z Instant.get(ChronoField.NANO_OF_SECOND) => 465000000 */ 复制代码
Duration
Duration
的内部实现与Instant
类似,也是包含两部分:seconds
表示秒,nanos
表示纳秒。两者的区别是Instant
用于表示一个时间戳(或者说是一个时间点),而Duration
表示一个时间段,比如想要获取两个时间的差值:
@Test public void durationTest() throws Exception { // Text '201-08-08T10:15:30' could not be parsed at index 0 Duration between = Duration.between(LocalDateTime.parse("2011-12-03T10:15:30"), LocalDateTime.parse("2021-08-08T10:15:30")); System.out.println("Duration.between(LocalDateTime.parse(\"2011-12-03T10:15:30\"), LocalDateTime.parse(\"2021-08-08T10:15:30\")) => "+ between); Duration duration = Duration.ofDays(7); System.out.println("Duration.ofDays(7) => "+ duration); } 复制代码
Period
Period
在概念上和Duration
类似,区别在于Period
是以年月日来衡量一个时间段(比如2年3个月6天),下面是对应单元测试以及相关的代码:
@Test public void periodTest() throws Exception { Period between = Period.between(LocalDate.parse("2011-12-03"), LocalDate.parse("2021-08-08")); Period period = Period.ofWeeks(53); Period period1 = Period.ofWeeks(22); System.out.println("Period.between(LocalDate.parse(\"2011-12-03\"), LocalDate.parse(\"2021-08-08\")) => "+ between); System.out.println("Period.ofWeeks(53) => "+ period); System.out.println("Period.ofWeeks(53) getDays => "+ period.getDays()); // 注意,这里如果没有对应值,会出现 0 System.out.println("Period.ofWeeks(53) getMonths => "+ period.getMonths()); System.out.println("Period.ofWeeks(22) getMonths => "+ period1.getMonths()); System.out.println("Period.ofWeeks(22) getYears => "+ period1.getYears()); }/*运行结果: Period.between(LocalDate.parse("2011-12-03"), LocalDate.parse("2021-08-08")) => P9Y8M5D Period.ofWeeks(53) => P371D Period.ofWeeks(53) getDays => 371 Period.ofWeeks(53) getMonths => 0 Period.ofWeeks(22) getMonths => 0 Period.ofWeeks(22) getYears => 0 */ 复制代码
TemporalAdjusters 复杂日期操作
这个类可以对于时间进行各种更加复杂的操作,比如下一个工作日,本月的最后一天,这时候我们可以借助with
这个方法进行获取:
@Test public void testTemporalAdjusters(){ LocalDate of = LocalDate.of(2021, 8, 1); // 获取当前年份的第一天 LocalDate with = of.with(TemporalAdjusters.firstDayOfYear()); System.out.println(" TemporalAdjusters.firstDayOfYear => "+ with); // 获取指定日期的下一个周六 LocalDate with1 = of.with(TemporalAdjusters.next(DayOfWeek.SATURDAY)); System.out.println(" TemporalAdjusters.next(DayOfWeek.SATURDAY) => "+ with1); // 获取当月的最后一天 LocalDate with2 = of.with(TemporalAdjusters.lastDayOfMonth()); System.out.println("TemporalAdjusters.lastDayOfMonth() => "+ with2); } 复制代码
下面从网络找到一份表,对应所有的方法作用
方法名 | 描述 |
dayOfWeekInMonth |
返回同一个月中每周的第几天 |
firstDayOfMonth |
返回当月的第一天 |
firstDayOfNextMonth |
返回下月的第一天 |
firstDayOfNextYear |
返回下一年的第一天 |
firstDayOfYear |
返回本年的第一天 |
firstInMonth |
返回同一个月中第一个星期几 |
lastDayOfMonth |
返回当月的最后一天 |
lastDayOfNextMonth |
返回下月的最后一天 |
lastDayOfNextYear |
返回下一年的最后一天 |
lastDayOfYear |
返回本年的最后一天 |
lastInMonth |
返回同一个月中最后一个星期几 |
next / previous |
返回后一个/前一个给定的星期几 |
nextOrSame / previousOrSame |
返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回 |
DateTimeFormatter 格式化器
这个类可以认为是用来替代SimpleDateFormat
这个类,他拥有更加强大的定制化操作,同时他是线程安全的类,不用担心多线程访问会出现问题。
下面是根据DateTimeFormatter 构建一个本土化的格式化器,代码也十分的简单易懂:
private static DateTimeFormatter generateDefualtPattern(String timeFormat) { return new DateTimeFormatterBuilder().appendPattern(timeFormat) .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) .toFormatter(Locale.CHINA); } 复制代码
时区信息
时区信息一般用的比较少,在做和国际化相关的操作时候有可能会用到,比如最近个人从苹果买了一个东西,虽然我下单是在6号,但是电话说订单时间却是5号下单的,这里个人认为苹果的确切下单时间是按照美国时间算的。
JDK8日期类关于时区的强相关类(注意是JDK8才出现的类,不要误认为是对之前类的兼容),在之前的单元测试其实已经用到了相关时区的方法,在JDK8中使用了 ZoneId
这个类来表示,但是我们有时候不知道怎么获取地区,可以参考下面的内容:
// ZoneName => java.time.format.ZoneName.zidMap 从这个map里面进行获取 LocalTime japan = LocalTime.now(Clock.system(ZoneId.of("Japan"))); 复制代码