【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

YourBatman发布于 今天 04:43

你好,我是A哥(YourBatman)。

在JSR 310日期时间体系了,一共有三个API可用于表示日期时间:

  • LocalDateTime:本地日期时间
  • OffsetDateTime:带偏移量的日期时间
  • ZonedDateTime:带时区的日期时间

也许平时开发中你只用到过LocalDateTime这个API,那是极好的,但是不能止步于此,否则就图样图森破了。

随着场景的多样性变化,咱们开发者接触到OffsetDateTime/ZonedDateTime的概率越来越大,但凡和国际化产生上关系的大概率都会用得到它们。本文依然站在实用的角度,辅以具体代码示例,介绍它三。

本文提纲

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

版本约定

  • JDK:8

正文

下面这张图是一个完整的日期时间,拆解各个部分的含义,一目了然(建议收藏此图):

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

因为LocalDate、LocalTime等理解起来比较简单,就不用再花笔墨介绍了,重点放在LocalDateTime、OffsetDateTime、ZonedDateTime它三身上。

什么是LocalDateTime?

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

ISO-8601日历系统中不带时区的日期时间。

LocalDateTime是一个不可变的日期-时间对象,它表示一个日期时间,通常被视为年-月-日-小时-分钟-秒。还可以访问其他日期和时间字段,如day-of-year、day-of-week和week-of-year等等,它的精度能达纳秒级别。

该类不存储时区,所以适合日期的描述,比如用于生日、deadline等等。但是请记住,如果没有偏移量/时区等附加信息,一个时间是不能表示时间线上的某一时刻的。

代码示例

最大/最小值:

@Test

public void test1() {

LocalDateTime min = LocalDateTime.MIN;

LocalDateTime max = LocalDateTime.MAX;

System.out.println("LocalDateTime最小值:" + min);

System.out.println("LocalDateTime最大值:" + max);

System.out.println(min.getYear() + "-" + min.getMonthValue() + "-" + min.getDayOfMonth());

System.out.println(max.getYear() + "-" + max.getMonthValue() + "-" + max.getDayOfMonth());

}

输出:

LocalDateTime最小值:-999999999-01-01T00:00

LocalDateTime最大值:+999999999-12-31T23:59:59.999999999

-999999999-1-1

999999999-12-31

构造:

@Test

public void test2() {

System.out.println("当前时区的本地时间:" + LocalDateTime.now());

System.out.println("当前时区的本地时间:" + LocalDateTime.of(LocalDate.now(), LocalTime.now()));

System.out.println("纽约时区的本地时间:" + LocalDateTime.now(ZoneId.of("America/New_York")));

}

输出:

当前时区的本地时间:2021-01-17T17:00:41.446

当前时区的本地时间:2021-01-17T17:00:41.447

纽约时区的本地时间:2021-01-17T04:00:41.450

注意,最后一个构造传入了ZoneId,并不是说LocalDateTime和时区有关了,而是告诉说这个Local指的是纽约,细品这句话。

计算:

@Test

public void test3() {

LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());

System.out.println("计算前:" + now);

// 加3天

LocalDateTime after = now.plusDays(3);

// 减4个小时

after = after.plusHours(-3); // 效果同now.minusDays(3);

System.out.println("计算后:" + after);

// 计算时间差

Period period = Period.between(now.toLocalDate(), after.toLocalDate());

System.out.println("相差天数:" + period.getDays());

Duration duration = Duration.between(now.toLocalTime(), after.toLocalTime());

System.out.println("相差小时数:" + duration.toHours());

}

输出:

计算前:2021-01-17T17:10:15.381

计算后:2021-01-20T14:10:15.381

相差天数:3

相差小时数:-3

格式化:

@Test

public void test4() {

LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());

// System.out.println("格式化输出:" + DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(now));

System.out.println("格式化输出(本地化输出,中文环境):" + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(now));

String dateTimeStrParam = "2021-01-17 18:00:00";

System.out.println("解析后输出:" + LocalDateTime.parse(dateTimeStrParam, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.US)));

}

输出:

格式化输出(本地化输出,中文环境):21-1-17 下午5:15

解析后输出:2021-01-17T18:00

什么是OffsetDateTime?

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

ISO-8601日历系统中与UTC偏移量有关的日期时间。OffsetDateTime是一个带有偏移量的日期时间类型。存储有精确到纳秒的日期时间,以及偏移量。可以简单理解为 OffsetDateTime = LocalDateTime + ZoneOffset。

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

OffsetDateTime、ZonedDateTime和Instant它们三都能在时间线上以纳秒精度存储一个瞬间(请注意:LocalDateTime是不行的),也可理解我某个时刻。OffsetDateTime和Instant可用于模型的字段类型,因为它们都表示瞬间值并且还不可变,所以适合网络传输或者数据库持久化。

代码示例

最大/最小值:

@Test

