Java LocalDateTime 到 ZonedDateTime
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36417317/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
LocalDateTime to ZonedDateTime
提问by sonoerin
I have Java 8 Spring web app that will support multiple regions. I need to make calendar events for a customer location. So lets say my web and Postgres server is hosted in MST timezone (but I guess it could be anywhere if we go cloud). But the customer is in EST. So, following some best practices I read, I thought I would store all date times in UTC format. All date time fields in the database are declared as TIMESTAMP.
我有支持多个区域的 Java 8 Spring Web 应用程序。我需要为客户位置制作日历事件。所以可以说我的 Web 和 Postgres 服务器托管在 MST 时区(但我想如果我们使用云,它可以在任何地方)。但客户在 EST。因此,按照我阅读的一些最佳实践,我认为我会以 UTC 格式存储所有日期时间。数据库中的所有日期时间字段都声明为 TIMESTAMP。
So here is how I take a LocalDateTime and convert to UTC:
所以这里是我如何获取 LocalDateTime 并转换为 UTC:
ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getStartDateTime(), ZoneOffset.UTC, null);
//appointment startDateTime is a LocalDateTime
appointment.setStartDateTime( startZonedDT.toLocalDateTime() );
Now , for example, when a search for a date comes in, I have to convert from the requested date time to UTC, get the results and convert to the user timezone (stored in the db):
现在,例如,当搜索日期时,我必须从请求的日期时间转换为 UTC,获取结果并转换为用户时区(存储在数据库中):
ZoneId userTimeZone = ZoneId.of(timeZone.getID());
ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(appointment.getStartDateTime(), userTimeZone, null);
dto.setStartDateTime( startZonedDT.toLocalDateTime() );
Now, I am not sure this is the correct approach. I also wonder if because I am going from LocalDateTime to ZonedDateTime and vice versa, I might be losing any timezone info.
现在,我不确定这是正确的方法。我也想知道是否因为我从 LocalDateTime 到 ZonedDateTime 反之亦然,我可能会丢失任何时区信息。
Here is what I am seeing and it doesn't seem correct to me. When I receive the LocalDateTime from the UI, I get this:
这是我所看到的,对我来说似乎不正确。当我从 UI 收到 LocalDateTime 时,我得到了这个:
2016-04-04T08:00
The ZonedDateTime =
ZonedDateTime =
dateTime=2016-04-04T08:00
offset="Z"
zone="Z"
And then when I assign that converted value to my appointment LocalDateTime I store:
然后当我将转换后的值分配给我的约会 LocalDateTime 我存储:
2016-04-04T08:00
I feel like because I am storing in LocalDateTime I am losing the timezone that I converted into ZonedDateTime.
我觉得因为我存储在 LocalDateTime 中,所以我丢失了转换为 ZonedDateTime 的时区。
Should I make my entity (appointment) use ZonedDateTime instead of LocalDateTime so that Postgres doesn't loose that information?
我应该让我的实体(约会)使用 ZonedDateTime 而不是 LocalDateTime 以便 Postgres 不会丢失该信息?
---------------- Edit----------------
----------------编辑----------------
After Basils excellent answer, I realized that I have the luxury of not caring of the users timezone - all appointments are against a specific location so I can store all date times as UTC and then convert them to the location timezone when retrieved. I made the following followup question
在 Basils 出色的回答之后,我意识到我可以不关心用户时区 - 所有约会都针对特定位置,因此我可以将所有日期时间存储为 UTC,然后在检索时将它们转换为位置时区。我提出了以下后续问题
采纳答案by Basil Bourque
Postgres has no such data type as TIMESTAMP
. Postgres has two typesfor date plus time-of-day: TIMESTAMP WITH TIME ZONE
and TIMESTAMP WITHOUT TIME ZONE
. These types have very different behavior with regard to time zone information.
Postgres 没有像TIMESTAMP
. Postgres 有两种类型的日期加时间:TIMESTAMP WITH TIME ZONE
和TIMESTAMP WITHOUT TIME ZONE
. 这些类型在时区信息方面有非常不同的行为。
- The
WITH
typeuses any offset or time zone information to adjust the date-time to UTC, then disposes of that offset or time zone; Postgres never saves the offset/zone info.- This type represents a moment, a specific point on the timeline.
- The
WITHOUT
typeignores any offset or zone info that may be present.- This type does notrepresent a moment. It represents a vague idea of potentialmoments along a range of about 26-27 hours (the range of time zones around the globe).
- 该
WITH
类型使用任何偏移量或时区信息将日期时间调整为UTC,然后处理该偏移量或时区;Postgres 从不保存偏移/区域信息。- 这种类型代表一个时刻,时间线上的一个特定点。
- 该
WITHOUT
类型忽略可能存在的任何偏移量或区域信息。- 这种类型也并不表示一个时刻。它代表了大约 26-27 小时(全球时区范围)内潜在时刻的模糊概念。
You virtually always want the WITH
type, as explained hereby expert David E. Wheeler. The WITHOUT
only makes sense when you have the vague idea of a date-time rather than a fixed point on the timeline. For example, "Christmas this year starts at 2016-12-25T00:00:00" would be stored in the WITHOUT
as it applies to anytime zone, not yet having been applied to any one single time zone to get an actual moment on the timeline. If Santa's elves were tracking the start time for Eugene Oregon US, then they would use the WITH
type and an input that included an offset or time zone such as 2016-12-25T00:00:00-08:00
which gets saved into Postgres as 2016-12-25T08:00.00Z
(where the Z
means Zulu or UTC).
WITH
正如专家 David E. Wheeler在此解释的那样,您几乎总是想要这种类型。该WITHOUT
才有意义,当你有一个日期-时间的模糊的概念,而不是在时间轴上的固定点。例如,“今年圣诞节开始于 2016-12-25T00:00:00”将存储在WITHOUT
适用于任何时区的 中,尚未应用于任何一个时区以获取实际时刻时间线。如果圣诞老人的精灵正在跟踪 Eugene Oregon US 的开始时间,那么他们将使用WITH
包含偏移量或时区的类型和输入,例如将2016-12-25T00:00:00-08:00
其保存到 Postgres 中2016-12-25T08:00.00Z
(其中Z
表示祖鲁语或 UTC)。
The equivalent of Postgres' TIMESTAMP WITHOUT TIME ZONE
in java.time is java.time.LocalDateTime
. As your intention was to work in UTC (a good thing), you should notbe using LocalDateTime
(a bad thing). That may be the main point of confusion and trouble for you. You keep thinking about using LocalDateTime
or ZonedDateTime
but you should be using neither; instead you should be using Instant
(discussed below).
TIMESTAMP WITHOUT TIME ZONE
java.time中 Postgres' 的等价物是java.time.LocalDateTime
. 由于您的意图是在 UTC 中工作(一件好事),您不应该使用LocalDateTime
(一件坏事)。这可能是你的主要困惑和麻烦。您一直在考虑使用LocalDateTime
或,ZonedDateTime
但您不应该使用两者;相反,您应该使用Instant
(在下面讨论)。
I also wonder if because I am going from LocalDateTime to ZonedDateTime and vice versa, I might be losing any timezone info.
我也想知道是否因为我从 LocalDateTime 到 ZonedDateTime 反之亦然,我可能会丢失任何时区信息。
Indeed you are. The entire point to LocalDateTime
is to losetime zone info.So we rarely use this class in most apps. Again, the Christmas example. Or another example, "Company policy: All our factories around the world take lunch at 12:30 PM". That would be LocalTime
, and for a particular date, LocalDateTime
. But that has no real meaning, not an actual point on the timeline, until you apply a time zone to get a ZonedDateTime
. That lunch break will be at different points on the timeline in the Delhi factory than the Düsseldorf factory and different again at the Detroit factory.
确实你是。重点LocalDateTime
是丢失时区信息。所以我们在大多数应用中很少使用这个类。再次,圣诞节的例子。或者另一个例子,“公司政策:我们在世界各地的所有工厂都在中午 12:30 吃午饭”。那将是LocalTime
,对于特定日期,LocalDateTime
。但这没有真正的意义,不是时间线上的实际点,直到您应用时区来获得ZonedDateTime
. 德里工厂的午休时间与杜塞尔多夫工厂的时间线不同,底特律工厂的午休时间也不同。
The word "Local" in LocalDateTime
may be counter-intuitive as it means no particularlocality. When you read “Local” in a class name, think “Not a moment… not on the timeline… just a fuzzy idea about a kinda-sorta date-time”.
“本地”一词LocalDateTime
可能违反直觉,因为它表示没有特定的位置。当您在班级名称中读到“本地”时,请想一想“不是片刻……不在时间线上……只是关于某种日期时间的模糊概念”。
Your servers should almost always be set to UTC in their operating system time zone. But your programming should never depend on this externality as it is all too easy for a sysadmin to change it or for any other Java app to change the current default time zone within the JVM. So always specify your desired/expected time zone. (Same goes for Locale
, by the way.)
您的服务器几乎应始终在其操作系统时区中设置为 UTC。但是您的编程永远不应该依赖于这种外部性,因为系统管理员或任何其他 Java 应用程序更改 JVM 中的当前默认时区都太容易了。因此,请始终指定您想要/预期的时区。(Locale
顺便说一下,同样适用。)
Upshot:
结果:
- You are working way too hard.
- Programmers/sysadmins must learn to "Think global, Present local".
- 你工作太辛苦了。
- 程序员/系统管理员必须学会“思考全球,呈现本地”。
During the work day while wearing your geek hard-hat, think in UTC. Only at the end of the day when switching to your layperson's had should you go back to thinking of the local time of your town.
在工作日,戴着你的极客安全帽,用 UTC 思考。只有在一天结束时切换到您的外行人,您才应该回到考虑您所在城镇的当地时间。
Your business logic should focus on UTC. Your database storage, business logic, data exchange, serialization, logging, and your own thinking should all be done in UTC time zone (and 24-hour clock, by the way). When presenting data to users, only then apply a particular time zone. Think of zoned date-times as an external thing, not a working part of your app' internals.
您的业务逻辑应关注 UTC。您的数据库存储、业务逻辑、数据交换、序列化、日志记录和您自己的想法都应该在 UTC 时区(顺便说一下,24 小时制)中完成。向用户展示数据时,只应用特定的时区。将分区日期时间视为外部事物,而不是应用程序内部的工作部分。
On the Java side, use java.time.Instant
(a moment on the timeline in UTC) in much of your business logic.
在 Java 方面,java.time.Instant
在您的大部分业务逻辑中使用(UTC 时间线上的一个时刻)。
Instant now = Instant.now();
Hopefully JDBCdrivers will eventually be updated to deal with java.time types like Instant
directly. Until then we must use java.sql types. The old java.sql class have new methods for conversion to/from java.time.
希望JDBC驱动程序最终将被更新以Instant
直接处理 java.time 类型。在此之前我们必须使用 java.sql 类型。旧的 java.sql 类具有与 java.time 相互转换的新方法。
java.sql.TimeStamp ts = java.sql.TimeStamp.valueOf( instant );
Now pass that java.sql.TimeStamp
object via setTimestamp
on a PreparedStatement
to be saved to a column defined as TIMESTAMP WITH TIME ZONE
in Postgres.
现在通过a传递该java.sql.TimeStamp
对象以保存到Postgres 中定义的列。setTimestamp
PreparedStatement
TIMESTAMP WITH TIME ZONE
To go the other direction:
去另一个方向:
Instant instant = ts.toInstant();
So that is easy, going from Instant
to java.sql.Timestamp
to TIMESTAMP WITH TIME ZONE
, all in UTC. No time zones involved.The current default time zone of your server OS, your JVM, and your clients, is all irrelevant.
所以这很容易,从Instant
到java.sql.Timestamp
到TIMESTAMP WITH TIME ZONE
,全部采用 UTC。不涉及时区。服务器操作系统、JVM 和客户端的当前默认时区都无关紧要。
To present to user, apply a time zone. Use proper time zone names, never the 3-4 letter codes such as EST
or IST
.
要呈现给用户,请应用时区。使用正确的时区名称,不要使用 3-4 个字母的代码,例如EST
或IST
。
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
You can adjust into a different zone as needed.
您可以根据需要调整到不同的区域。
ZonedDateTime zdtKolkata = zdt.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) );
To get back to an Instant
, a moment on the timeline in UTC, you can extract from the ZonedDateTime
.
要回到Instant
UTC 时间线上的某个时刻,您可以从ZonedDateTime
.
Instant instant = zdt.toInstant();
No where in there did we use LocalDateTime
.
我们在那里没有使用LocalDateTime
.
If you do get a piece of data without any offset-from-UTC or time zone, such as 2016-04-04T08:00
, that data is entirely useless to you (assuming we are not talking about the Christmas or Company Lunch type scenarios discussed above). A date-time without offset/zone info is like a monetary amount without indicating currency: 142.70
or even $142.70
-- useless. But USD 142.70
, or CAD 142.70
, or MXN 142.70
… those are useful.
如果您确实获得了一条没有任何 UTC 或时区偏移量的数据,例如2016-04-04T08:00
,那么该数据对您来说完全没有用(假设我们不是在讨论上面讨论的圣诞节或公司午餐类型的场景)。没有偏移/区域信息的日期时间就像没有指示货币的货币金额:142.70
或者甚至$142.70
- 无用。但是USD 142.70
,或CAD 142.70
,或MXN 142.70
……这些很有用。
If you do get that 2016-04-04T08:00
value, and you are absolutely certainof the intended offset/zone context, then:
如果您确实获得了该2016-04-04T08:00
值,并且您绝对确定预期的偏移/区域上下文,则:
- Parse that string as a
LocalDateTime
. - Apply an offset-from-UTC to get a
OffsetDateTime
, or (better) apply a time zone to get aZonedDateTime
.
- 将该字符串解析为
LocalDateTime
. - 应用从 UTC 开始的偏移量以获取
OffsetDateTime
,或者(更好)应用时区以获取ZonedDateTime
.
Like this code.
喜欢这个代码。
LocalDateTime ldt = LocalDateTime.parse( "2016-04-04T08:00" );
ZoneId zoneId = ZoneId.of( "Asia/Kolkata" ); // Or "America/Montreal" etc.
ZonedDateTime zdt = ldt.atZone( zoneId ); // Or atOffset( myZoneOffset ) if only an offset is known rather than a full time zone.
Your Question really is a duplicate of many others. These issues have been discussed many times in other Questions and Answers. I urge you to search and study Stack Overflow to learn more on this topic.
您的问题确实与许多其他问题重复。这些问题已在其他问答中多次讨论。我敦促您搜索和研究 Stack Overflow 以了解有关此主题的更多信息。
JDBC 4.2
JDBC 4.2
As of JDBC 4.2 we can directly exchange java.timeobjects with the database. No need to ever use java.sql.Timestamp
again, nor its related classes.
从 JDBC 4.2 开始,我们可以直接与数据库交换java.time对象。无需java.sql.Timestamp
再次使用,也无需再使用其相关类。
Storing, using OffsetDateTime
as defined in the JDBC spec.
存储,OffsetDateTime
按照 JDBC 规范中的定义使用。
myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ; // The JDBC spec requires support for `OffsetDateTime`.
…or possibly use Instant
directly, if supported by your JDBC driver.
...或者可能Instant
直接使用,如果您的 JDBC 驱动程序支持。
myPreparedStatement.setObject( … , instant ) ; // Your JDBC driver may or may not support `Instant` directly, as it is not required by the JDBC spec.
Retrieving.
检索。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
About java.time
关于java.time
The java.timeframework is built into Java 8 and later. These classes supplant the troublesome old legacydate-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
该java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date
, Calendar
, & SimpleDateFormat
。
The Joda-Timeproject, now in maintenance mode, advises migration to the java.timeclasses.
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310。
You may exchange java.timeobjects directly with your database. Use a JDBC drivercompliant with JDBC 4.2or later. No need for strings, no need for java.sql.*
classes.
您可以直接与您的数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC 驱动程序。不需要字符串,不需要类。java.sql.*
Where to obtain the java.time classes?
从哪里获得 java.time 类?
- Java SE 8, Java SE 9, and later
- Built-in.
- Part of the standard Java API with a bundled implementation.
- Java 9 adds some minor features and fixes.
- Java SE 6and Java SE 7
- Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
- Android
- Later versions of Android bundle implementations of the java.time classes.
- For earlier Android (<26), the ThreeTenABPproject adapts ThreeTen-Backport(mentioned above). See How to use ThreeTenABP….
- Java SE 8、Java SE 9及更高版本
- 内置。
- 具有捆绑实现的标准 Java API 的一部分。
- Java 9 添加了一些小功能和修复。
- Java SE 6和Java SE 7
- 多的java.time功能后移植到Java 6和7在ThreeTen-反向移植。
- 安卓
- 更高版本的 Android 捆绑实现 java.time 类。
- 对于早期的 Android(<26),ThreeTenABP项目采用了ThreeTen-Backport(上面提到过)。请参阅如何使用ThreeTenABP ...。
The ThreeTen-Extraproject extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.
该ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如Interval
,YearWeek
,YearQuarter
,和更多。