一.前言
在介绍Java SE 8中新的日期时间库前,先了解下Java 8之前的日期时间工具的诟病。
在Java SE 8前,日期时间工具库在java.util包中,包括:
- java.util.Date:表示日期和时间
- java.util.Calendar以及其实现子类:表示各种日历系统,常用的是格林威治日历java.util.GregorianCalendar
- java.util.TimeZone以及其实现子类:表示时区偏移量和夏令时
以及辅助其进行格式化和解析的工具库在java.text包中,包括:
- java.text.DateFormat:格式化日期时间和解析日期时间的工具抽象类
- java.text.SimpleDateFormat:DateDateFormat的实现
从以上的简述中,对java 8之前的日期时间库,有所宏观视觉。下面简要总结下其设计上的瑕疵和被开发者无限吐槽的诟病:
- 从以上的api上看,java 8之前的日期时间工具库缺乏年、月、日、时间、星期的单独抽象;
- Dater日期时间类既描述日期又描述时间,耦合,且Date不仅在java.util包中存在,在java.sql中也存在,重复名称,容易导致bug发生;
- api的设计上晦涩,难用,不够生动,难以以自然人类的思维理解日期时间。年月日需要从Calendar中获取。q;
- 最被开发者抱怨的是类型不安全,Calendar类中全局属性是可变的,在多线程访问时,会存在线程安全问题。SimpleDateFormat格式化和解析日期,需要使用年月日时分秒,所以持有了Calendar属性,导致其也是非线程安全;
// 以下都是Calendar中持有的全局属性
// 这些全局属性都是可变的,提供了set
protected int fields[];
transient private int stamp[];
protected long time;
protected boolean isTimeSet;
// 在其子类GregorianCalendar中
private transient int[] zoneOffsets;
// setTime方法会调用此方法
// 该方法中修改了上述的很多全局属性
public void setTimeInMillis(long millis) {
// If we don't need to recalculate the calendar field values,
// do nothing.
if (time == millis && isTimeSet && areFieldsSet && areAllFieldsSet
&& (zone instanceof ZoneInfo) && !((ZoneInfo)zone).isDirty()) {
return;
}
time = millis;
isTimeSet = true;
areFieldsSet = false;
computeFields();
areAllFieldsSet = areFieldsSet = true;
}
所以在多线程环境中使用Calendar是非线程安全,多个线程修改其属性域会发生数据一致性和可见性问题。
在DateFormat中持有了Calendar属性,用于解析和格式化日期:
// 从注释上看,Calendar用于计算日期时间域
/**
* The {@link Calendar} instance used for calculating the date-time fields
* and the instant of time. This field is used for both formatting and
* parsing.
*
* <p>Subclasses should initialize this field to a {@link Calendar}
* appropriate for the {@link Locale} associated with this
* <code>DateFormat</code>.
* @serial
*/
protected Calendar calendar;
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
format方法中设置了全局成员Calendar的time,多线程访问时每次都会改变Calendar类,导致format格式化时会出现线程安全问题。所以DateFormat和其子类SimpleDateFormat都是非类型安全。
这个可以说是被开发者极度抱怨的。所以每次在使用日期格式工具时大多数都会重新new或者使用ThreadLocal。
基于此诸多问题,java设计者终于在Java SE 8中引入了新的日期时间库。新的日期时间库的易用程度会让你振服!下面开始进入主题,Java SE 8中的日期时间库java.time。
二.概览
先认识下joda项目,joda项目包括:
- Joda-Time - Basic types for Date and Time
- Joda-Money - Basic types for Money
- Joda-Beans - Next generation JavaBeans
- Joda-Convert - String to Object conversion
- Joda-Collect - Additional collection data structures
其中joda time是日期时间三方库,但是在java 8之前joda time其实是标准的日期时间库,其出色的语义表达,易用易于理解的api,类型安全的特性,大受开发者的追捧。而且其日历系统遵循的是IOS_8601国际标准,同时还包括其他的非标准的日历系统。支持时区、持续时间、格式化和解析功能。
在java 8之前可以依赖joda time三方库,使用其日期时间库。
但在java 8中提出了JSR 310: Date and Time API规范,该规范即新版的日期时间库java.time规约。可以说JSR-310的设计上汲取了大量的joda time的特性。新版本的日期时间库基于JSR 310: Date and Time API被开发,java.time是基于国际化标准日历系统(International Organization for Standardization)ISO_8601,同时java.time.chrono支持对全球日历系统的扩展。
JSR-310中设计的java.time包括年、月、星期、日期时间、持续时间段、瞬时、时钟、时区的抽象及处理。且api的设计上使用易读易于理解的名称和设计模式,让使用者欣然接受。而且提供旧版和新版api之间的互通以处理兼容性问题。
下面看张概览图,从宏观角度了解下java.time
- 第一层是对年、月、月中日、星期的抽象;
- 第二层是对日期、日期时间、时区的抽象,其中时区分为时区Id(Europe/Paris)和时区偏移量(Z/+hh:mm/-hh:mm);
- 第三层是对区域时间和便宜时间的抽象;
- 第四层是对瞬时和时钟的抽象;
- 第五层是对时序时段和持续周期的抽象
- 右侧层是辅助工具类,如:日期时间格式、日期时间调整器、其他的日历系统;
java 8中日期时间库共分为五个package:
- java.time:基于ISO_8601日历系统实现的日期时间库
- java.time.chrono:全球日历系统扩展库,可以自行扩展
- java.time.format:日期时间格式,包含大量预定义的格式,可以自行扩展
- java.time.zone:时区信息库
- java.time.temporal:日期时间调整辅助库
关于日期时间库的使用详细过程,推荐查看oracle提供的java教程The Java™ Tutorials——Trail: Date Time
也可以查看我的github中java 8新特性代码:lixyou/java
三.java.time优点
1.设计
java.time中使用了大量的设计模式
- 工厂模式:now()工厂方法直接生成当前日期时间或者瞬时;of()工厂方法根据年月日时分秒生成日期或者日期时间;
- 装饰模式:时区时间ZoneDateTime/便宜时间OffsetDateTime,都是在LocalDateTime的基础上加上时区/偏移量的修饰成为时区时间,然后可以进行时区转换;
- 建造者模式:Calendar中加入建造者类,用于生成新的Calendar对象;
2.命名
- java 8中的日期时间库类名、方法名命名上都是极其形象生动,易于理解,让开发者极易于使用——语义清晰精确!如:LocalDate中提供的now表示现在的日期,of用于年月日组成的日期(这里和英文中的of意义非常贴切),plus/minus加减等等;
3.合理的接口设计
- LocalDate表示日期,由年月日组成,提供了获取所在年,所在月,所在日的api,提供所在一年的第几天api,用于比较日期前后api,替换年份、月份、日的api,这些api使得日期或者日期时间的处理上得到的功能上的极大提升;
- 抽象出年、月、日、星期、日期、日期时间、瞬时、周期诸多接口,对事物本质有了细腻的抽象,并提供了相互转换的能力——提供极强的处理能力和语言表达能力;
- 对于遗留的日期时间库Calendar/Date/Timezone和新的日期时间库的互通性;
- 将全球的非标准日历系统单独抽象并支持扩展,从标准日历系统中隔离(符合设计原则:对修改关闭,对扩展开放)
四.补充
时区:时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。
世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。
理论时区以被15整除的子午线为中心,向东西两侧延伸7.5度,即每15°划分一个时区,这是理论时区。理论时区的时间采用其*经线(或标准经线)的地方时。所以每差一个时区,区时相差一个小时,相差多少个时区,就相差多少个小时。东边的时区时间比西边的时区时间早。为了避免日期的紊乱,提出国际日期变更线的概念
但是,为了避开国界线,有的时区的形状并不规则,而且比较大的国家以国家内部行政分界线为时区界线,这是实际时区,即法定时区。请参见时区列表。子午线:经线也称子午线,和纬线一样是人类为度量而假设出来的辅助线,定义为地球表面连接南北两极的大圆线上的半圆弧。任两根经线的长度相等,相交于南北两极点。每一根经线都有其相对应的数值,称为经度。经线指示南北方向。
本初子午线:即0度经线,亦称格林尼治子午线或本初经线,是经过英国格林尼治天文台的一条经线(亦称子午线)。本初子午线的东西两边分别定为东经和西经,于180度相遇。
国际标准ISO 8601:是国际标准化组织的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是2004年12月1日发行的第三版“ISO8601:2004”以替代1998年的第一版“ISO8601:1988”与2000年的第二版“ISO8601:2000”。
年由4位数字组成YYYY,或者带正负号的四或五位数字表示±YYYYY。以公历公元1年为0001年,以公元前1年为0000年,公元前2年为-0001年,其他以此类推。应用其他纪年法要换算成公历,但如果发送和接受信息的双方有共同一致同意的其他纪年法,可以自行应用。
月、日用两位数字表示:MM、DD。
只使用数字为基本格式。使用短横线"-"间隔开年、月、日为扩展格式。
ISO 8601:2004不再允许缺省(默认)世纪仅用两位数字表示年,这会与小时数的表示相混淆。而遵循ISO 8601:2000的GB/T 7408-2005,尚还存在这一问题。协调世界时英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC):是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。中华民国采用CNS 7648的《资料元及交换格式–资讯交换–日期及时间的表示法》(与ISO 8601类似)称之为世界协调时间。*采用ISO 8601:2000的国家标准GB/T 7408-2005《数据元和交换格式 信息交换 日期和时间表示法》中亦称之为协调世界时。
协调世界时是世界上调节时钟和时间的主要时间标准,它与0度经线的平太阳时相差不超过1秒[4],并不遵守夏令时。协调世界时是最接近格林威治标准时间(GMT)的几个替代时间系统之一。对于大多数用途来说,UTC时间被认为能与GMT时间互换,但GMT时间已不再被科学界所确定。
如果时间是以协调世界时(UTC)表示,则在时间后面直接加上一个“Z”(不加空格)。“Z”是协调世界时中0时区的标志。因此,“09:30 UTC”就写作“09:30Z”或是“0930Z”。“14:45:15 UTC”则为“14:45:15Z”或“144515Z”。UTC偏移量:UTC偏移量用以下形式表示:±[hh]:[mm]、±[hh][mm]、或者±[hh]。如果所在区时比协调世界时早1个小时(例如柏林冬季时间),那么时区标识应为“+01:00”、“+0100”或者直接写作“+01”。这也同上面的“Z”一样直接加在时间后面。
"UTC+8"表示当协调世界时(UTC)时间为凌晨2点的时候,当地的时间为2+8点,即早上10点。格林尼治平时(英语:Greenwich Mean Time,GMT):是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。
格林尼治平时的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代。
参考
Trail: Date Time
Class DateTimeFormatter
Java 8新特性终极指南
Is java.util.Calendar thread safe or not?Ask
协调世界时
时区
ISO 8601
经线
[时区列表](https://zh.wikipedia.org/wiki/ %E6%97%B6%E5%8C%BA%E5%88%97%E8%A1%A8#UTC%EF%BC%88WET_-%E6%AD%90%E6%B4%B2%E8%A5%BF%E9%83%A8%E6%99%82%E5%8D%80%EF%BC%8CGMT-_%E6%A0%BC%E6%9E%97%E5%A8%81%E6%B2%BB%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4%EF%BC%89)
Java 8——日期时间工具库(java.time)的更多相关文章
-
【转】JAVA 8 日期/时间(Date Time)API指南
前言 本来想写下Java 8的日期/时间API,发现已经有篇不错的文章了,那就直接转载吧~ PS:主要内容没变,做了部分修改. 原文链接: journaldev 翻译: ImportNew.com - ...
-
Java 8 日期时间 API
转自:https://www.runoob.com/java/java8-datetime-api.html Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与 ...
-
Java 8 新特性-菜鸟教程 (8) -Java 8 日期时间 API
Java 8 日期时间 API Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理. 在旧版的 Java 中,日期时间 API 存在诸多问题,其中有: ...
-
Java 8 日期时间API
Java 8一个新增的重要特性就是引入了新的时间和日期API,它们被包含在java.time包中.借助新的时间和日期API可以以更简洁的方法处理时间和日期; 在介绍本篇文章内容之前,我们先来讨论Jav ...
-
【Hutool】Hutool工具类之日期时间工具——DateUtil
一.用于取代Date对象的DateTime对象 再也不用Date SimpleDateFormat Calendar之间倒腾来倒腾去了!日期创建-获取-操作一步到位! 如果JDK版本更新到了8及以上, ...
-
【Hutool】工具类之日期时间工具-DateUtil
日期时间工具类 一.依赖 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-al ...
-
Apache Commons Lang之日期时间工具类
码农不识Apache,码尽一生也枉然. FastDateFormat FastDateFormat是一个快速且线程安全的时间操作类,它完全可以替代SimpleDateFromat.因为是线程安全的,所 ...
-
Java中日期时间API小结
Java中为处理日期和时间提供了大量的API,确实有把一件简单的事情搞复杂的嫌疑,各种类:Date Time Timestamp Calendar...,但是如果能够看到时间处理的本质就可以轻松hol ...
-
Java 8 日期时间API使用介绍
如何正确处理时间 现实生活的世界里,时间是不断向前的,如果向前追溯时间的起点,可能是宇宙出生时,又或是是宇宙出现之前, 但肯定是我们目前无法找到的,我们不知道现在距离时间原点的精确距离.所以我们要表示 ...
随机推荐
-
避免jsp传参返回乱码问题
$("#searchForm input").each(function(i){ var obj=$(this); var va=obj.val(); obj.val(decode ...
-
D3 学习资源
发现这个网站还是挺不错的:http://www.ourd3js.com/wordpress/
-
Linux TTY框架【转】
本文转载自:http://ju.outofmemory.cn/entry/281168 1. 前言 由于串口的缘故,TTY是Linux系统中最普遍的一类设备,稍微了解Linux系统的同学,对它都不陌生 ...
-
修改mysql的root密码
use msyql; update user set password=password('新密码') where user='root'; flush privileges; quit net st ...
-
【UVA10537】The Toll! Revisited (逆推最短路)
题目: Sample Input1a Z19 a Z5A DD XA bb cc X39 A X-1Sample OutputCase 1:20a-ZCase 2:44A-b-c-X 题意: 有两种节 ...
-
不能执行已释放script的代码
”从Dom中删除IFrame后,IE9+会回收内存.影响范围:适用于 Internet Explorer 9 以及更高版本.“ 1. 应用场景(相当隐蔽!!!) 在主页面定义一个全局变量,然后让子页面 ...
-
一个测ip和端口是否联通的工具类
public class TestIp { public static void main(String[] args) { Socket connect = new Socket(); try { ...
-
mongodb: Remote server has closed the connection
<?php function getMongoClient($seeds = "", $options = array(), $retry = 3) { try { retu ...
-
java面试题之分析(二)
QUESTION NO:2 package com.cdu.test; public class Test { static boolean foo(char c) { System.out.pri ...
-
关于mysql中unique的插入Duplicate key
MySQL数据库中 如果在后台中不做判断是否unique的column是否存在的话,直接把数据操作给dao层再传给DB的话,就会报重复的唯一值.如果确实是不希望先取出判断unique的column是否 ...