public void test5() {

OffsetDateTime min = OffsetDateTime.MIN;

OffsetDateTime max = OffsetDateTime.MAX;

System.out.println("OffsetDateTime最小值:" + min);

System.out.println("OffsetDateTime最大值:" + max);

System.out.println(min.getOffset() + ":" + min.getYear() + "-" + min.getMonthValue() + "-" + min.getDayOfMonth());

System.out.println(max.getOffset() + ":" + max.getYear() + "-" + max.getMonthValue() + "-" + max.getDayOfMonth());

}

输出:

OffsetDateTime最小值:-999999999-01-01T00:00+18:00

OffsetDateTime最大值:+999999999-12-31T23:59:59.999999999-18:00

+18:00:-999999999-1-1

-18:00:999999999-12-31

偏移量的最大值是+18,最小值是-18,这是由ZoneOffset内部的限制决定的。

构造:

@Test

public void test6() {

System.out.println("当前位置偏移量的本地时间:" + OffsetDateTime.now());

System.out.println("偏移量-4(纽约)的本地时间::" + OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-4")));

System.out.println("纽约时区的本地时间:" + OffsetDateTime.now(ZoneId.of("America/New_York")));

}

输出:

当前位置偏移量的本地时间:2021-01-17T19:02:06.328+08:00

偏移量-4(纽约)的本地时间::2021-01-17T19:02:06.329-04:00

纽约时区的本地时间:2021-01-17T06:02:06.330-05:00

计算:

格式化:

@Test

public void test7() {

OffsetDateTime now = OffsetDateTime.now(ZoneId.systemDefault());

System.out.println("格式化输出(本地化输出,中文环境):" + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(now));

String dateTimeStrParam = "2021-01-17T18:00:00+07:00";

System.out.println("解析后输出:" + OffsetDateTime.parse(dateTimeStrParam));

}

输出:

格式化输出(本地化输出,中文环境):21-1-17 下午7:06

解析后输出:2021-01-17T18:00+07:00

转换:
LocalDateTime -> OffsetDateTime

@Test

public void test8() {

LocalDateTime localDateTime = LocalDateTime.of(2021, 01, 17, 18, 00, 00);

System.out.println("当前时区(北京)时间为:" + localDateTime);

// 转换为偏移量为 -4的OffsetDateTime时间

// 1、-4地方的晚上18点

System.out.println("-4偏移量地方的晚上18点:" + OffsetDateTime.of(localDateTime, ZoneOffset.ofHours(-4)));

System.out.println("-4偏移量地方的晚上18点(方式二):" + localDateTime.atOffset(ZoneOffset.ofHours(-4)));

// 2、北京时间晚上18:00 对应的-4地方的时间点

System.out.println("当前地区对应的-4地方的时间:" + OffsetDateTime.ofInstant(localDateTime.toInstant(ZoneOffset.ofHours(8)), ZoneOffset.ofHours(-4)));

}

输出:

当前时区(北京)时间为:2021-01-17T18:00

-4偏移量地方的晚上18点:2021-01-17T18:00-04:00

-4偏移量地方的晚上18点(方式二):2021-01-17T18:00-04:00

当前地区对应的-4地方的时间:2021-01-17T06:00-04:00

通过此例值得注意的是:LocalDateTime#atOffset()/atZone()只是增加了偏移量/时区,本地时间是并没有改变的。若想实现本地时间到其它偏移量的对应的时间只能通过其ofInstant()系列构造方法。

OffsetDateTime -> LocalDateTime

@Test

public void test81() {

OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-4));

System.out.println("-4偏移量时间为:" + offsetDateTime);

// 转为LocalDateTime 注意:时间还是未变的哦

System.out.println("LocalDateTime的表示形式:" + offsetDateTime.toLocalDateTime());

}

输出:

-4偏移量时间为:2021-01-17T19:33:28.139-04:00

LocalDateTime的表示形式:2021-01-17T19:33:28.139

什么是ZonedDateTime?

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

ISO-8601国际标准日历系统中带有时区的日期时间。它存储所有的日期和时间字段,精度为纳秒,以及一个时区,带有用于处理不明确的本地日期时间的时区偏移量。

这个API可以处理从LocalDateTime -> Instant -> ZonedDateTime的转换,其中用zone时区来表示偏移量(并非直接用offset哦)。两个时间点之间的转换会涉及到使用从ZoneId访问的规则计算偏移量(换句话说:偏移量并非写死而是根据规则计算出来的)。

获取瞬间的偏移量很简单,因为每个瞬间只有一个有效的偏移量。但是,获取本地日期时间的偏移量并不简单。存在这三种情况:

  • 正常情况:有一个有效的偏移量。对于一年中的绝大多数时间,适用正常情况,即本地日期时间只有一个有效的偏移量
  • 时间间隙情况:没有有效偏移量。这是由于夏令时开始时从“冬季”改为“夏季”而导致时钟向前拨的时候。在间隙中,没有有效偏移量
  • 重叠情况:有两个有效偏移量。这是由于秋季夏令时从“夏季”到“冬季”的变化,时钟会向后拨。在重叠部分中,有两个有效偏移量

