Java8新特性--日期和时间API

如何正确处理时间

现实生活的世界里,时间是不断向前的,若是向前追溯时间的起点,多是宇宙出生时,又或是是宇宙出现以前, 
但确定是咱们目前没法找到的,咱们不知道如今距离时间原点的精确距离。因此咱们要表示时间, 
就须要人为定义一个原点。java

原点被规定为,格林威治时间(GMT)1970年1月1日的午夜 为起点,之于为啥是GMT时间,大概是由于本初子午线在那的缘由吧。sql

Java中的时间

若是你跟你朋友说:“咱们 1484301456 一块儿去吃饭,别迟到!”,而你朋友能立刻理解你说的时间,表示时间就会很简单, 
只须要一个long值来表示原点的偏移量,这是个绝对时间,在世界范围内都适用。但实际上咱们不能立刻理解这串数字, 
并且咱们须要不一样的时间单位来表示时间的跨度,好比一个季度是3个月,一个月有30天等。 
你能够跟朋友约好“明天这个时候再见面”,你朋友很容易理解明天的意思,但要是没有’天’这个单位, 
他就须要在那串数字上加上86400(一天是86400秒)。数据库

Java三次引入处理时间的API,JDK1.0中包含了一个Date类,但大多数方法在java1.1引入Calendear类以后被弃用了。 
它的实例都是可变的,并且它的API很难使用,好比月份是从0开始这种反人类的设置。api

java8引入的java.time API 已经纠正了以前的问题。它已经彻底实现了JSR310规范。安全

java8时间API介绍及使用

在新的时间API中,Instant表示一个精确的时间点,DurationPeriod表示两个时间点之间的时间量。 
LocalDate表示日期,即xx年xx月xx日,即不包括时间也不带时区。LocalTimeLocalDate相似, 
但只包含时间。LocalDateTime则包含日期和时间。ZoneDateTime表示一个带时区的时间。 
DateTimeFormatter提供格式化和解析功能。下面详细的介绍使用方法。markdown

Instant

Instant表示一个精确的时间,时间数轴就是由无数个时间点组成,数轴的原点就是上面提 
到的1970-1-1 00:00:00Instant由两部分组成,一是从原点开始到指定时间点的秒数s, 
二是距离该秒数s的纳秒数。ide

使用静态方法Instant.now()能够获取当前的时间点,该方法默认使用的是UTC(协调世界时——由原子钟提供)时间,可使用equeal 和 compareTo来比较两个时间点的值。post

计算某段代码执行时间可使用下面的方式:测试

Instant start = Instant.now(); doSomething(); Instant end = Instant.now(); Duration timeElapsed = Duration.between(start, end); long millis = timeElapsed.toMillis(); System.out.println("millis = " + millis);

Duration对象表示两个时间点之间的距离,经过相似toMillis() toDays() getSeconds()等方法, 
获得各类时间单位表示的Duration对象。若是确实须要使用纳秒来作一些计算,能够调用toNanos() 
得到一个long类型的值,该值表示距离原点的纳秒值。大概300年的纳秒值会致使long值溢出。spa

Duration内部使用一个long类型来保存秒钟的值,使用一个int来保存纳秒的值,与Instant相似, 
这个纳秒保存的是距离该秒钟的纳秒值.

Instant与Duration均可以进行一些运算,来调整表示的时间,好比:plus() minus 方法, 
表示增长或减小一段时间,plusSeconds() minusSeconds() plusXxx()等表示增长或减小相应时间单位的一段时间。

Duration能够进行multipliedBy()乘法和dividedBy()除法运算。negated()作取反运算,即1.2秒取反后为-1.2秒。

很是重要的是,Instant 和 Duration类都是不可变的,他们的全部方法都返回一个新的实例。不可变类有不少优势: 
不可变类使用起来不容易出错,其本质上是线程安全的,对象能够被自由的共享,而不用担忧被某个方法修改。

LocalDate(本地日期)

上面介绍的Instant是一个绝对的准确时间点,是人类不容易理解的时间,如今介绍人类使用的时间。

LocalDate 表示像 2017-01-01这样的日期。它包含有年份、月份、当月天数,它不不包含一天中的时间, 
以及时区信息。因为上面的这些特色,因此LocalDate不能表示一个准确的时间点,即Instant。

有不少时间的计算是不须要时区的,并且有一些状况下使用时区会致使一些问题,例如你在中国设置了一个 
2017-01-01 UT+8:00 的放假提醒,但以后你去了美国,到了2017-01-01 UT+8:00时间时你收到了提醒, 
可是此时美国还没到放假的时间。

