LocalDateTime到ZonedDateTime [英] LocalDateTime to ZonedDateTime

查看:101
本文介绍了LocalDateTime到ZonedDateTime的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有Java 8 Spring Web应用程序,它将支持多个区域。我需要为客户位置进行日历活动。因此,可以说我的Web和Postgres服务器托管在MST时区中(但如果我们迁移到云端,我想它可能在任何地方)。但是客户在EST中。因此,按照我阅读的一些最佳做法,我想我将所有日期时间都存储为UTC格式。数据库中的所有日期时间字段都声明为TIMESTAMP。



所以这是我采用LocalDateTime并将其转换为UTC的方法:

  ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getStartDateTime(),ZoneOffset.UTC,null); 
//约会startDateTime是LocalDateTime
约会。setStartDateTime(startZonedDT.toLocalDateTime());

现在,例如,当搜索日期时,我必须从请求的日期转换将时间设置为UTC,获取结果并转换为用户时区(存储在数据库中):

  ZoneId userTimeZone = ZoneId。的(timeZone.getID()); 
ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(appointment.getStartDateTime(),userTimeZone,null);
dto.setStartDateTime(startZonedDT.toLocalDateTime());

现在,我不确定这是正确的方法。我也想知道是否因为我从LocalDateTime转到ZonedDateTime,反之亦然,所以我可能会丢失任何时区信息。



这是我所看到的,但没有对我来说似乎正确。当我从UI收到LocalDateTime时,得到以下信息:

  2016-04-04T08:00 

ZonedDateTime =

  dateTime = 2016-04-04T08:00 
offset = Z
zone = Z

然后将转换后的值分配给我的约会LocalDateTime时存储:

  2016-04-04T08 :00 

我感觉是因为我存储在LocalDateTime中,所以我失去了转换为ZonedDateTime的时区



我应该让我的实体(约会)使用ZonedDateTime而不是LocalDateTime,以便Postgres不会丢失该信息吗?



---------------- 编辑 ----------------



在Basils给出了很好的答案之后,我意识到我非常关心用户的时区-所有约会都针对特定位置,因此我可以将所有日期时间存储为UTC,然后进行转换检索到位置时区。我做了以下跟进






关于 java.time





ThreeTen-Extra 项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如 时间间隔 YearWeek YearQuarter 更多


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.

So here is how I take a LocalDateTime and convert to 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):

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.

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:

2016-04-04T08:00

The ZonedDateTime =

dateTime=2016-04-04T08:00
offset="Z"
zone="Z"

And then when I assign that converted value to my appointment LocalDateTime I store:

2016-04-04T08:00

I feel like because I am storing in LocalDateTime I am losing the timezone that I converted into ZonedDateTime.

Should I make my entity (appointment) use ZonedDateTime instead of LocalDateTime so that Postgres doesn't loose that information?

---------------- 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

解决方案

Postgres has no such data type as TIMESTAMP. Postgres has two types for 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.

  • The WITH type uses 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 type ignores any offset or zone info that may be present.
    • This type does not represent a moment. It represents a vague idea of potential moments along a range of about 26-27 hours (the range of time zones around the globe).

You virtually always want the WITH type, as explained here by 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 any time 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).

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 not be 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).

I also wonder if because I am going from LocalDateTime to ZonedDateTime and vice versa, I might be losing any timezone info.

Indeed you are. The entire point to LocalDateTime is to lose time 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.

The word "Local" in LocalDateTime may be counter-intuitive as it means no particular locality. 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".

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.)

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.

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.

On the Java side, use java.time.Instant (a moment on the timeline in UTC) in much of your business logic.

Instant now = Instant.now();

Hopefully JDBC drivers 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.

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.

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.

To present to user, apply a time zone. Use proper time zone names, never the 3-4 letter codes such as EST or 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 instant = zdt.toInstant();

No where in there did we use 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.

If you do get that 2016-04-04T08:00 value, and you are absolutely certain of the intended offset/zone context, then:

  1. Parse that string as a LocalDateTime.
  2. Apply an offset-from-UTC to get a OffsetDateTime, or (better) apply a time zone to get a 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.

JDBC 4.2

As of JDBC 4.2 we can directly exchange java.time objects with the database. No need to ever use java.sql.Timestamp again, nor its related classes.

Storing, using OffsetDateTime as defined in the JDBC spec.

myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ;  // The JDBC spec requires support for `OffsetDateTime`. 

…or possibly use Instant directly, if supported by your JDBC driver.

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

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project 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.

这篇关于LocalDateTime到ZonedDateTime的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