这三种情况如果要自己处理,估计头都大了。这就是使用JSR 310的优势,ZonedDateTime全帮你搞定,让你使用无忧。

ZonedDateTime可简单认为是LocalDateTime和ZoneId的组合。而ZoneOffset是其内置的动态计算出来的一个次要信息,以确保输出一个瞬时值而存在,毕竟在某个瞬间偏移量ZoneOffset肯定是确定的。ZonedDateTime也可以理解为保存的状态相当于三个独立的对象:LocalDateTime、ZoneId和ZoneOffset。某个瞬间 = LocalDateTime + ZoneOffset。ZoneId确定了偏移量如何改变的规则。所以偏移量我们并不能自由设置(不提供set方法,构造时也不行),因为它由ZoneId来控制的。

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

代码示例

构造:

@Test

public void test9() {

System.out.println("当前位置偏移量的本地时间:" + ZonedDateTime.now());

System.out.println("纽约时区的本地时间:" + ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("America/New_York")));

System.out.println("北京实现对应的纽约时区的本地时间:" + ZonedDateTime.now(ZoneId.of("America/New_York")));

}

输出:

当前位置偏移量的本地时间:2021-01-17T19:25:10.520+08:00[Asia/Shanghai]

纽约时区的本地时间:2021-01-17T19:25:10.521-05:00[America/New_York]

北京实现对应的纽约时区的本地时间:2021-01-17T06:25:10.528-05:00[America/New_York]

计算:

格式化:

转换:
LocalDateTime -> ZonedDateTime

@Test

public void test10() {

LocalDateTime localDateTime = LocalDateTime.of(2021, 01, 17, 18, 00, 00);

System.out.println("当前时区(北京)时间为:" + localDateTime);

// 转换为偏移量为 -4的OffsetDateTime时间

// 1、-4地方的晚上18点

System.out.println("纽约时区晚上18点:" + ZonedDateTime.of(localDateTime, ZoneId.of("America/New_York")));

System.out.println("纽约时区晚上18点(方式二):" + localDateTime.atZone(ZoneId.of("America/New_York")));

// 2、北京时间晚上18:00 对应的-4地方的时间点

System.out.println("北京地区此时间对应的纽约的时间:" + ZonedDateTime.ofInstant(localDateTime.toInstant(ZoneOffset.ofHours(8)), ZoneOffset.ofHours(-4)));

System.out.println("北京地区此时间对应的纽约的时间:" + ZonedDateTime.ofInstant(localDateTime, ZoneOffset.ofHours(8), ZoneOffset.ofHours(-4)));

}

输出:

当前时区(北京)时间为:2021-01-17T18:00

纽约时区晚上18点:2021-01-17T18:00-05:00[America/New_York]

纽约时区晚上18点(方式二):2021-01-17T18:00-05:00[America/New_York]

北京地区此时间对应的纽约的时间:2021-01-17T06:00-04:00

北京地区此时间对应的纽约的时间:2021-01-17T06:00-04:00

OffsetDateTime -> ZonedDateTime

@Test

public void test101() {

OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-4));

System.out.println("-4偏移量时间为:" + offsetDateTime);

// 转换为ZonedDateTime的表示形式

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.toZonedDateTime());

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSameInstant(ZoneId.of("America/New_York")));

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSimilarLocal(ZoneId.of("America/New_York")));

}

-4偏移量时间为:2021-01-17T19:43:28.320-04:00

ZonedDateTime的表示形式:2021-01-17T19:43:28.320-04:00

ZonedDateTime的表示形式:2021-01-17T18:43:28.320-05:00[America/New_York]

ZonedDateTime的表示形式:2021-01-17T19:43:28.320-05:00[America/New_York]

本例有值得关注的点:

  • atZoneSameInstant():将此日期时间与时区结合起来创建ZonedDateTime,以确保结果具有相同的Instant

    • 所有偏移量-4 -> -5,时间点也从19 -> 18,确保了Instant保持一致嘛

  • atZoneSimilarLocal:将此日期时间与时区结合起来创建ZonedDateTime,以确保结果具有相同的本地时间

    • 所以直接效果和toLocalDateTime()是一样的,但是它会尽可能的保留偏移量(所以你看-4变为了-5,保持了真实的偏移量)

我这里贴出纽约2021年的夏令时时间区间:

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

也就是说在2021.03.14 - 2021.11.07期间,纽约的偏移量是-4,其余时候是-5。那么再看这个例子(我把时间改为5月5号,也就是处于夏令营期间):

@Test

public void test101() {

OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.of(2021, 05, 05, 18, 00, 00), ZoneOffset.ofHours(-4));

System.out.println("-4偏移量时间为:" + offsetDateTime);

// 转换为ZonedDateTime的表示形式

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.toZonedDateTime());

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSameInstant(ZoneId.of("America/New_York")));

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSimilarLocal(ZoneId.of("America/New_York")));

}

