简短概述
Instant
和 LocalDateTime
是两个完全不同的概念:一个代表一个时刻,而另一个则不是。
Instant
代表一个时刻,即时间轴上的一个具体点。
LocalDateTime
代表一个日期和一天中的某个时间。但由于缺少时区或与UTC的偏移量,这个类无法表示一个时刻。它表示了一系列潜在的时刻,范围大约为全球所有时区的26到27小时。一个LocalDateTime
值是本质上含糊不清的。
错误的推断
LocalDateTime
是包括人类时区的日期/时钟表示。
你的陈述是不正确的:LocalDateTime
没有时区。没有时区是该类的整个重点。
引用该类文档的话:
这个类并不存储或表示时区。相反,它是日期的描述,如生日,再加上墙上时钟所显示的当地时间。如果没有附加信息(例如偏移量或时区),它无法表示时间线上的瞬间。因此,
Local...
意味着“未分区,无偏移量”。
Instant
Instant
是一个时间线上的时刻,使用UTC计算,自1970年第一个UTC时刻的纪元以来的纳秒数(详细信息请参阅类文档)。由于大多数业务逻辑、数据存储和数据交换都应该使用UTC,因此这是一个经常使用的便捷类。
Instant instant = Instant.now() ; // Capture the current moment in UTC.
OffsetDateTime
类OffsetDateTime
表示一个时刻,其日期和时间相对于UTC超前或落后一定的小时-分钟-秒数。偏移量,即小时-分钟-秒数由ZoneOffset
类表示。
如果小时-分钟-秒数为零,则OffsetDateTime
表示与Instant
相同的UTC时刻。
ZoneOffset
ZoneOffset
类表示与UTC相比提前或落后UTC的小时-分钟-秒数偏移量。
ZoneOffset
仅仅是一个小时-分钟-秒数,没有更多的信息。而时区则包含名称和偏移量变化的历史记录。因此,使用时区总是优先于使用简单的偏移量。
ZoneId
时区由ZoneId
类表示。
例如,在巴黎,新的一天比在蒙特利尔早到来。因此,我们需要将时钟指针移动以更好地反映给定地区正午(太阳正好在头顶上的时间)。从欧洲西部/非洲向东/向西远离UTC线路越远,偏移量就越大。
时区是处理本地社区或地区实践的调整和异常规则集。最常见的异常情况是众所周知的疯狂行为夏令时(DST)。
时区具有过去规则、现在规则和未来近期确认规则的历史记录。
这些规则变化比您想象的要频繁。务必保持您的日期时间库的规则,通常是
'tz'数据库的副本,处于最新状态。在Java 8中,通过Oracle发布
时区更新工具更加容易保持最新。
以
大陆/地区
的格式指定
正确的时区名称,例如
America/Montreal
、
Africa/Casablanca
或
Pacific/Auckland
。永远不要使用2-4个字母的缩写,如
EST
或
IST
,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。
时区=偏移量+调整规则
ZoneId z = ZoneId.of( “Africa/Tunis” ) ;
ZonedDateTime
将ZonedDateTime
概念上理解为具有指定ZoneId
的Instant
。
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秒
LocalDate
,LocalTime
,LocalDateTime
"本地"日期时间类
LocalDateTime
,
LocalDate
,
LocalTime
是一种不同的实体。它们不与任何特定地区或时区相关联,也不与时间线相关联。
除非将它们应用于某个地区以找到时间线上的点,否则它们没有实际意义。
这些类名中的“本地”一词可能对未经培训的人来说有些反直觉。该词的意思是
任何地方或
每个地方,但
不是特定地区。
对于商业应用而言,“本地”类型通常不会使用,因为它们只代表可能的日期或时间的概念,而非时间轴上的特定时刻。商业应用往往关心发票到达的确切时刻、产品运送出发的时间、员工入职的时间或出租车离开车库的时间。因此,商业应用程序开发人员最常使用的是
Instant
和
ZonedDateTime
类。
那么我们什么时候会使用
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点。
对于预约,存储LocalDateTime
和ZoneId
,分开保存。稍后,在生成时间表时,通过调用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标准定义的类型。这可能有助于将Instant
和LocalDateTime
类置于更大的上下文中。
请注意Java团队在设计JDBC 4.2时所做的奇怪选择。他们选择支持所有的
java.time时间……除了两个最常用的类:
Instant
和
ZonedDateTime
。
但不用担心,我们可以轻松地进行转换。
转换
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.Date
、Calendar
和SimpleDateFormat
。
要了解更多信息,请参见
Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是
JSR 310。
Joda-Time项目现在处于
维护模式,建议迁移到
java.time类。
你可以直接使用与JDBC 4.2或更高版本兼容的JDBC驱动程序,直接在数据库中交换java.time对象,无需字符串,无需java.sql.*
类。Hibernate 5和JPA 2.2支持java.time。
如何获取java.time类?
ThreeTen-Extra项目通过添加额外的类来扩展java.time。该项目是java.time可能未来增加的类的试验场。您可能会在此处找到一些有用的类,例如Interval
, YearWeek
, YearQuarter
和更多。
LocalDateTime
没有时区! - Basil Bourque