一、简介
-
全球一共分为24个时区,伦敦所在的时区称为标准时区,其他时区按东/西偏移的小时区分
-
北京所在的时区是东八区,
GMT+08:00
或者UTC+08:00
表示东八区。 -
GMT
和UTC
可以认为基本是等价的,只是UTC
使用更精确的原子钟计时,每隔几年会有一个闰秒,我们在开发程序的时候可以忽略两者的误差,因为计算机的时钟在联网的时候会自动与时间服务器同步时间。 -
Asia/Shanghai
,表示上海所在地的时区。特别注意城市名称不是任意的城市,而是由国际标准组织规定的城市。 -
夏令时:所谓夏令时,就是夏天开始的时候,把时间往后拨1小时,夏天结束的时候,再把时间往前拨1小时,时间换算更加复杂。我们国家实行过一段时间夏令时,1992年就废除了,但美国到现在还在使用。 计算夏令时请使用标准库提供的相关类,不要试图自己计算夏令时。
-
在计算机中,通常使用
Locale
表示一个国家或地区的日期、时间、数字、货币等格式。例如:(语言使用小写,国家使用大写)
zh_CN
表示中文+中国en_US
表示英文+美国。
-
要获取当前时间戳,可以使用
System.currentTimeMillis()
,这是Java程序获取时间戳最常用的方法。(计算机存储的当前时间,本质上只是一个不断递增的整数。) -
由于Java提供了新旧两套日期和时间的API,除非涉及到遗留代码,否则我们应该坚持使用新的API,新旧转换:博客地址
-
标准库API:
Java标准库有两套处理日期和时间的API,为什么会有新旧两套API呢?因为历史遗留原因,旧的API存在很多问题,所以引入了新的API。
- 旧:
java.util
包,主要包括Date
、Calendar
和TimeZone
这几个类; - 新:
java.time
包,Java 8引入,主要包括LocalDateTime
、ZonedDateTime
、ZoneId
等。
- 旧:
二、旧的Date体系
-
Date
java.util.Date
是用于表示一个日期和时间的对象,注意与java.sql.Date
区分,后者用在数据库中。如果观察Date的源码,可以发现它实际上存储了一个long类型的以毫秒表示的时间戳private transient long fastTime;
基本用法:
// 获取当前时间: Date date = new Date(); System.out.println(date.getYear() + 1900); // 必须加上1900 System.out.println(date.getMonth() + 1); // 0~11,必须加上1 System.out.println(date.getDate()); // 1~31,不能加1 // 转换为GMT时区: System.out.println(date.toGMTString()); // 转换为本地时区: System.out.println(date.toLocaleString());
-
SimpleDateFormat
对 Date 数据使用预定义的字符串进行格式转换。
- yyyy:年
- MM:月
- dd: 日
- HH: 小时
- mm: 分钟
- ss: 秒
注意:(具体见 JDK 文档详细格式说明)
- M:输出9
- MM:输出09
- MMM:输出Sep
- MMMM:输出September
// 获取当前时间: Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(date));
-
Date弊端
Date对象有几个严重的问题:它不能转换时区,除了toGMTString()可以按GMT+0:00输出外,Date总是以当前计算机系统的默认时区为基础进行输出。此外,我们也很难对日期和时间进行加减,计算两个日期相差多少天,计算某个月第一个星期一的日期等。
-
Calender
Calendar
可以用于获取并设置年、月、日、时、分、秒,它和Date
比,主要多了一个可以做简单的日期和时间运算的功能。注意到Calendar
获取年月日这些信息变成了get(int field)
,返回的年份不必转换,返回的月份仍然要加1,返回的星期要特别注意,1
~7
分别表示周日,周一,……,周六。 利用
Calendar.getTime()
可以将一个Calendar
对象转换成Date
对象,然后就可以用SimpleDateFormat
进行格式化了。 -
TimeZone:时区用
TimeZone
对象表示。
三、新的 LocalDateTime体系
1、LocalDateTime
-
与之前的区别:
- 和旧的API相比,新API严格区分了时刻、本地日期、本地时间和带时区的日期时间,并且,对日期和时间进行运算更加方便。
- 此外,新API修正了旧API不合理的常量设计:
- Month的范围用1~12表示1月到12月;
- Week的范围用1~7表示周一到周日。
- 最后,新API的类型几乎全部是不变类型(和String类似),可以放心使用不必担心被修改。
-
一些特性:
LocalDateTime
没有时区,无法确定某一时刻。ZonedDateTime
相当于LocalDateTime
加时区的组合,它具有时区,可以与long
表示的时间戳进行转换。
-
当前时间戳在
java.time
中以Instant
类型表示,我们用Instant.now()
获取当前时间戳,效果和System.currentTimeMillis()
类似。(原此,本质上是一样的) -
简单实现
public class Main { public static void main(String[] args) { LocalDate d = LocalDate.now(); // 当前日期 LocalTime t = LocalTime.now(); // 当前时间 LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间 System.out.println(d); // 严格按照ISO 8601格式打印 System.out.println(t); // 严格按照ISO 8601格式打印 System.out.println(dt); // 严格按照ISO 8601格式打印 }}
-
指定日期
of()
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17); LocalDateTime dt3 = LocalDateTime.of(d2, t2);
-
按照默认格式(ISO 8601)解析
parse()
- ISO 8601规定的日期和时间分隔符是T。
- 日期:yyyy-MM-dd
- 时间:HH:mm:ss
- 带毫秒的时间:HH:mm:ss.SSS
- 日期和时间:yyyy-MM-dd’T’HH:mm:ss
- 带毫秒的日期和时间:yyyy-MM-dd’T’HH:mm:ss.SSS
LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17"); LocalDate d = LocalDate.parse("2019-11-19"); LocalTime t = LocalTime.parse("15:16:17");
-
DateTimeFormatter:自定义格式,用于输入或者输出均可。
-
LocalDateTime日期增减
-
**注意:**月份加减会自动调整日期,例如从2019-10-31减去1个月得到的结果是2019-09-30,因为9月没有31日。
-
对日期和时间进行调整则使用
withXxx()
方法,例如:
withHour(15)
会把10:11:12
变为15:11:12
。- 调整年:withYear()
- 调整月:withMonth()
- 调整日:withDayOfMonth()
- 调整时:withHour()
- 调整分:withMinute()
- 调整秒:withSecond()
with()
:更复杂的运算。
LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59); // 加5天减3小时: LocalDateTime dt2 = dt.plusDays(5).minusHours(3); System.out.println(dt2); // 2019-10-31T17:30:59 // 减1月: LocalDateTime dt3 = dt2.minusMonths(1); System.out.println(dt3); // 2019-09-30T17:30:59 // with LocalDateTime dt2 = dt.withDayOfMonth(31); // 本月最后1天: LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()); // 本月第1个周一(对于计算某个月第1个周日这样的问题,新的API可以轻松完成。) LocalDate firstWeekday = LocalDate.now() .with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
-
-
要判断两个LocalDateTime的先后,可以使用
isBefore()
、isAfter()
方法,对于LocalDate和LocalTime类似:Boolean b=LocalDate.now().isBefore(LocalDate.of(2019, 11, 19));
-
Duration和Period
Duration
表示两个时刻之间的时间间隔。Period
表示两个日期之间的天数。
注意到两个LocalDateTime之间的差值使用Duration表示,类似PT1235H10M30S,表示1235小时10分钟30秒。而两个LocalDate之间的差值用Period表示,类似P1M21D,表示1个月21天。
-
Duration和Period的表示方法也符合ISO 8601的格式,它以P…T…的形式表示
- P…T之间表示日期间隔。
- T后面表示时间间隔。
-
利用
ofXxx()
或者parse()
方法也可以直接创建Duration
:(用作时间单位嘛,这我之前接触过!)
Duration d1 = Duration.ofHours(10); // 10 hours Duration d2 = Duration.parse("P1DT2H3M"); //1day,2hours,3 minutes
2、ZonedDateTime
-
ZonedDateTime:
- 可以简单地把
ZonedDateTime
理解成LocalDateTime
加**ZoneId
**。 - **
ZoneId
**是java.time
引入的新的时区类,注意和旧的java.util.TimeZone
区别。
// 默认时区 ZonedDateTime zbj = ZonedDateTime.now(); // 用指定时区获取当前时间,用到了 ZoneId。 ZonedDateTime z = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); // 另一种方式是通过给一个LocalDateTime附加一个ZoneId LocalDateTime ldt = LocalDateTime.of(2019, 9, 15, 15, 16, 17); ZonedDateTime zbj = ldt.atZone(ZoneId.of("Asia/Shanghai"));
-
时区转换
withZoneSameInstant()
:(直接翻译:与该地区相同时刻…)
// 以中国时区获取当前时间: ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); // 转换为东京时间: ZonedDateTime z = zbj.withZoneSameInstant(ZoneId.of("Asia/Tokyo")); // 转换为本地时间(十分简单) z.toLocalDateTime();
- 可以简单地把
-
要特别注意:
时区转换的时候,由于夏令时的存在,不同的日期转换的结果很可能是不同的。涉及到时区时,千万不要自己计算时差,否则难以正确处理夏令时。
3、DateTimeFormatter
-
旧SimpleDateFormat
与新DateTimeFormatter
对比:- 使用旧的Date对象时,我们用SimpleDateFormat进行格式化显示;使用新的LocalDateTime或ZonedDateTime时,我们要进行格式化显示,就要使用DateTimeFormatter。
- SimpleDateFormat线程不安全,使用时只能在方法内部创建新的局部变量;DateTimeFormatter不但是不变对象,而且线程安全,可以只创建一个实例,到处引用。
- DateTimeFormatter格式化字符串的使用方式与SimpleDateFormat完全一致。
-
简单使用
(注意使用方法,是谁包括了谁!)