输出:

-4偏移量时间为:2021-05-05T18:00-04:00

ZonedDateTime的表示形式:2021-05-05T18:00-04:00

ZonedDateTime的表示形式:2021-05-05T18:00-04:00[America/New_York]

ZonedDateTime的表示形式:2021-05-05T18:00-04:00[America/New_York]

看到了吧,偏移量变为了-4。感受到夏令时的“威力”了吧。

OffsetDateTime和ZonedDateTime的区别

LocalDateTime、OffsetDateTime、ZonedDateTime这三个哥们,LocalDateTime好理解,一般都没有异议。但是很多同学对OffsetDateTime和ZonedDateTime傻傻分不清,这里说说它俩的区别。

  1. OffsetDateTime = LocalDateTime + 偏移量ZoneOffset;ZonedDateTime = LocalDateTime + 时区ZoneId
  2. OffsetDateTime可以随意设置偏移值,但ZonedDateTime无法自由设置偏移值,因为此值是由时区ZoneId控制的
  3. OffsetDateTime无法支持夏令时等规则,但ZonedDateTime可以很好的处理夏令时调整
  4. OffsetDateTime得益于不变性一般用于数据库存储、网络通信;而ZonedDateTime得益于其时区特性,一般在指定时区里显示时间非常方便,无需认为干预规则
  5. OffsetDateTime代表一个瞬时值,而ZonedDateTime的值是不稳定的,需要在某个瞬时根据当时的规则计算出来偏移量从而确定实际值

总的来说,OffsetDateTime和ZonedDateTime的区别主要在于ZoneOffset和ZoneId的区别。如果你只是用来传递数据,请使用OffsetDateTime,若你想在特定时区里做时间显示那么请务必使用ZonedDateTime。

总结

本着拒绝浅尝辄止的态度,深度剖析了很多同学可能不太熟悉的OffsetDateTime、ZonedDateTime两个API。总而言之,想要真正掌握日期时间体系(不限于Java语言,而是所有语言,甚至日常生活),对时区、偏移量的了解是绕不过去的砍,这块知识有所欠缺的朋友可往前翻翻补补课。

最后在使用它们三的过程中,有两个提醒给你:

  1. 所有日期/时间都是不可变的类型,所以若需要比较的话,请不要使用==,而是用equals()方法。
    2、任何时候,构造一个日期时间(包括它们三)请永远务必显示的指定时区,哪怕是默认时区。这么做的目的就是明确代码的意图,消除语义上的不确定性。比如若没指定时区,那到底是写代码的人欠考虑了呢,还是就是想用默认时区呢?总之显示指定绝大部分情况下比隐式“指定”语义上好得多。

本文思考题

看完了不一定懂,看懂了不一定会。来,文末3个思考题帮你复盘:

  1. 如何用LocalDateTime描述美国纽约本地时间?
  2. OffsetDateTime和ZonedDateTime你到底该使用谁?
  3. 一个人的生日应该用什么Java类型存储呢?

推荐阅读

GMT UTC CST ISO 夏令时 时间戳,都是些什么鬼?

全网最全!彻底弄透Java处理GMT/UTC日期时间

全球城市ZoneId和UTC时间偏移量的最全对照表

关注我

分享、成长,拒绝浅尝辄止。关注【BAT的乌托邦】回复关键字专栏有Spring技术栈、中间件等小而美的纯原创专栏。本文已被 https://www.yourbatman.cn 收录。

本文所属专栏:JDK日期时间,公号后台回复专栏名即可获取全部内容。

A哥(YourBatman):Spring Framework/Boot开源贡献者,Java架构师。非常注重基本功修养,相信底层基础决定上层建筑,坚实基础才能焕发程序员更强生命力。文章特点为以小而美专栏形式重构知识体系,抽丝剥茧,致力于做人人能看懂的最好的专栏系列。可加我好友(fsx1056342982)共勉哦!

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

System.out.println("点个赞吧!");

print_r('关注【BAT的乌托邦】!');

var_dump('点个赞吧!');

NSLog(@"关注【BAT的乌托邦】!");

console.log("点个赞吧!");

print("关注【BAT的乌托邦】!");

printf("点个赞吧!");

cout << "关注【BAT的乌托邦】!" << endl;

Console.WriteLine("点个赞吧!");

fmt.Println("关注【BAT的乌托邦】!");

Response.Write("点个赞吧!");

alert("关注【BAT的乌托邦】!");

echo("点个赞吧!");

java

阅读 50发布于 今天 04:43

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


BAT的乌托邦

专注Java领域分享、成长,拒绝浅尝辄止。公众号搜索:BAT的乌托邦

avatar

YourBatman

分享、成长,拒绝浅尝辄止。公众号:BAT的乌托邦

117 声望

12 粉丝

0 条评论

得票时间

avatar

YourBatman

