早期的日期 API
在早期也就是 Java 8 之前,JDK 原生比较有名的有两个类:
-
Date 类
-
Calendar 类
这两个类相对来说用起来是比较困难的,之前我们往往是用的第三方的库。
新日期 API 的引入
在我们业界来说,这两个有两个很致命的问题:
-
Date 类 和 Calendar 类都是线程不安全的
除非我们的实例是留在方法体内的
-
Calendar 类设计不太友好
比如,查第几个月是从 0 开始计数
现在呢,Java 8 引入了一些新的 API,彻底简化了这些操作。
围绕这套新的 API 有许多类,我们着重介绍如下几个:
-
Instant & Duration
-
ZonedDateTime & LocalDateTime
以上两个,是处理时间的关键
-
DateTimeFormatter
把一个时间对象序列化或者反序列化成字符串
-
TemporalAdjusters
更像是一个 help 类,不是一个非常核心的类
所有涉及时间的操作逻辑,我们可以主要分成如下两点:
-
时区的计算
时区是一个相对的概念,比如中国的时间、美国的时间是有时差的
-
绝对时间
从过去的某一个过去的时间点到现在总共过了多少秒或毫秒,比如 1970年1月1日 0点到现在过去了多少
我们一般以绝对时间来作为参考
我们要了解新的 API 也是从这两点出发。
Instant 类和 Duration 类
Instant 类就代表了绝对时间的概念,它能告诉你无论是哪一个国家或者是哪一个城市,我们相比地球上过去历史上的某个时间点到现在为止过去了多少时间。
1 package 新日期API; 2 3 import java.time.Instant; 4 5 public class NowDate { 6 public static void main(String[] args) { 7 Instant now = Instant.now(); 8 System.out.println("现在过去了多少秒:" + now.getEpochSecond()); // 现在过去了多少秒:1613704995 9 } 10 }
我们可以用 Instant 的一些 get 方法可以得到绝对时间的坐标参考,比如,我们可以测量一个函数执行花费了多少时间
1 package 新日期API; 2 3 import java.time.Duration; 4 import java.time.Instant; 5 6 public class Instant { 7 public static void main(String[] args) throws Throwable { 8 Instant start = Instant.now(); 9 // 执行一些逻辑 10 Thread.sleep(2000); 11 Instant end = Instant.now(); 12 13 // 和 Instant 挂钩的还有一个 Duration 14 // 计算这个时间持续了多久 15 Duration duration = Duration.between(start, end); 16 System.out.println(duration.getSeconds()); 17 } 18 } 19 20 /** 21 输出: 22 2 23 */
ZonedDateTime 类和 LocalDateTime 类
Instant 类代表了时间轴上的绝对时间,但是它并不是一个友好的日历时间,只能是说当前这个时间点离格林威治时间(1970年1月1日 0点)过去了多少,但是它并不能代表着今天是星期几或者几几年的几月几日。
那么我们怎么知道一个日历时间呢?
这就要涉及到 ZonedDateTime 和 LocalDateTime 这两个类,这两个时间可以理解为相对时间。
ZonedDateTime 是带时区的类,LocalDateTime 是不带时区的类。
1. ZonedDateTime 类
1 package 新日期API; 2 3 import java.time.Instant; 4 import java.time.ZoneId; 5 import java.time.ZonedDateTime; 6 7 public class ZonedDateTime { 8 public static void main(String[] args) { 9 /** 10 * 将一个绝对的时间转换成一个与地区相关的时间 11 */ 12 Instant start = Instant.now(); 13 14 // 方法一: 15 // start.atZone(ZoneId.of(ZoneId)) 16 ZonedDateTime zoneTime01 = start.atZone(ZoneId.of("UTC")); // Asia/Shanghai 传递即得上海的时间 17 System.out.println("atZone转成的ZonedDateTime类型的UTC时间:" + zoneTime01); 18 19 // 方法二: 20 // ZonedDateTime.ofInstant(start, ZoneId.of(ZoneId)) 21 // ZoneId 22 // 地区id 23 ZonedDateTime dtUTC = ZonedDateTime.ofInstant(start, ZoneId.of("UTC")); 24 System.out.println("ZonedDateTime转成的UTC时间:" + dtUTC); 25 26 // ZoneId.of() 也不一定显示的传递一个地区字符串或者 UTC 等 27 ZonedDateTime dtSH = start.atZone(ZoneId.systemDefault()); 28 System.out.println("systemDefault:" + dtSH); 29 } 30 } 31 32 /** 33 输出: 34 atZone转成的ZonedDateTime类型的UTC时间:2021-02-19T03:58:22.270337800Z[UTC] 35 ZonedDateTime转成的UTC时间:2021-02-19T03:58:22.270337800Z[UTC] 36 systemDefault:2021-02-19T11:58:22.270337800+08:00[Asia/Shanghai] 37 */
2. LocalDateTime 类
和 ZonedDateTime 类的 API 几乎是一模一样,唯一的区别就是 LocalDateTime 类是一个不带时区的。
也就是说,使用 LocalDateTime 这个类 ,你可以知道某年某月某日或某个时间,但是你不知道是属于哪个国家的,一般来说,不建议使用 LocalDateTime 。
3. 获取 ZoneId 列表
可以使用 ZoneRulesProvider.getAvailableZoneIds()
获取 ZoneId 列表
1 package 新日期API; 2 3 import java.time.zone.ZoneRulesProvider; 4 5 public class ZoneId { 6 public static void main(String[] args) { 7 System.out.println(ZoneRulesProvider.getAvailableZoneIds()); 8 } 9 }
DateTimeFormatter 类
用于把一个时间对象序列化或者反序列化成字符串
序列化成字符串
1 package 新日期API; 2 3 import java.time.Instant; 4 import java.time.ZoneId; 5 import java.time.ZonedDateTime; 6 import java.time.format.DateTimeFormatter; 7 8 public class DateTimeFormatter { 9 public static void main(String[] args) { 10 Instant start = Instant.now(); 11 ZonedDateTime dtSH = ZonedDateTime.ofInstant(start, ZoneId.of("Asia/Shanghai")); 12 13 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 14 System.out.println(df.format(dtSH)); 15 } 16 } 17 18 /** 19 输出: 20 2021-02-19 13:48:52 21 */
格式可以随意设置,再例如:
1 package 新日期API; 2 3 import java.time.Instant; 4 import java.time.ZoneId; 5 import java.time.ZonedDateTime; 6 import java.time.format.DateTimeFormatter; 7 8 public class DateTimeFormatter { 9 public static void main(String[] args) { 10 Instant start = Instant.now(); 11 ZonedDateTime dtSH = ZonedDateTime.ofInstant(start, ZoneId.of("Asia/Shanghai")); 12 13 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); 14 System.out.println(df.format(dtSH)); 15 } 16 } 17 18 /** 19 输出: 20 2021/02/19 13:51:28 21 */
也可以加上时区的偏移量
1 package 新日期API; 2 3 import java.time.Instant; 4 import java.time.ZoneId; 5 import java.time.ZonedDateTime; 6 import java.time.format.DateTimeFormatter; 7 8 public class DateTimeFormatter5 { 9 public static void main(String[] args) { 10 Instant start = Instant.now(); 11 ZonedDateTime dtSH = ZonedDateTime.ofInstant(start, ZoneId.of("Asia/Shanghai")); 12 13 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxx"); 14 String dateStr = df.format(dtSH); 15 System.out.println(dateStr); 16 } 17 } 18 19 /** 20 输出: 21 2021-02-19 13:52:25+0800 22 */
注意:
yyyy-MM-dd HH:mm:ss 年-月-日 时:分:秒
M 大写是为了区分 "月" 和 "分"
H 大写是为了区分 12小时制 和 24小时制
小写的 h 是12小时制
大写的 H 是24小时制
有时候我们会看到这样的格式:
yyyy-M-d H:m:s
mm 与 m 等,它们的区别为 是否有前导零
H,m,s表示 非零开始 ,HH,mm,ss表示 从零开始
比如凌晨1点2分,HH:mm显示为 01:02 ,H:m显示为 1:2 。
反序列化
我们可以使用 ZonedDateTime.parse()
将一个字符串转回一个反序列化的 ZonedDateTime
1 ZonedDateTime dt = ZonedDateTime.parse(dateStr, df); 2 System.out.println(dt); 3 4 /** 5 输出: 6 2021-02-19T14:06:35+08:00 7 */
TemporalAdjusters
这个类更像是一个 help 的方法,用来调整时间,比如说,我想设置一些时间,但是可能因为某些业务逻辑,想单纯的设置某些量有点困难,就可以用这个类。
可以做一些时间业务上给我们封装好的事情
例如,我们想获取下周五的日期时间
1 package 新日期API; 2 3 import java.time.DayOfWeek; 4 import java.time.Instant; 5 import java.time.ZoneId; 6 import java.time.ZonedDateTime; 7 import java.time.temporal.TemporalAdjusters; 8 9 public class TemporalAdjusters { 10 public static void main(String[] args) { 11 Instant now = Instant.now(); 12 ZonedDateTime dtSH = now.atZone(ZoneId.of("Asia/Shanghai")); 13 ZonedDateTime nextFriday = dtSH.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); 14 System.out.println(nextFriday); 15 } 16 } 17 18 /** 19 输出: 20 2021-02-26T14:16:04.088263300+08:00[Asia/Shanghai] 21 */