即时时间和本地时间的区别是什么?

541

我知道:

  • Instant 是一种计算机技术中的时间戳表示方式(以纳秒为单位)。
  • LocalDateTime 是一种更适合人类使用的日期/时钟表示方式,但不包含时区信息。

总的来说,对于大多数应用场景,这两种类型都可以使用。例如:我正在运行一个批处理作业,需要根据日期计算下一次运行时间,除了 Instant 具有纳秒精度和 LocalDateTime 具有时区部分之外,我很难找到这两种类型之间的优缺点。

你能列举一些只能使用 Instant 或者 LocalDateTime 的应用场景吗?

注:请注意关于精度和时区的问题,不要误读关于 LocalDateTime 的文档。


Instant更基础,用于封装UTC标准的长整型。对于像cron这样的批处理而言,这并不是很合适的选择。 - Joop Eggen
68
错误的定义。LocalDateTime没有时区! - Basil Bourque
5个回答

1842

Table of all date-time types in Java, both modern and legacy

简短概述

InstantLocalDateTime 是两个完全不同的概念:一个代表一个时刻,而另一个则不是。

  • Instant 代表一个时刻,即时间轴上的一个具体点。
  • LocalDateTime 代表一个日期和一天中的某个时间。但由于缺少时区或与UTC的偏移量,这个类无法表示一个时刻。它表示了一系列潜在的时刻,范围大约为全球所有时区的26到27小时。一个LocalDateTime 值是本质上含糊不清的

错误的推断

LocalDateTime 是包括人类时区的日期/时钟表示。

你的陈述是不正确的:LocalDateTime 没有时区。没有时区是该类的整个重点。

引用该类文档的话:

这个类并不存储或表示时区。相反,它是日期的描述,如生日,再加上墙上时钟所显示的当地时间。如果没有附加信息(例如偏移量或时区),它无法表示时间线上的瞬间。因此,Local... 意味着“未分区,无偏移量”。

Instant

enter image description here

Instant是一个时间线上的时刻,使用UTC计算,自1970年第一个UTC时刻的纪元以来的纳秒数(详细信息请参阅类文档)。由于大多数业务逻辑、数据存储和数据交换都应该使用UTC,因此这是一个经常使用的便捷类。

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

OffsetDateTime

enter image description here

OffsetDateTime表示一个时刻,其日期和时间相对于UTC超前或落后一定的小时-分钟-秒数。偏移量,即小时-分钟-秒数由ZoneOffset类表示。

如果小时-分钟-秒数为零,则OffsetDateTime表示与Instant相同的UTC时刻。

ZoneOffset

enter image description here

ZoneOffset类表示与UTC相比提前或落后UTC的小时-分钟-秒数偏移量

ZoneOffset仅仅是一个小时-分钟-秒数,没有更多的信息。而时区则包含名称和偏移量变化的历史记录。因此,使用时区总是优先于使用简单的偏移量。

ZoneId

enter image description here

时区ZoneId类表示。

例如,在巴黎,新的一天比在蒙特利尔早到来。因此,我们需要将时钟指针移动以更好地反映给定地区正午(太阳正好在头顶上的时间)。从欧洲西部/非洲向东/向西远离UTC线路越远,偏移量就越大。

时区是处理本地社区或地区实践的调整和异常规则集。最常见的异常情况是众所周知的疯狂行为夏令时(DST)

时区具有过去规则、现在规则和未来近期确认规则的历史记录。

这些规则变化比您想象的要频繁。务必保持您的日期时间库的规则,通常是'tz'数据库的副本,处于最新状态。在Java 8中,通过Oracle发布时区更新工具更加容易保持最新。
大陆/地区的格式指定正确的时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。永远不要使用2-4个字母的缩写,如ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。

时区=偏移量+调整规则

ZoneId z = ZoneId.of( “Africa/Tunis” ) ; 

ZonedDateTime

enter image description here

ZonedDateTime概念上理解为具有指定ZoneIdInstant

ZonedDateTime = ( Instant + ZoneId )