分享、成长,拒绝浅尝辄止。公众号:BAT的乌托邦

117 声望

12 粉丝

宣传栏

你好,我是A哥(YourBatman)。

在JSR 310日期时间体系了,一共有三个API可用于表示日期时间:

  • LocalDateTime:本地日期时间
  • OffsetDateTime:带偏移量的日期时间
  • ZonedDateTime:带时区的日期时间

也许平时开发中你只用到过LocalDateTime这个API,那是极好的,但是不能止步于此,否则就图样图森破了。

随着场景的多样性变化,咱们开发者接触到OffsetDateTime/ZonedDateTime的概率越来越大,但凡和国际化产生上关系的大概率都会用得到它们。本文依然站在实用的角度,辅以具体代码示例,介绍它三。

本文提纲

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

版本约定

  • JDK:8

正文

下面这张图是一个完整的日期时间,拆解各个部分的含义,一目了然(建议收藏此图):

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

因为LocalDate、LocalTime等理解起来比较简单,就不用再花笔墨介绍了,重点放在LocalDateTime、OffsetDateTime、ZonedDateTime它三身上。

什么是LocalDateTime?

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

ISO-8601日历系统中不带时区的日期时间。

LocalDateTime是一个不可变的日期-时间对象,它表示一个日期时间,通常被视为年-月-日-小时-分钟-秒。还可以访问其他日期和时间字段,如day-of-year、day-of-week和week-of-year等等,它的精度能达纳秒级别。

该类不存储时区,所以适合日期的描述,比如用于生日、deadline等等。但是请记住,如果没有偏移量/时区等附加信息,一个时间是不能表示时间线上的某一时刻的。

代码示例

最大/最小值:

@Test

public void test1() {

LocalDateTime min = LocalDateTime.MIN;

LocalDateTime max = LocalDateTime.MAX;

System.out.println("LocalDateTime最小值:" + min);

System.out.println("LocalDateTime最大值:" + max);

System.out.println(min.getYear() + "-" + min.getMonthValue() + "-" + min.getDayOfMonth());

System.out.println(max.getYear() + "-" + max.getMonthValue() + "-" + max.getDayOfMonth());

}

输出:

LocalDateTime最小值:-999999999-01-01T00:00

LocalDateTime最大值:+999999999-12-31T23:59:59.999999999

-999999999-1-1

999999999-12-31

构造:

@Test

public void test2() {

System.out.println("当前时区的本地时间:" + LocalDateTime.now());

System.out.println("当前时区的本地时间:" + LocalDateTime.of(LocalDate.now(), LocalTime.now()));

System.out.println("纽约时区的本地时间:" + LocalDateTime.now(ZoneId.of("America/New_York")));

}

输出:

当前时区的本地时间:2021-01-17T17:00:41.446

当前时区的本地时间:2021-01-17T17:00:41.447

纽约时区的本地时间:2021-01-17T04:00:41.450

注意,最后一个构造传入了ZoneId,并不是说LocalDateTime和时区有关了,而是告诉说这个Local指的是纽约,细品这句话。

计算:

@Test

public void test3() {

LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());

System.out.println("计算前:" + now);

// 加3天

LocalDateTime after = now.plusDays(3);

// 减4个小时

after = after.plusHours(-3); // 效果同now.minusDays(3);

System.out.println("计算后:" + after);

// 计算时间差

Period period = Period.between(now.toLocalDate(), after.toLocalDate());

System.out.println("相差天数:" + period.getDays());

Duration duration = Duration.between(now.toLocalTime(), after.toLocalTime());

System.out.println("相差小时数:" + duration.toHours());

}

输出:

计算前:2021-01-17T17:10:15.381

计算后:2021-01-20T14:10:15.381

相差天数:3

相差小时数:-3

格式化:

@Test

public void test4() {

LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());

// System.out.println("格式化输出:" + DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(now));

System.out.println("格式化输出(本地化输出,中文环境):" + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(now));

String dateTimeStrParam = "2021-01-17 18:00:00";

System.out.println("解析后输出:" + LocalDateTime.parse(dateTimeStrParam, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.US)));

}

输出:

格式化输出(本地化输出,中文环境):21-1-17 下午5:15

解析后输出:2021-01-17T18:00

什么是OffsetDateTime?

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

ISO-8601日历系统中与UTC偏移量有关的日期时间。OffsetDateTime是一个带有偏移量的日期时间类型。存储有精确到纳秒的日期时间,以及偏移量。可以简单理解为 OffsetDateTime = LocalDateTime + ZoneOffset。

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

OffsetDateTime、ZonedDateTime和Instant它们三都能在时间线上以纳秒精度存储一个瞬间(请注意:LocalDateTime是不行的),也可理解我某个时刻。OffsetDateTime和Instant可用于模型的字段类型,因为它们都表示瞬间值并且还不可变,所以适合网络传输或者数据库持久化。

代码示例

最大/最小值:

@Test

