Java JDK8时间API
Table of Contents generated with DocToc
JDK8 时间API
Class Summary
- Clock
- Instant
- LocalDate
- LocalTime
- LocalDateTime
- OffsetTime
- OffsetDateTime
- Duration
- Period
- MonthDay
- YearMonth
- Year
- ZoneId
- ZoneOffset
- ZonedDateTime
时间戳 & 时区
时间戳,指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,时间戳没有时区概念,不同时区下,同一时间拥有相同时间戳。比如0时区在1970年1月1日00时00分01秒的时间戳为1000,而在东八区(1970年1月1日08时00分01秒)的时间戳也为1000。
时区,时区的划分能方便世界各地本地使用时间,全球总共分为24个时区,东1区~东11区,西1区~西11区,0区和12区。0区中心经度0,东西辐射7.5度。
Java 8 时间工具
无时区时间类
- LocalDate
- LocalTime
- LocalDateTime
public void testLocal() {
long timeStamp = System.currentTimeMillis();
// epochDays 从1970年1月1日开始,到timeStamp的天数
LocalDate localDate = LocalDate.ofEpochDay(Math.floorDiv(timeStamp, 24 * 60 * 60 * 1000));
// 转换成一天的时间(无日期信息)
LocalTime localTime = LocalTime.ofSecondOfDay(Math.floorDiv(timeStamp, 1000) % (24 * 60 * 60));
// 转换成时间
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
System.out.println("now: " + LocalDateTime.now());
System.out.println("timeStamp: " + timeStamp);
System.out.println("localDate: " + localDate.toString());
System.out.println("localTime: " + localTime.toString());
System.out.println("localDateTime: " + localDateTime.toString());
}
结果:
now: 2019-09-28T02:28:02.258
timeStamp: 1569608882253
localDate: 2019-09-27
localTime: 18:28:02
localDateTime: 2019-09-27T18:28:02
顾名思义,误以为local的含义默认为本地时间,实际上默认的local为0时区的时间,并不会帮你自动转换成本地时间。换一种理解方式,Local类的时间中,没有时区信息,从而无法表示成其他时区的时间。
而且,Local类的时间API很难用哦,有个时间戳,想转换成时间类型还的自己先算算。
时区时间类
- ZoneId
- ZoneOffset
- ZoneDateTime
时区ID对应关系
Short IDs | IDs |
---|---|
EST | -05:00 |
HST | -10:00 |
MST | -07:00 |
ACT | Australia/Darwin |
AET | Australia/Sydney |
AGT | America/Argentina/Buenos_Aires |
ART | Africa/Cairo |
AST | America/Anchorage |
BET | America/Sao_Paulo |
BST | Asia/Dhaka |
CAT | Africa/Harare |
CNT | America/St_Johns |
CST | America/Chicago |
CTT | Asia/Shanghai |
EAT | Africa/Addis_Ababa |
ECT | Europe/Paris |
IET | America/Indiana/Indianapolis |
IST | Asia/Kolkata |
JST | Asia/Tokyo |
MIT | Pacific/Apia |
NET | Asia/Yerevan |
NST | Pacific/Auckland |
PLT | Asia/Karachi |
PNT | America/Phoenix |
PRT | America/Puerto_Rico |
PST | America/Los_Angeles |
SST | Pacific/Guadalcanal |
VST | Asia/Ho_Chi_Minh |
public void testZoned() {
long timeStamp = System.currentTimeMillis();
LocalDate localDate = LocalDate.ofEpochDay(Math.floorDiv(timeStamp, 24 * 60 * 60 * 1000));
LocalTime localTime = LocalTime.ofSecondOfDay(Math.floorDiv(timeStamp, 1000) % (24 * 60 * 60));
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
// 系统默认时间ID
ZoneId zoneId = ZoneId.systemDefault();
// 根据和0时区相差的小时数获取时区偏移
ZoneOffset zoneOffset = ZoneOffset.ofHours(8);
ZonedDateTime zonedDateTimeOfId = ZonedDateTime.of(localDateTime, zoneId);
ZonedDateTime zonedDateTimeOfIdAndOffset = ZonedDateTime.ofInstant(localDateTime, zoneOffset, zoneId);
ZonedDateTime zonedDateTimeOfIdAndOffsetLocal = ZonedDateTime.ofLocal(localDateTime, zoneId, zoneOffset);
long beforeConversion = Math.floorDiv(timeStamp, 1000);
long afterConversion = zonedDateTimeOfIdAndOffset.toEpochSecond();
System.out.println("now: " + ZonedDateTime.now());
System.out.println("timeStamp: " + timeStamp);
System.out.println("zoneId: " + zoneId.toString());
System.out.println("zoneOffset: " + zoneOffset.toString());
System.out.println("zonedDateTimeOfId: " + zonedDateTimeOfId.toString());
System.out.println("zonedDateTimeOfIdAndOffset: " + zonedDateTimeOfIdAndOffset.toString());
System.out.println("zonedDateTimeOfIdAndOffsetLocal: " + zonedDateTimeOfIdAndOffsetLocal.toString());
System.out.println("beforeConversion: " + beforeConversion);
System.out.println("afterConversion: " + afterConversion);
System.out.println("conversionOffsetHours: " + ((afterConversion - beforeConversion)/3600));
}
now: 2019-09-28T02:26:47.884+08:00[Asia/Shanghai]
timeStamp: 1569608807874
zoneId: Asia/Shanghai
zoneOffset: +08:00
zonedDateTimeOfId: 2019-09-27T18:26:47+08:00[Asia/Shanghai]
zonedDateTimeOfIdAndOffset: 2019-09-27T18:26:47+08:00[Asia/Shanghai]
zonedDateTimeOfIdAndOffsetLocal: 2019-09-27T18:26:47+08:00[Asia/Shanghai]
beforeConversion: 1569608807
afterConversion: 1569580007
conversionOffsetHours: -8
由对比结果可以下结论:ZonedDateTime在获取时间的时候,并不会根据时间戳+时区偏移来得到时间,而是将其变成:时间戳时间 == 带时区的时间。从上面测试可以看出,当直接获取系统时间时,时间为正常的东8区时间。当用时间戳转换成ZonedDateTime时,时间“损失”了8小时,原因即ZonedDateTime在将时间转换成某时区时间的时候,并不会对时间进行便宜,而仅仅是打上了一个时区标签而已。(跟我没有测试之前的直觉完全相反)
或者说,人家不是以时间戳为基准的[摊手]…
所以,如果我在开发一个接口,入参是一个时间戳,而我要将这个时间戳转换成东八区的时间的时候,在Java8的时间API上我要进行非常多步乱七八糟的转换,才能用上它的ZonedDateTime,而且极易出错。(比如上面的测试,实际的时间提前了8小时)
Java8的时间API真的好用吗???为什么of静态方法不增加一个从时间戳转换而来的方法呢???
PS. Joda Time
- DateTime
public void testJoda() {
long timeStamp = System.currentTimeMillis();
DateTime dateTime = new DateTime(timeStamp);
DateTime offsetDateTime = dateTime.toDateTime(DateTimeZone.forOffsetHours(6));
DateTime utcDateTime = dateTime.toDateTime(DateTimeZone.UTC);
System.out.println("now: " + ZonedDateTime.now());
System.out.println("timeStamp: " + timeStamp);
System.out.println("dateTime: " + dateTime + "zone: " + dateTime.getZone());
System.out.println("offset dateTime: " + offsetDateTime + "offsetzone: " + offsetDateTime.getZone());
System.out.println("utc dateTime: " + utcDateTime + "utc zone: " + utcDateTime.getZone());
}
结果:
now: 2019-09-28T12:54:06.786+08:00[Asia/Shanghai]
timeStamp: 1569646446711
dateTime: 2019-09-28T12:54:06.711+08:00zone: Asia/Shanghai
offset dateTime: 2019-09-28T10:54:06.711+06:00offsetzone: +06:00
utc dateTime: 2019-09-28T04:54:06.711Zutc zone: UTC
Joda Time会直接很多。
时间段 & 时间点
- Duration
- Period
- Instant
- Clock
利用Clock生成ZonedDateTime:
Clock clock = Clock.systemDefaultZone();
Instant defaultZoneInstant = clock.instant();
ZonedDateTime atrDT = defaultZoneInstant.atZone(ZoneId.of("Asia/Tokyo"))
Instant为某一个UTC时间点,Instant利用Clock生成,Clock实际上也是利用System.currentTimeMillis生成,Instant可以转换成任意时区的时间。
总结
UTC时间类(不用考虑时区):
- Clock
- Instant
- LocalDate、LocalTime、LocalDateTime
以UTC时间作为接口时间字段时,可以直接使用Instant来初始化,这个类能使用atZone方法转换成本地时间。用Instant的好处是业务之间可以统一用UTC时间来处理,在需要本地时间的情况下再做时区转换。LocalDateTime虽然能作为UTC时间来使用,但是个人认为尽量少用,因为很容易让人困惑该时间属于哪个时区?
简单来讲,如果要用Java8的时间API,用Instant和ZonedDateTime。源码注释很有用。如果你懒得理解这些类之间的关系,那就用Joda Time(跑路…