要捕捉特定区域(时区)人们使用的挂钟时间中看到的当前时刻:

ZonedDateTime zdt = ZonedDateTime.now( z ) ;  // Pass a `ZoneId` object such as `ZoneId.of( "Europe/Paris" )`. 

几乎所有后端、数据库、业务逻辑、数据持久化和数据交换都应该使用UTC时间。但是为了向用户展示,您需要将其调整为用户期望的时区。这就是ZonedDateTime类和用于生成这些日期时间值的格式化程序类的目的。
ZonedDateTime zdt = instant.atZone( z ) ;
String output = zdt.toString() ;                 // Standard ISO 8601 format.

你可以使用DateTimeFormatter生成本地化格式的文本。
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( Locale.CANADA_FRENCH ) ; 
String outputFormatted = zdt.format( f ) ;

印度时间2019年4月30日23时22分55秒

LocalDateLocalTimeLocalDateTime

Diagram showing only a calendar for a LocalDate.

Diagram showing only a clock for a LocalTime.

Diagram showing a calendar plus clock for a LocalDateTime.

"本地"日期时间类LocalDateTimeLocalDateLocalTime是一种不同的实体。它们不与任何特定地区或时区相关联,也不与时间线相关联。除非将它们应用于某个地区以找到时间线上的点,否则它们没有实际意义
这些类名中的“本地”一词可能对未经培训的人来说有些反直觉。该词的意思是任何地方或每个地方,但不是特定地区。
对于商业应用而言,“本地”类型通常不会使用,因为它们只代表可能的日期或时间的概念,而非时间轴上的特定时刻。商业应用往往关心发票到达的确切时刻、产品运送出发的时间、员工入职的时间或出租车离开车库的时间。因此,商业应用程序开发人员最常使用的是InstantZonedDateTime类。
那么我们什么时候会使用LocalDateTime呢?有三种情况:
  • 我们想要在多个位置应用某个日期和某个时间。
  • 我们正在预约。
  • 我们有一个打算但尚未确定的时区。
请注意,这三种情况都不涉及时间轴上的单个特定点,也没有任何时刻。

一天中的某个时间,多个时刻

有时候我们想要表示某个日期的某个时间,但希望将其应用于跨越多个时区的多个地方。
例如,“圣诞节从2015年12月25日午夜开始”是一个LocalDateTime。 在巴黎,午夜的时刻与蒙特利尔不同,与西雅图奥克兰也不同。
LocalDate ld = LocalDate.of( 2018 , Month.DECEMBER , 25 ) ;
LocalTime lt = LocalTime.MIN ;   // 00:00:00
LocalDateTime ldt = LocalDateTime.of( ld , lt ) ;  // Christmas morning anywhere. 

另一个例子,“Acme公司的政策是,在其全球各工厂午餐时间从下午12:30开始”是一个LocalTime。为了真正有意义,您需要将其应用于时间轴,以确定斯图加特工厂或拉巴特工厂或悉尼工厂的下午12:30时刻。

预约

另一种使用LocalDateTime的情况是预约未来事件(例如:牙医预约)。这些约会可能在将来足够遥远,以至于您面临政客重新定义时区的风险。政客通常很少提前警告,甚至没有任何警告。如果您的意思是“下一年1月23日下午3点”,无论政客如何改变时钟,您都不能记录一个时刻-如果该地区采用或取消夏令时,那么下午3点将变成下午2点或下午4点。

对于预约,存储LocalDateTimeZoneId,分开保存。稍后,在生成时间表时,通过调用LocalDateTime::atZone( ZoneId )来动态确定一个时刻,并生成一个ZonedDateTime对象。

ZonedDateTime zdt = ldt.atZone( z ) ;  // Given a date, a time-of-day, and a time zone, determine a moment, a point on the timeline.

如果需要的话,您可以调整为UTC。从ZonedDateTime中提取一个Instant。
Instant instant = zdt.toInstant() ;  // Adjust from some zone to UTC. Same moment, same point on the timeline, different wall-clock time.

未知时区

有些人可能会在时区或偏移量未知的情况下使用LocalDateTime