public void test5() {

OffsetDateTime min = OffsetDateTime.MIN;

OffsetDateTime max = OffsetDateTime.MAX;

System.out.println("OffsetDateTime最小值:" + min);

System.out.println("OffsetDateTime最大值:" + max);

System.out.println(min.getOffset() + ":" + min.getYear() + "-" + min.getMonthValue() + "-" + min.getDayOfMonth());

System.out.println(max.getOffset() + ":" + max.getYear() + "-" + max.getMonthValue() + "-" + max.getDayOfMonth());

}

输出:

OffsetDateTime最小值:-999999999-01-01T00:00+18:00

OffsetDateTime最大值:+999999999-12-31T23:59:59.999999999-18:00

+18:00:-999999999-1-1

-18:00:999999999-12-31

偏移量的最大值是+18,最小值是-18,这是由ZoneOffset内部的限制决定的。

构造:

@Test

public void test6() {

System.out.println("当前位置偏移量的本地时间:" + OffsetDateTime.now());

System.out.println("偏移量-4(纽约)的本地时间::" + OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-4")));

System.out.println("纽约时区的本地时间:" + OffsetDateTime.now(ZoneId.of("America/New_York")));

}

输出:

当前位置偏移量的本地时间:2021-01-17T19:02:06.328+08:00

偏移量-4(纽约)的本地时间::2021-01-17T19:02:06.329-04:00

纽约时区的本地时间:2021-01-17T06:02:06.330-05:00

计算:

格式化:

@Test

public void test7() {

OffsetDateTime now = OffsetDateTime.now(ZoneId.systemDefault());

System.out.println("格式化输出(本地化输出,中文环境):" + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(now));

String dateTimeStrParam = "2021-01-17T18:00:00+07:00";

System.out.println("解析后输出:" + OffsetDateTime.parse(dateTimeStrParam));

}

输出:

格式化输出(本地化输出,中文环境):21-1-17 下午7:06

解析后输出:2021-01-17T18:00+07:00

转换:
LocalDateTime -> OffsetDateTime

@Test

public void test8() {

LocalDateTime localDateTime = LocalDateTime.of(2021, 01, 17, 18, 00, 00);

System.out.println("当前时区(北京)时间为:" + localDateTime);

// 转换为偏移量为 -4的OffsetDateTime时间

// 1、-4地方的晚上18点

System.out.println("-4偏移量地方的晚上18点:" + OffsetDateTime.of(localDateTime, ZoneOffset.ofHours(-4)));

System.out.println("-4偏移量地方的晚上18点(方式二):" + localDateTime.atOffset(ZoneOffset.ofHours(-4)));

// 2、北京时间晚上18:00 对应的-4地方的时间点

System.out.println("当前地区对应的-4地方的时间:" + OffsetDateTime.ofInstant(localDateTime.toInstant(ZoneOffset.ofHours(8)), ZoneOffset.ofHours(-4)));

}

输出:

当前时区(北京)时间为:2021-01-17T18:00

-4偏移量地方的晚上18点:2021-01-17T18:00-04:00

-4偏移量地方的晚上18点(方式二):2021-01-17T18:00-04:00

当前地区对应的-4地方的时间:2021-01-17T06:00-04:00

通过此例值得注意的是:LocalDateTime#atOffset()/atZone()只是增加了偏移量/时区,本地时间是并没有改变的。若想实现本地时间到其它偏移量的对应的时间只能通过其ofInstant()系列构造方法。

OffsetDateTime -> LocalDateTime

@Test

public void test81() {

OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-4));

System.out.println("-4偏移量时间为:" + offsetDateTime);

// 转为LocalDateTime 注意:时间还是未变的哦

System.out.println("LocalDateTime的表示形式:" + offsetDateTime.toLocalDateTime());

}

输出:

-4偏移量时间为:2021-01-17T19:33:28.139-04:00

LocalDateTime的表示形式:2021-01-17T19:33:28.139

什么是ZonedDateTime?

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

ISO-8601国际标准日历系统中带有时区的日期时间。它存储所有的日期和时间字段,精度为纳秒,以及一个时区,带有用于处理不明确的本地日期时间的时区偏移量。

这个API可以处理从LocalDateTime -> Instant -> ZonedDateTime的转换,其中用zone时区来表示偏移量(并非直接用offset哦)。两个时间点之间的转换会涉及到使用从ZoneId访问的规则计算偏移量(换句话说:偏移量并非写死而是根据规则计算出来的)。

获取瞬间的偏移量很简单,因为每个瞬间只有一个有效的偏移量。但是,获取本地日期时间的偏移量并不简单。存在这三种情况:

  • 正常情况:有一个有效的偏移量。对于一年中的绝大多数时间,适用正常情况,即本地日期时间只有一个有效的偏移量
  • 时间间隙情况:没有有效偏移量。这是由于夏令时开始时从“冬季”改为“夏季”而导致时钟向前拨的时候。在间隙中,没有有效偏移量
  • 重叠情况:有两个有效偏移量。这是由于秋季夏令时从“夏季”到“冬季”的变化,时钟会向后拨。在重叠部分中,有两个有效偏移量

