时区
地球自西向东旋转,东边比西边先看到太阳,东边的时间也比西边的早。为了统一世界的时间,1884年的国际经度会议规规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为零时区(GMT+00),东1-12区,西1-12区,中国北京处于东8区(GMT+08)。
若英国时间为6点整,则GMT时间为6点整,则北京时间为14点整。
时区ID
ZoneId 为时区ID,比如Europe/Paris,表示欧洲巴黎时区
GMT和UTC
GMT,即格林尼治标准时间,也就是世界时。GMT的正午是指当太阳横穿格林尼治子午线(本初子午线)时的时间。但由于地球自转不均匀不规则,导致GMT不精确,现在已经不再作为世界标准时间使用。

UTC,即协调世界时。UTC是以原子时秒长为基础,在时刻上尽量接近于GMT的一种时间计量系统。为确保UTC与GMT相差不会超过0.9秒,在有需要的情况下会在UTC内加上正或负闰秒。UTC现在作为世界标准时间使用。

所以,UTC与GMT基本上等同,误差不超过0.9秒。

UNIX时间戳
计算机中的UNIX时间戳,是以GMT/UTC时间「1970-01-01T00:00:00」为起点,到具体时间的秒数,不考虑闰秒。这么做当然是为了简化计算机对时间操作的复杂度。

比如我的电脑现在的系统时间为2015年2月27日15点43分0秒,因为我的电脑默认时区为东8区,则0时区的时间为2015年2月27日7点43分0秒,则UNIX时间戳为1425022980秒。
所以要有时区或者偏移量信息的时间,才能和UTC时间比较,计算出时间差,才能算出时间戳。

偏移量 VS 时区 (ZoneOffset VS ZoneId)

偏移量(与UTC的偏移量) offset-from-UTC 仅仅只记录了时分秒而已,除此之外没有任何其他信息。举个例子 ,+08:00的意思时超前于UTC八个小时,而 -05:45 意思是落后于UTC五小时四十五分钟。

而时区对于特定地区的人来说是过去,现在,未来的偏移量的历史集合。像夏令时 这样的异常会导致特定时间段内的偏移量会随时间变化,无论是过去已经发生的还是 未来政客们宣布计划的改变。

时区是偏移的针对特定区域中的历史加上一组规则来处理异常,如日光节约时间(DST) ,导致在时间的偏移量超过特定时间段的变化。

时区=(偏移历史+异常规则)

因此在知道时更好地使用区域。

很多地区的偏移量都会随时间变化。比如,美国的夏令时 会导致约半年左右的时间内都会再原来的基础上偏移一个小时,然后再下半年再调整回这一个小时的偏移量。时区的意义就是记录这些所有的会造成偏移的情况。

因此,没有结合日期计算的偏移量没有意义的。举个例子,法国时间 (Europe/Paris),在一年的一小部分时间内偏移量为+01:00,而由于夏令时的缘故,大部分时间(三月末到十月末)的偏移量都是+02:00。

// 指定区域,构造时间: 法国巴黎时间的当前时间,五月有DST,偏移量+2

LocalDateTime now = LocalDateTime.of(LocalDate.of(2020, 5, 31), LocalTime.now())
ZonedDateTime zonedDateTime = ZonedDateTime.of(now, ZoneId.of("Europe/Paris"));
zonedDateTime.getOffset(); // +02:00

// 手动调整的二月份,此时没有DST,偏移量是+1

ZonedDateTime thirdMonth = zonedDateTime.minusMonths(3);
thirdMonth.getOffset(); // +01:00

获得JVM当前默认时区ID:

ZoneId.systemDefault()

获得JVM当前默认Offset:

 ZoneOffset offset = ZonedDateTime.now().getOffset();
 ZoneOffset offset = OffsetDateTime.now().getOffset();

●Instant 它代表的是时间戳,比如2016-04-14T14:20:13.592Z,Instant是无时区的,这可以从java.time.Clock类中获取,像这样:

Instant current = Clock.system(ZoneId.of("Asia/Tokyo")).instant();

Clock:时钟,比如获取目前美国纽约的时间
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。

Clock clock = Clock.systemDefaultZone();

long millis = clock.millis();

Instant instant = clock.instant();

●LocalDate 它表示的是不带时间的日期,比如:2021-04-14。

●LocalTime - 它表示的是不带日期的时间,比如:19:23:01.438。

●LocalDateTime - 它包含了时间与日期,不过没有带时区的偏移量,比如:2021-04-27T19:30:42.749。

●ZonedDateTime - 这是一个带时区的完整时间,它根据UTC/格林威治时间来进行时区调整
,比如:2021-04-27T19:31:43.090+08:00[Asia/Shanghai]
由于ZonedDateTime是包含时区信息的,所以要构造或者转换成ZonedDateTime必须指定ZoneId,
比如LocalDateTime,示例如下:

LocalDateTime localDateTime = LocalDateTime.now();
//LocalDateTime转ZonedDateTime需要指定ZoneId
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
//ZonedDateTime转LocalDateTime,只需要去掉ZonedDateTime中包含的ZoneId即可
LocalDateTime localDateTime2 = zonedDateTime.toLocalDateTime();

Instant也是没有时区信息的,所以从Instant构造ZonedDateTime也要指定ZoneId,示例如下:

ZonedDateTime.ofInstant(Instant.now(),ZoneId.systemDefault())

Clock是有时区信息的,所以从Clock构造ZonedDateTime无需指定ZoneId,示例如下:

ZonedDateTime.now(Clock.systemDefaultZone());