我认为这种情况是不恰当和不明智的。如果有意但未确定时区或偏移量,则存在错误数据。这就像存储产品价格但不知道所用货币(美元、英镑、欧元等)。不是一个好主意。

所有日期时间类型

为了完整起见,这里列出了Java中所有可能的现代和遗留日期时间类型,以及SQL标准定义的类型。这可能有助于将InstantLocalDateTime类置于更大的上下文中。

Table of all date-time types in Java (both modern & legacy) as well as SQL standard.

请注意Java团队在设计JDBC 4.2时所做的奇怪选择。他们选择支持所有的java.time时间……除了两个最常用的类:InstantZonedDateTime
但不用担心,我们可以轻松地进行转换。
转换Instant
// Storing
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;

// Retrieving
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Instant instant = odt.toInstant() ;

转换ZonedDateTime

// Storing
OffsetDateTime odt = zdt.toOffsetDateTime() ;
myPreparedStatement.setObject( … , odt ) ;

// Retrieving
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime zdt = odt.atZone( z ) ; 

关于 java.time

java.time框架内置于Java 8及以上版本。这些类取代了老旧的传统日期时间类,如java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参见Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310Joda-Time项目现在处于维护模式,建议迁移到java.time类。

你可以直接使用与JDBC 4.2或更高版本兼容的JDBC驱动程序,直接在数据库中交换java.time对象,无需字符串,无需java.sql.*类。Hibernate 5和JPA 2.2支持java.time

如何获取java.time类?

Table of which java.time library to use with which version of Java or Android

ThreeTen-Extra项目通过添加额外的类来扩展java.time。该项目是java.time可能未来增加的类的试验场。您可能会在此处找到一些有用的类,例如Interval, YearWeek, YearQuarter更多


77
很好的回答。我认为一些混淆(至少是我的)来自于“本地”命名。我对“本地”的直觉意味着与我所在的位置和时间有关,这让我相信它实际上应该是ZonedDateTime的含义。 - mkobit
6
是的,这很令人困惑。这就是为什么java.time聪明地在其前身Joda-Time使用的DateTime类名中添加了单词“Zoned”,以强调与“Local”类的区别(产生ZonedDateTime)。将名称“Local”视为“需要应用于某个特定地点”的简称。 - Basil Bourque
2
在单词“Local”前加上前缀可能也是一种与java.util包区分的方式,尽管我觉得可能有更好的词选择。 - riddle_me_this
2
相反地,当那位新员工签署了包括人寿保险在内的福利待遇文件后,走出去喝咖啡却被一辆卡车撞死时,许多人,如人力资源经理、保险代理和律师,都想知道新雇佣生效的确切时间。 - Basil Bourque
3
@simonh 是的,有一些情况下使用“本地”日期时间是合适的。除了我回答中提到的情况之外,在商业领域中另一个常见情况是预约将在未来几个月内进行,足够久远以至于政客可能会改变时区规则,通常很少有预警。政客经常进行这些更改,例如更改进出夏令时(DST)的日期或永久停止进出夏令时。 - Basil Bourque
显示剩余29条评论

40

一个主要的区别是LocalDateTime中的“本地”部分。如果你住在德国并创建了一个LocalDateTime实例,而其他人住在美国并在同一时刻创建了另一个实例(假设时钟设置正确),这些对象的值实际上会不同。但这不适用于Instant,它是独立于时区计算的。

LocalDateTime存储日期和时间,但不带时区,但其初始值是依赖于时区的。而Instant则没有。

此外,LocalDateTime提供了操纵日期组件(如天、小时和月份)的方法,而Instant则没有。

除了Instant具有纳秒精度优势和LocalDateTime的时区部分之外

两个类具有相同的精度。 LocalDateTime不存储时区信息。请仔细阅读Javadocs文档,因为您可能会因此做出错误的假设:InstantLocalDateTime