这三种情况如果要自己处理,估计头都大了。这就是使用JSR 310的优势,ZonedDateTime全帮你搞定,让你使用无忧。

ZonedDateTime可简单认为是LocalDateTime和ZoneId的组合。而ZoneOffset是其内置的动态计算出来的一个次要信息,以确保输出一个瞬时值而存在,毕竟在某个瞬间偏移量ZoneOffset肯定是确定的。ZonedDateTime也可以理解为保存的状态相当于三个独立的对象:LocalDateTime、ZoneId和ZoneOffset。某个瞬间 = LocalDateTime + ZoneOffset。ZoneId确定了偏移量如何改变的规则。所以偏移量我们并不能自由设置(不提供set方法,构造时也不行),因为它由ZoneId来控制的。

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

代码示例

构造:

@Test

public void test9() {

System.out.println("当前位置偏移量的本地时间:" + ZonedDateTime.now());

System.out.println("纽约时区的本地时间:" + ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("America/New_York")));

System.out.println("北京实现对应的纽约时区的本地时间:" + ZonedDateTime.now(ZoneId.of("America/New_York")));

}

输出:

当前位置偏移量的本地时间:2021-01-17T19:25:10.520+08:00[Asia/Shanghai]

纽约时区的本地时间:2021-01-17T19:25:10.521-05:00[America/New_York]

北京实现对应的纽约时区的本地时间:2021-01-17T06:25:10.528-05:00[America/New_York]

计算:

格式化:

转换:
LocalDateTime -> ZonedDateTime

@Test

public void test10() {

LocalDateTime localDateTime = LocalDateTime.of(2021, 01, 17, 18, 00, 00);

System.out.println("当前时区(北京)时间为:" + localDateTime);

// 转换为偏移量为 -4的OffsetDateTime时间

// 1、-4地方的晚上18点

System.out.println("纽约时区晚上18点:" + ZonedDateTime.of(localDateTime, ZoneId.of("America/New_York")));

System.out.println("纽约时区晚上18点(方式二):" + localDateTime.atZone(ZoneId.of("America/New_York")));

// 2、北京时间晚上18:00 对应的-4地方的时间点

System.out.println("北京地区此时间对应的纽约的时间:" + ZonedDateTime.ofInstant(localDateTime.toInstant(ZoneOffset.ofHours(8)), ZoneOffset.ofHours(-4)));

System.out.println("北京地区此时间对应的纽约的时间:" + ZonedDateTime.ofInstant(localDateTime, ZoneOffset.ofHours(8), ZoneOffset.ofHours(-4)));

}

输出:

当前时区(北京)时间为:2021-01-17T18:00

纽约时区晚上18点:2021-01-17T18:00-05:00[America/New_York]

纽约时区晚上18点(方式二):2021-01-17T18:00-05:00[America/New_York]

北京地区此时间对应的纽约的时间:2021-01-17T06:00-04:00

北京地区此时间对应的纽约的时间:2021-01-17T06:00-04:00

OffsetDateTime -> ZonedDateTime

@Test

public void test101() {

OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(-4));

System.out.println("-4偏移量时间为:" + offsetDateTime);

// 转换为ZonedDateTime的表示形式

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.toZonedDateTime());

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSameInstant(ZoneId.of("America/New_York")));

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSimilarLocal(ZoneId.of("America/New_York")));

}

-4偏移量时间为:2021-01-17T19:43:28.320-04:00

ZonedDateTime的表示形式:2021-01-17T19:43:28.320-04:00

ZonedDateTime的表示形式:2021-01-17T18:43:28.320-05:00[America/New_York]

ZonedDateTime的表示形式:2021-01-17T19:43:28.320-05:00[America/New_York]

本例有值得关注的点:

  • atZoneSameInstant():将此日期时间与时区结合起来创建ZonedDateTime,以确保结果具有相同的Instant

    • 所有偏移量-4 -> -5,时间点也从19 -> 18,确保了Instant保持一致嘛

  • atZoneSimilarLocal:将此日期时间与时区结合起来创建ZonedDateTime,以确保结果具有相同的本地时间

    • 所以直接效果和toLocalDateTime()是一样的,但是它会尽可能的保留偏移量(所以你看-4变为了-5,保持了真实的偏移量)

我这里贴出纽约2021年的夏令时时间区间:

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

也就是说在2021.03.14 - 2021.11.07期间,纽约的偏移量是-4,其余时候是-5。那么再看这个例子(我把时间改为5月5号,也就是处于夏令营期间):

@Test

public void test101() {

OffsetDateTime offsetDateTime = OffsetDateTime.of(LocalDateTime.of(2021, 05, 05, 18, 00, 00), ZoneOffset.ofHours(-4));

System.out.println("-4偏移量时间为:" + offsetDateTime);

// 转换为ZonedDateTime的表示形式

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.toZonedDateTime());

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSameInstant(ZoneId.of("America/New_York")));