API的设计者推荐使用不带时区的时间,除非真的但愿表示绝对的时间点。

可使用静态方法now()of()建立LocalDate。java.util.Date使用0做为月份的开始,年份从1990年开始算起, 
而新的API中彻底是用生活中同样的方式来表示年和月份。

//获取当前日期 LocalDate now = LocalDate.now(); //2017-01-01 LocalDate newYear = LocalDate.of(2017, 1, 1);

能够经过一些方法对日期作一些运算。

//三天后 now.plusDays(3); //一周后 now.plusWeeks(1) //两天前 now.minusDays(2) //增长一个月不会出现2017-02-31 而是会返回该月的最后一个有效日期,即2017-02-28 LocalDate.of(2017, 1, 31).plusMonths(1) LocalDate feb = LocalDate.of(2017, 2, 1); //withXxx()表示以该日期为基础,修改年、月、日字段,并返回一个新的日期 //2019-2-1 feb.withYear(2019); //2017-1-10 feb.withDayOfYear(10); //2017-2-10 feb.withDayOfMonth(10);

上面讲过Duration表示的是Instant对应的时间段,LocalDate对应的表示时间段的是Period, 
Period内部使用三个int值分表表示年、月、日。 
Duration和Period都是TemporalAmount接口的实现,该接口表示时间量。

LocalDate 也能够增长或减小一段时间:

//2019-02-01 feb.plus(Period.ofYears(2)); //2015-02-01 feb.minus(Period.ofYears(2);

使用until得到两个日期之间的Period对象

//输出P9D,表示相差9天 feb.until(LocalDate.of(2017, 2, 10));//输出---> P9D

 

LocalDate提供了一些测试方法: 
isBefore isAfter比较两个LocalDate,isLeapYear判断是不是闰年。

LocalDate还提供了各类getXxx方法来返回所须要的数据,其中getDayOfWeek()返回DayOfWeek枚举。 
DayOfWeek提供了plus minus来方便计算星期。

//SUNDAY
LocalDate.of(2017, 1, 1).getDayOfWeek(); //TUESDAY DayOfWeek.SUNDAY.plus(2);

除了LocalDate,Java8还提供了Year MonthDay YearMonth来表示部分日期,例如MonthDay能够表示1月1日。

日期校订器TemporalAdjuster

若是想找到某个月的第一个周五,或是某个月的最后一天,像这样的日期就可使用TemporalAdjuster来进行日期调整。 
TemporalAdjusters提供一些静态方法,返回经常使用的TemporalAdjuster

//2017-02-03的下一个星期五(包含当天) 2017-03-03 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)); //2017-02-03的下一个星期五(不包含当天) 2017-02-10 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); //2月中的第3个星期五 2017-02-17 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY)); //2月中的最后一个星期五 2017-02-24 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); //下个月的第一天 LocalDate.of(2017, 2, 3).with(TemporalAdjusters.firstDayOfNextMonth());

 

这是上面例子对应的当月日历

LocalTime(本地时间)

LocalTime表示一天中的某个时间,例如18:00:00。LocaTime与LocalDate相似,他们也有类似的API。

须要注意的是:LocalTime自己不关心是AM仍是PM,而是格式化程序来负责这个事情。

LocalDateTime(本地日期时间)

LocalDateTime表示一个日期和时间,它适合用来存储肯定时区的某个时间点。不适合跨时区的问题。

若须要处理跨时区的时间,须要使用ZonedDateTime.

ZonedDateTime(带时区的时间)

时区(Time Zone)是地球上的区域使用同一个时间定义。1884年在华盛顿召开国际经度会议时, 
为了克服时间上的混乱,规定将全球划分为24个时区。 
因为实用上经常1个国家,或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便, 
常将1个国家或1个省份划在一块儿。因此时区并不严格按南北直线来划分,而是按天然条件来划分。

Java使用ZoneId来标识不一样的时区.

//得到全部可用的时区  size=590 ZoneId.getAvailableZoneIds(); //获取默认ZoneId对象 ZoneId defZoneId = ZoneId.systemDefault(); //获取指定时区的ZoneId对象 ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai"); //ZoneId.SHORT_IDS返回一个Map<String, String> 是时区的简称与全称的映射。下面能够获得字符串 Asia/Shanghai String shanghai = ZoneId.SHORT_IDS.get("CTT");

我在测试的时候一共有590个时区可用,但要知道,这个时区的个数不是固定的。

IANA(Internet Assigned Numbers Authority,因特网拨号管理局)维护着一份全球全部已知的时区数据库, 
每一年会更新几回,主要处理夏令时规则的改变。Java使用了IANA的数据库。