ZonedDateTime 示例:2021-04-27T19:31:43.090+08:00[Asia/Shanghai],不仅包含了LocalDateTime和ZoneId还包含了ZoneOffset.但是上面在构造ZonedDateTime的时候并没有指定ZoneOffset,为什么呢?

在ZoneDateTime中也是可以传入ZoneOffset的,示例如下:

ZoneOffset offset = OffsetDateTime.now().getOffset();//我本地是上海偏移量是(+8)
ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(localDateTime, zoneOffset, ZoneId.systemDefault());//2021-04-27T20:21:35.300+08:00[Asia/Shanghai]

可不可以传别的值呢,比如+9,肯定是可以的,示例如下:

 ZoneOffset zoneOffset3 = ZoneOffset.of("+09:00");
 ZonedDateTime zonedDateTime3 = ZonedDateTime.ofInstant(localDateTime, zoneOffset3, ZoneId.systemDefault());//2021-04-27T19:21:35.300+08:00[Asia/Shanghai]

可以看到和上面的相比,时间不是20点而是19点。如果是想把LocalDateTime转成ZoneDateTime这是不对的,所以最好还是不要调用这个方法,要调用不带Offset参数的,这个方法什么场景会调用,还不太清楚,如果有大神指教,不胜感激。
所以上面的答案是:ZoneID决定偏移量的变化方式和时间。不能自由设置偏移量,因为区域控制哪些偏移量是有效的。

●OffsetDateTime类实际上包含了LocalDateTime与ZoneOffset。它用来表示一个包含格林威治时间偏移量(+/-小时:分,比如+06:00或者 -08:00)的完整的日期(年月日)及时间(时分秒,纳秒),但不包含时区 ID。比如:2021-04-29T15:37:11.331+08:00
所以没有时区或Offset信息的日期转成OffsetDateTime,需要指定ZoneId或者Offset

//Instantto OffsetDateTime
OffsetDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
//LocalDateTime to OffsetDateTime
LocalDateTime.now().atOffset(OffsetDateTime.now().getOffset())
或者
OffsetDateTime.of(LocalDateTime.now(),OffsetDateTime.now().getOffset())

ZonedDateTime和OffsetDateTime之间的区别

ZonedDateTime:

● 存储所有日期和时间字段,精度为纳秒,时区,区域偏移用于处理模糊的本地日期时间

● 无法自由设置偏移,因为区域控制有效的偏移值

● 完全支持DST并处理夏令时调整

● 在用户特定的时区显示日期时间字段非常方便

OffsetDateTime:

● 存储所有日期和时间字段,精度为纳秒,以及与GMT / UTC的偏移量(无时区信息)

● 应该用于在数据库中存储日期或通过网络进行通信

Clock是有时区信息的,所以从Clock构造OffsetDateTime无需指定ZoneId,示例如下:

OffsetDateTime.now(Clock.systemDefaultZone());

构造OffsetDateTime还可以调用它的from(TemporalAccessor temporal)方法,但是注意一定是要包含时区或者Offset信息的实现类,否则运行时报异常。提示信息类似:“Unable to obtain ZoneOffset from TemporalAccessor”

OffsetDateTime.from(OffsetDateTime.now());//正确
OffsetDateTime.from(ZonedDateTime.now());//正确
OffsetDateTime.from(LocalDateTime.now());//运行报错
OffsetDateTime.from(LocalTime.now());//运行报错
OffsetDateTime.from(LocalDate.now());//运行报错

Duration:计算两个“时间”的间隔
Period:用于计算两个“日期”的间隔
ZoneOffset:时区偏移量,比如:+8:00
ZonedDateTime:可以得到特定时区的日期/时间

总之,1、时区和Offset可以转化,所以有时候可以替代,但是尽量使用时区ZoneId.
2、无时区时间转化为有时区时间,需要指定ZoneId.反过来不必。

Temporal 接口、TemporalAdjuster 接口与TemporalAdjusters工具类
Temporal可以根据实际需求表示为日期、时间或两者组合。其几个实现类包括:
LocalDate – 表示没有时区的日期
LocalDateTime – 表示没有时区的日期和时间

TemporalAdjuster用于时间较正,例如,可以获得当月的最后一天、下一年的第一天。一般调用时间日期的with方法,例如:

localDate.with(TemporalAdjusters.lastDayOfMonth());

TemporalAdjusters工具类有很多预定义的TemporalAdjuster实现。
例如:

dayOfWeekInMonth() – 一周中的某一天,例如,三月中第二个星期二
firstDayOfMonth() – 当前月的第一天
firstDayOfNextMonth() – 下一个月的第一天
firstDayOfNextYear() – 下一年的第一天
firstDayOfYear() – 当年的第一天
lastDayOfMonth() – 当月的最后一天
nextOrSame() – 下一次或当天发生的一周中的某天

自定义TemporalAdjuster 实现,比如获得之后14天的日期。

 LocalDate localDate = LocalDate.of(2021, 04, 30);
 TemporalAdjuster temporalAdjuster = t -> t.plus(Period.ofDays(14));
 LocalDate result = localDate.with(temporalAdjuster);//输出2021-05-14

时间段 Period与Duration

 LocalDate birthDay = LocalDate.of(2021, 02, 15);
 LocalDate localDate = LocalDate.now();
 //两个日期差
 Period period = Period.between(birthDay, localDate);
 //从Period 获得月份或者天
 period.getMonths(), period.getDays());
 //加3天
 birthDay.plus(Period.ofDays(3))//2021-02-18
 // 计算时间差值
 Instant instant = Instant.now();
 Instant instant1 = instant.plus(Duration.ofMinutes(2));
 Duration duration = Duration.between(instant, instant1);

高旭
40 声望3 粉丝