System.out.println("ZonedDateTime的表示形式:" + offsetDateTime.atZoneSimilarLocal(ZoneId.of("America/New_York")));

}

输出:

-4偏移量时间为:2021-05-05T18:00-04:00

ZonedDateTime的表示形式:2021-05-05T18:00-04:00

ZonedDateTime的表示形式:2021-05-05T18:00-04:00[America/New_York]

ZonedDateTime的表示形式:2021-05-05T18:00-04:00[America/New_York]

看到了吧,偏移量变为了-4。感受到夏令时的“威力”了吧。

OffsetDateTime和ZonedDateTime的区别

LocalDateTime、OffsetDateTime、ZonedDateTime这三个哥们,LocalDateTime好理解,一般都没有异议。但是很多同学对OffsetDateTime和ZonedDateTime傻傻分不清,这里说说它俩的区别。

  1. OffsetDateTime = LocalDateTime + 偏移量ZoneOffset;ZonedDateTime = LocalDateTime + 时区ZoneId
  2. OffsetDateTime可以随意设置偏移值,但ZonedDateTime无法自由设置偏移值,因为此值是由时区ZoneId控制的
  3. OffsetDateTime无法支持夏令时等规则,但ZonedDateTime可以很好的处理夏令时调整
  4. OffsetDateTime得益于不变性一般用于数据库存储、网络通信;而ZonedDateTime得益于其时区特性,一般在指定时区里显示时间非常方便,无需认为干预规则
  5. OffsetDateTime代表一个瞬时值,而ZonedDateTime的值是不稳定的,需要在某个瞬时根据当时的规则计算出来偏移量从而确定实际值

总的来说,OffsetDateTime和ZonedDateTime的区别主要在于ZoneOffset和ZoneId的区别。如果你只是用来传递数据,请使用OffsetDateTime,若你想在特定时区里做时间显示那么请务必使用ZonedDateTime。

总结

本着拒绝浅尝辄止的态度,深度剖析了很多同学可能不太熟悉的OffsetDateTime、ZonedDateTime两个API。总而言之,想要真正掌握日期时间体系(不限于Java语言,而是所有语言,甚至日常生活),对时区、偏移量的了解是绕不过去的砍,这块知识有所欠缺的朋友可往前翻翻补补课。

最后在使用它们三的过程中,有两个提醒给你:

  1. 所有日期/时间都是不可变的类型,所以若需要比较的话,请不要使用==,而是用equals()方法。
    2、任何时候,构造一个日期时间(包括它们三)请永远务必显示的指定时区,哪怕是默认时区。这么做的目的就是明确代码的意图,消除语义上的不确定性。比如若没指定时区,那到底是写代码的人欠考虑了呢,还是就是想用默认时区呢?总之显示指定绝大部分情况下比隐式“指定”语义上好得多。

本文思考题

看完了不一定懂,看懂了不一定会。来,文末3个思考题帮你复盘:

  1. 如何用LocalDateTime描述美国纽约本地时间?
  2. OffsetDateTime和ZonedDateTime你到底该使用谁?
  3. 一个人的生日应该用什么Java类型存储呢?

推荐阅读

GMT UTC CST ISO 夏令时 时间戳,都是些什么鬼?

全网最全!彻底弄透Java处理GMT/UTC日期时间

全球城市ZoneId和UTC时间偏移量的最全对照表

关注我

分享、成长,拒绝浅尝辄止。关注【BAT的乌托邦】回复关键字专栏有Spring技术栈、中间件等小而美的纯原创专栏。本文已被 https://www.yourbatman.cn 收录。

本文所属专栏:JDK日期时间,公号后台回复专栏名即可获取全部内容。

A哥(YourBatman):Spring Framework/Boot开源贡献者,Java架构师。非常注重基本功修养,相信底层基础决定上层建筑,坚实基础才能焕发程序员更强生命力。文章特点为以小而美专栏形式重构知识体系,抽丝剥茧,致力于做人人能看懂的最好的专栏系列。可加我好友(fsx1056342982)共勉哦!

【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你

System.out.println("点个赞吧!");

print_r('关注【BAT的乌托邦】!');

var_dump('点个赞吧!');

NSLog(@"关注【BAT的乌托邦】!");

console.log("点个赞吧!");

print("关注【BAT的乌托邦】!");

printf("点个赞吧!");

cout << "关注【BAT的乌托邦】!" << endl;

Console.WriteLine("点个赞吧!");

fmt.Println("关注【BAT的乌托邦】!");

Response.Write("点个赞吧!");

alert("关注【BAT的乌托邦】!");

echo("点个赞吧!");

以上是 【Java】LocalDateTime、OffsetDateTime、ZonedDateTime互转,这一篇绝对喂饱你 的全部内容, 来源链接: utcz.com/a/107862.html

回到顶部