建立ZonedDateTime

//2017-01-20T17:35:20.885+08:00[Asia/Shanghai] ZonedDateTime.now(); //2017-01-01T12:00+08:00[Asia/Shanghai] ZonedDateTime.of(2017, 1, 1, 12, 0, 0, 0, ZoneId.of("Asia/Shanghai")); //使用一个准确的时间点来建立ZonedDateTime,下面这个代码会获得当前的UTC时间,会比北京时间早8个小时 ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("UTC"));

LocalDateTime转换为ZonedDateTime

//atZone方法能够将LocalDateTime转换为ZonedDateTime,下面的方法将时区设置为UTC。 //假设如今的LocalDateTime是2017-01-20 17:55:00 转换后的时间为2017-01-20 17:55:00[UTC] LocalDateTime.now().atZone(ZoneId.of("UTC")); //使用静态of方法建立zonedDateTime ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("UTC"));

ZonedDateTime的一些方法

ZonedDateTime的许多方法与LocalDateTime、LocalDate、LocalTime相似,下面简单介绍几个方法的使用。

ZonedDateTime utcDateTime = ZonedDateTime.of(2017, 1, 1, 12, 0, 0, 0, ZoneId.of("UTC"));//2017-01-01T12:00Z[UTC] //withZoneSameLocal返回指定时区中的一个新ZonedDateTime,替换时区为指定时区,表示相同的本地时间的该时区时间。 utcDateTime.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));//2017-01-01T12:00+08:00[Asia/Shanghai] //withZoneSameInstant返回指定时区中的一个新ZonedDateTime,替换为指定时区,表示相同时间点的该时区时间。 utcDateTime.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));//2017-01-01T20:00+08:00[Asia/Shanghai] 

 

有一些国家和地区使用夏令时,处理起来须要注意,但在中国没有该问题, 
须要注意的是使用plus()时要用Period对象表示的时间量,而不该该用Duration表示的时间量, 
Duration不能处理夏令时。

utcDateTime.plus(Duration.ofDays(7));//不能处理夏令时 utcDateTime.plus(Period.ofDays(7));//正确方式 

 

格式化和解析 DateTimeFormatter

DateTimeFormatter是不可变类,而SimpleDateFormat是非线程安全的,是一个常见的坑。

格式化

DateTimeFormatter使用了三种格式化方法来打印日期和时间

  • 预约义的标准格式

DateTimeFormatter预约义了一些格式,能够直接调用format方法

//2017-01-01 DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.of(2017, 1, 1)) //20170101 DateTimeFormatter.BASIC_ISO_DATE.format(LocalDate.of(2017, 1, 1)); //2017-01-01T09:10:00 DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.of(2017, 1, 1, 9, 10, 0)); 

 

语言环境相关的格式化风格

根据当前操做系统语言环境,有SHORET MEDIUM LONG FULL 四种不一样的风格来格式化。 
能够经过DateTimeFormatter的静态方法ofLocalizedDate ofLocalizedTime ofLocalizedDateTime

//2017年1月1日 星期日 DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(LocalDate.of(2017, 1, 1)); //上午09时10分00秒 DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG).format(LocalTime.of(9, 10, 0)); //2017-2-27 22:32:03 DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).format(LocalDateTime.now());

 

上面的方法都使用的是默认的语言环境,若是想改语言环境,须要使用withLocale方法来改变。

//Feb 27, 2017 10:34:36 PM DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.US).format(LocalDateTime.now());

 使用自定义模式格式化

//2017-02-27 22:48:52 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now())

 

解析

//使用的ISO_LOCAL_DATE格式解析  2017-01-01 LocalDate.parse("2017-01-01"); //使用自定义格式解析 2017-01-01T08:08:08 LocalDateTime.parse("2017-01-01 08:08:08", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

遗留代码相互操做

Instant 相似于java.util.Date

ZonedDateTime相似于java.util.GregorianCalendar

//Date --> Instant Instant timestamp = new Date().toInstant(); //Instant --> Date Date.from(Instant.now()); //GregorianCalendar --> ZonedDateTime new GregorianCalendar().toZonedDateTime(); //ZonedDateTime --> GregorianCalendar GregorianCalendar.from(zonedDateTime); //2017-02-27T21:16:13.647 LocalDateTime.ofInstant(timestamp, ZoneId.of(ZoneId.SHORT_IDS.get("PST"))); //Calendar --> Instant //2017-02-28T05:16:13.656Z Calendar.getInstance().toInstant(); 
相关文章
相关标签/搜索