对于误读时区和精度部分感到抱歉。对于单一时区应用程序,您会在哪些用例中更喜欢使用LocalDateTime或相反? - manuel aldana
2
每当我需要日期和/或时间时,我会使用LocalDateTime。以小时、分钟等为单位。例如,我会使用Instant来测量执行时间,或者存储某个事件的内部字段。像你这样计算下一次运行时间?LocalDateTime似乎是合适的选择,但这只是我的意见。正如你所说,两者都可以使用。 - Dariusz

19
关于 LocalDateTime,你的理解是错误的:它不存储任何时区信息,且具有纳秒精度。引用Javadoc(重点在于强调): 在 ISO-8601 日历系统中不带时区的日期时间,例如 2007-12-03T10:15:30。 LocalDateTime 是一个不可变的日期时间对象,表示日期时间,通常被视为年月日时分秒。可以访问其他日期和时间字段,例如年份中的天数、星期几和一年中的周数。时间以纳秒的精度表示。例如,“2007 年 10 月 2 日下午 13:45.30.123456789” 的值可以存储在 LocalDateTime 中。
Instant 表示自 1970 年 01-01 的偏移量,因此代表时间线上特定的瞬间。在地球上两个不同地方同时创建的两个 Instant 对象将具有完全相同的值。

考虑到单一时区应用程序,在哪些使用情况下您会更喜欢使用LocalDateTime或相反的情况? - manuel aldana
3
更多是口味问题。我更喜欢在与用户相关的任何事情(例如生日)中使用LocalDateTime,在与机器相关的任何事情(例如执行时间)中使用Instant。 - Tunaki
2
@manuelaldana 如果不是根本不存在的话,单一时区应用程序就很少见了。您可能可以无视时区,为本地巴洛克音乐俱乐部制作一个小应用程序。但是,一旦您需要向旅行者(并跨越时区)发布事件,他们将希望将数据绑定到时区,以便他们的日历应用程序可以根据需要进行调整。我建议您在所有应用程序中都正确处理时区。 - Basil Bourque
@Tunaki 在最后一段中使用“offset”这个词有些分散注意力。在日期时间工作中,该词具有特定的含义,因此在此上下文中使用可能不太有帮助。 - Basil Bourque

7
LocalDateTime没有时区信息:同一LocalDateTime在世界各地的不同机器上可能代表不同的时刻。因此,您不应尝试使用隐式时区(系统默认时区)来使用它。您应该根据其所代表的内容使用它,例如“新年是1月1日0:00”:这意味着全球各地的所有时间点都不同,但在这种情况下需要。

Instant是格林威治时区的一个时间点。将其与用户的时区一起使用,以向他/她显示会议的开始时间,例如。

如果这两个类不表示您要存储/交换的内容,则可能ZonedDateTime或其他类可以更好地完成工作。

以下是一个简单的综合模式,可帮助您了解java.time包中的类及其与ISO-8601标准的关系,该标准用于可靠而轻松地在Java和其他语言或框架之间交换日期和时间:

Classes of the java.time package and their relations to the ISO-8601 standard

这里详细解释了模式:http://slaout.linux62.org/java-date-time/

2
我在我的职业生涯中处理过许多项目中的时区问题。事后我必须承认,由于这个话题的复杂性出乎意料,我们从未完全做对。这就是为什么我决定写一篇详细介绍java.time包的博客文章
虽然已经有很多关于上述问题的优秀答案,但这篇博客文章不仅涵盖了Java,还涵盖了与关系型数据库、NoSQL数据库(如MongoDB)或JavaScript等技术的交互,这些技术在处理时区时通常比Java支持更有限。
虽然我建议阅读这篇文章以获得对所有概念的详细理解,但我想添加两个来自我的博文的图表,以便快速了解。
第一个图表总结了描述时间点的java.time类的特点。一些类代表着绝对(或明确)的时间点(称为瞬间),而其他类如LocalDateTime只描述了所谓的挂钟时间。

Summary of the features of the java.time classes describing a point in time

第二个模式提供了一个决策树,它将帮助您选择适合您使用情况的正确类别。

Decision tree to help you pick the right class for your use case.

在这个决策树中,有一些决策并不是那么琐碎。这些决策在我的博客文章中有更详细的解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接