主要内容
新的日期和时间API
在前几篇文章中介绍过一些旧的时间处理API,事实证明,旧的API存在着一些比较严重的问题。例如,java.util.Date
和SimpleDateFormatter
,它们并非是线程安全的,因此在多线程编程中有可能出现问题。其次,旧的API设计上是糟糕的,并不是那么规范。例如我们之前强调的,一个星期的开始是星期日,与之对应的常数是0,0=星期天,7=星期六。为了解决这些问题,在java 8中引入了java.time包,这个包中的所有对象都是不可变的,意味着如果我们对这些对象修改,会返回一个全新的实例。
时间线对象(Instant)
一个Instant对象表示时间轴上的一个点,原点规定为1970年1月1日的午夜(0点)。从原点开始,时间按照每天86400秒进行计算,向前向后分别以纳秒为单位。
创建Instant对象
使用Instant类的静态方法now()
创建一个Instant的实例,该实例是基于UTC时钟
的,也就是相对于我们使用的北京时间来说,晚了8个小时。
1 2 3 |
Instant instant = Instant.now(); //2017-03-04T02:50:11.066Z System.out.println(instant); |
Instant对象内部包括两个字段,一个long值,用来保存从1970年一月一日开始到现在为止的秒数,一个int值,保存的是纳秒数(不足一秒的都保存到这个值中)。我们可以使用下面两个方法访问这两个字段。
- getEpochSecond()
- getNano()
Instant对象的加减法
Instant类定义了一组plus方法和minus方法,我们可以使用这些方法方便地操作时间的加减运算
- plusSeconds()
- plusMillis()
- plusNanos()
- minusSeconds()
- minusMillis()
- minusNanos()
1 2 3 4 5 6 7 |
Instant now = Instant.now(); Instant aMinuteLater = now.plusSeconds(60); Instant aMinuteEarlier = now.minusSeconds(60); System.out.println(aMinuteLater); System.out.println(now); System.out.println(aMinuteEarlier); |
持续时间(Duration)对象
Duration对象代表时间轴上的两个时间点(Instant)之间经过的时间量。例如,如果要计算一段时间运行所需要的时间,可以使用Duration对象的between()
方法。
1 2 3 4 5 6 7 8 9 10 |
Instant begin = Instant.now(); try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } Instant end = Instant.now(); Duration usedTime = Duration.between(begin, end); //5 System.out.println(usedTime.getSeconds()); |
和Instant类一样,Duration内部也由两个部分组成
- 基于秒的持续时间
- 基于纳秒的持续时间,不足一秒的都保存在这里
同样地,也分别有两个方法可访问它们的值
- getNano()
- getSeconds()
创建Duration对象
Duration类提供了一系列的of()方法来创建Duration对象。
- ofDays(long days)
- ofHours(long hours)
- ofMinutes(long minutes)
更多的可查阅java doc了解。下面是一个简单的ofDay()
例子
1 2 3 |
Duration duration = Duration.ofDays(1); //24 System.out.println(duration.toHours()); |
Duration对象单位之间的转换
Duration内部提供了一系列的to()方法,可以方便地完成Hour、Millisecond、Day、Minute等之间的转换。
- toNanos()
- toMillis()
- toMinutes()
- toHours()
- toDays()
1 2 3 4 |
Duration duration = Duration.ofHours(1); long minutes = duration.toMinutes(); //60 System.out.println(minutes); |
和Duration对象的getNanos()
方法不同,getNano()方法只返回小于秒
的部分;而toNanos()方法会把整个Duration的时间值都转换为纳秒。你可能会好奇,为什么会没有toSeconds()
方法。原因在于,我们可以使用Duration的getSeconds()
方法获取这个以秒为单位的值。
1 2 3 4 |
Duration duration = Duration.ofMinutes(2); long seconds = duration.getSeconds(); //120 System.out.println(seconds); |
Duration对象计算
Duration类提供了一系列的plus和minus方法,用来对Duration对象进行加减法运算。
- plusNanos()
- plusMillis()
- plusSeconds()
- plusMinutes()
- plusHours()
- plusDays()
- minusNanos()
- minusMillis()
- minusSeconds()
- minusMinutes()
- minusHours()
- minusDays()
下面是依旧是简单的例子
1 2 3 4 5 6 7 |
Duration duration = Duration.ofDays(2); Duration plusDays = duration.plusDays(3); //5 System.out.println(plusDays.toDays()); Duration minusDays = plusDays.minusDays(1); //4 System.out.println(minusDays.toDays()); |
除了加法和减法,还可以对Duration对象进行乘法和除法操作
- multipliedBy(long multiplicand)
- dividedBy(long divisor)
ChronoUnit
ChronoUnit定义了一系列的时间测量单位
。使用ChronoUnit的between()
方法可以测量两个时间段之间的差值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Instant begin = Instant.now(); try{ Thread.sleep(3000); }catch(Exception e){ e.printStackTrace(); } Instant end = Instant.now(); long between = ChronoUnit.MILLIS.between(begin, end); //3000 System.out.println(between); long between2 = ChronoUnit.MICROS.between(begin, end); //3000000 System.out.println(between2); |
本地日期(LocalDate)
LocalDate表示一个不带时区信息的日期,通常表现形式为“年-月-日”。其他字段例如: day-of-year, day-of-week and week-of-year,特别有用. 例如: 2017年3月的第几天这样的信息就可以存储在LocalDate对象中。
创建LocalDate对象
可以创建一个基于现在日期的LocalDate对象,使用now()
方法即可。
1 2 3 |
LocalDate date = LocalDate.now(); //2017-03-04 System.out.println(date); |
同样地,LocalDate对象也提供了一系列的加减法操作。这些方法都使用minus
或plus
开头,具体可参考java doc。不过LocalDate最小的单位是Day(天)
,因为它本身不参与时间的显示。如果需要使用时间,可使用LocalTime
,使用方法可参阅java doc。
计算两个LocalDate之间的时间差
使用LocalDate对象的until()
方法可以测量两个对象之间的时间差。该方法的参数也是一个LocalDate对象。
1 2 3 4 5 |
LocalDate date1 = LocalDate.now(); LocalDate lastYear = LocalDate.of(2016, 1, 1); Period diff = date1.until(lastYear); //P-1Y-2M-3D System.out.println(diff); |
程序输出的结果有点奇怪:P-1Y-2M-3D,它表示一年两个月3日。我们可以用更人性化的方式使用until()方法。只需要为until添加第二个参数。但是使用两个参数的until方法返回值是long类型,而不是Period。
1 2 3 4 5 |
LocalDate thisYear = LocalDate.now(); LocalDate lastYear = LocalDate.of(2016, 1, 1); long diff = thisYear.until(lastYear, ChronoUnit.DAYS); //-428 System.out.println(diff); |
输出结果是-428天,表示thisYear比lastYear晚了428天。
获取LocalDate的日期信息
LocalDate提供了get方法获取日期信息,如年,月,日等
- getYear()
- getMonth()
- getDayOfMonth()
- getDayOfWeek()
- getDayOfYear()
和旧的api中不按常理的做法(月份从0开始,年份从1900开始计算)不同。可以使用与正常生活中一样的数字来表示月份(即1表示1月份)。除此之外,也可以使用Month枚举
Period
Period表示一段时期。它的最少单位是Day。通常我们创建一个Period对象会提供完整的年月日信息
,如果单独使用of
方法设置Period,其他没有被设置的字段都为0。
1 2 3 |
Period period = Period.ofDays(20); //20 System.out.println(period.getDays()); |
因为我们只使用了ofDays()方法设置了Days字段,其他的year和month字段都自动设置为0。
1 2 3 4 |
Period period = Period.ofDays(20); //0 0 System.out.println(period.getMonths()); System.out.println(period.getYears()); |
结果两个输出都是0。