在SQL数据库中存储约会,如Postgres,以供java.time框架使用。

huangapple go评论87阅读模式
英文:

Storing appointments in a SQL database such as Postgres for use with java.time framework

问题

假设我们在意大利米兰有一个约会,时间是在01/23/2021 21:00,使用时区为“欧洲/罗马”。这个约会以UTC时间保存在数据库中,保存在类似于SQL标准类型TIMESTAMP WITH TIME ZONE的列中。

现在,一个居住在美国纽约的用户需要了解这个约会将在何时举行。我们可以将日期时间转换为“美国/纽约”时区,或者转换为“欧洲/罗马”时区来展示给用户。一旦用户从纽约飞往米兰,他会发现这两个信息都很有用。

关键是将所有内容都转换为相同的时区参考(UTC),然后根据您使用的目标使用随现代Java捆绑的java.time框架来操作日期时间。

有道理吗,还是有什么错误/遗漏吗?

英文:

Let's say that we have an appointment in Milan Italy happening on 01/23/2021 21:00 "Europe/Rome". This appointment is saved to the database in UTC in a column of a type akin to the SQL-standard type TIMESTAMP WITH TIME ZONE.

Now a user living in New York US needs to understand when this appointment will take place. We can show that user the date-time converted to "America/New_York" time zone, or instead, in "Europe/Rome" TZ. Once the user will fly from New York to Milan, he will find both info useful.

The point is to store everything converted into the same TZ reference (UTC), and the manipulate the date-time depending on the goal you have using the java.time framework bundled with modern Java.

Makes sense or there is something wrong/missing?

答案1

得分: 7

>是否合理或存在问题/遗漏?

这取决于预约的类型。

有两种类型的预约:

  • 时刻,时间轴上的特定点,忽略对时区规则的任何更改。示例:火箭发射。
  • 日期和一天中的时间,应该调整为适应时区规则的更改。示例:医疗/牙科就诊。

时刻

如果我们正在预约火箭发射,例如,我们不关心日期和一天中的时间。我们只关心(a)天空对齐的时刻,和(b)我们预期的有利天气时刻。

如果在此期间政治家改变了我们发射地点或办公地点使用的时区规则,这对我们的发射预约没有影响。如果管理我们发射地点的政治家采用夏令时(DST),我们的发射时刻仍然相同。如果管理我们办公室的政治家决定由于与邻国的外交关系,将时钟提前半小时,我们的发射时刻仍然相同。

对于这种预约,是的,您的方法是正确的。您将使用TIMESTAMP WITH TIME ZONE类型的列在协调世界时(UTC)中记录预约。在检索时,根据用户偏好的任何时区进行调整。

诸如Postgres的数据库会使用输入中附带的任何时区信息来调整为UTC,然后丢弃该时区信息。从Postgres检索值时,它始终表示UTC中看到的日期和一天中的时间。请注意,某些工具或中间件可能具有在从数据库检索到交付给程序员的过程中应用一些默认时区的反功能。但要明确:Postgres始终以UTC保存和检索TIMESTAMP WITH TIME ZONE类型的值,始终是UTC,偏移量为零小时-分钟-秒。

以下是一些示例Java代码。

LocalDate launchDateAsSeenInRome = LocalDate.of(2021, 1, 23);
LocalTime launchTimeAsSeenInRome = LocalTime.of(21, 0);
ZoneId zoneEuropeRome = ZoneId.of("Europe/Rome");
// 将这三个部分组合在一起以确定时刻。
ZonedDateTime launchMomentAsSeenInRome = ZonedDateTime.of(launchDateAsSeenInRome, launchTimeAsSeenInRome, zoneEuropeRome);

>launchMomentAsSeenInRome.toString(): 2021-01-23T21:00+01:00[Europe/Rome]

要在UTC中看到相同的时刻,请转换为InstantInstant对象始终表示在UTC中看到的时刻。

Instant launchInstant = launchMomentAsSeenInRome.toInstant();  // 从罗马时区调整到UTC。

> launchInstant.toString(): 2021-01-23T20:00:00Z

上述字符串示例末尾的Z是UTC的标准表示法,发音为“Zulu”。

不幸的是,JDBC 4.2团队未要求支持InstantZonedDateTime类型。因此,您的JDBC驱动程序可能无法将这些对象读写到数据库中。如果不行,只需转换为OffsetDateTime。这三种类型都表示时刻,时间轴上的特定点。但是OffsetDateTime具有JDBC 4.2所需的支持,原因我不太清楚。

OffsetDateTime odtLaunchAsSeenInRome = launchMomentAsSeenInRome.toOffsetDateTime();

写入数据库。

myPreparedStatement.setObject(, odtLaunchAsSeenInRome);

从数据库检索。

OffsetDateTime launchMoment = myResultSet.getObject(, OffsetDateTime.class);

根据您的用户所需的纽约时区进行调整。

ZoneId zoneAmericaNewYork = ZoneId.of("America/New_York");
ZonedDateTime launchAsSeenInNewYork = launchMoment.atZoneSameInstant(zoneAmericaNewYork);

> launchAsSeenInNewYork.toString(): 2021-01-23T15:00-05:00[America/New_York]

您可以在IdeOne.com上查看上述所有代码的运行示例。

顺便说一下,跟踪过去的事件也被视为一个时刻。病人实际上是在什么时候到达预约,客户是什么时候支付了发票,新员工是什么时候签署了文件,服务器是什么时候崩溃的...所有这些都被跟踪为UTC中的一个时刻。如上所述,通常会是一个Instant,尽管ZonedDateTimeOffsetDateTime也表示一个时刻。对于数据库使用TIMESTAMP WITH TIME ZONE(而不是WITHOUT)。

一天中的时间

我预计大多数面向业务的应用程序关注的是另一种类型的预约,即我们针对的是具有一天中的时间的日期,而不是特定的时刻。

如果用户与其医疗保健提供者预约以查看测试结果,他们会为该日期的特定时间进行预约。如果在此期间政治家改变了他们的时区规则,将时钟向前或向后移动一小时、半小时或其他任何时间量,医疗预约的日期和一天中的时间保持不变。实际上,在政治家改变时区后,原始预约的时间轴上的点将发生变化,向时间轴的较早/较晚

英文:

>Makes sense or there is something wrong/missing?

It depends on the kind of appointment.

There are two kinds of appointments:

  • A moment, a specific point on the timeline, ignoring any changes to time zone rules.<br/>Example: Rocket launch.
  • A date and time-of-day that should adjust for changes to time zone rules.<br/>Example: Medical/Dental visit.

在SQL数据库中存储约会,如Postgres,以供java.time框架使用。

Moment

If we are booking the launch of a rocket, for example, we do not care about the date and the time-of-day. We only care about the moment when (a) the heavens align, and (b) we expect favorable weather.

If in the intervening time the politicians change the rules of the time zone in use at our launch site or at our offices, that has no effect on our launch appointment. If politicians governing our launch site adopt Daylight Saving Time (DST), the moment of our launch remains the same. If the politicians governing our offices decide to change the clock a half-hour earlier because of diplomatic relations with a neighboring country, the moment of our launch remains the same.

For such an appointment, yes, your approach would be correct. You would record the appointment in UTC using a column of type TIMESTAMP WITH TIME ZONE. Upon retrieval, adjust into any time zone the user prefers.

Databases such as Postgres use any time zone info accompanying an input to adjust into UTC, and then disposes of that time zone info. When you retrieve the value from Postgres, it will always represent a date with time-of-day as seen in UTC. Beware, some tooling or middleware may have the anti-feature of applying some default time zone between retrieval from database and delivery to you the programmer. But be clear: Postgres always saves and retrieves values of type TIMESTAMP WITH TIME ZONE in UTC, always UTC, and offset-from-UTC of zero hours-minutes-seconds.

Here is some example Java code.

LocalDate launchDateAsSeenInRome = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime launchTimeAsSeenInRome = LocalTime.of( 21 , 0 ) ;
ZoneId zoneEuropeRome = ZoneId.of( &quot;Europe/Rome&quot; ) ;
// Assemble those three parts to determine a moment.
ZonedDateTime launchMomentAsSeenInRome = ZonedDateTime.of( launchDateAsSeenInRome , launchTimeAsSeenInRome , zoneEuropeRome ) ;

>launchMomentAsSeenInRome.toString(): 2021-01-23T21:00+01:00[Europe/Rome]

To see the same moment in UTC, convert to an Instant. An Instant object always represents a moment as seen in UTC.

Instant launchInstant = launchMomentAsSeenInRome.toInstant() ;  // Adjust from Rome time zone to UTC.

> launchInstant.toString(): 2021-01-23T20:00:00Z

The Z on the end of the above string example is standard notation for UTC, and is pronounced “Zulu”.

Unfortunately the JDBC 4.2 team neglected to require support for either Instant or ZonedDateTime types. So your JDBC driver may or may not be able to read/write such objects to your database. If not, simply convert to OffsetDateTime. All three types represent a moment, a specific point on the timeline. But OffsetDateTime has support required by JDBC 4.2 for reasons that escape me.

OffsetDateTime odtLaunchAsSeenInRome = launchMomentAsSeenInRome.toOffsetDateTime() ;

Writing to database.

myPreparedStatement.setObject( … , odtLaunchAsSeenInRome ) ;

Retrieval from database.

OffsetDateTime launchMoment = myResultSet.getObject( … , OffsetDateTime.class ) ;

Adjust to the New York time zone desired by your user.

ZoneId zoneAmericaNewYork = ZoneId.of( &quot;America/New_York&quot; ) ;
ZonedDateTime launchAsSeenInNewYork = launchMoment.atZoneSameInstant( zoneAmericaNewYork ) ;

> launchAsSeenInNewYork.toString(): 2021-01-23T15:00-05:00[America/New_York]

You can see all of the above code run live at IdeOne.com.

By the way, tracking past events are also treated as a moment. When did the patient actually arrive for appointment, when did the customer pay the invoice, when did a new hire sign their documents, when did the server crash… all these are tracked as a moment in UTC. As discussed above, usually that would be an Instant, though ZonedDateTime & OffsetDateTime also represent a moment. For the database use TIMESTAMP WITH TIME ZONE (not WITHOUT).

Time-of-day

I expect most business-oriented apps are focused on appointments of the other type, where we aim at a date with a time-of-day rather than a specific moment.

If the user makes an appointment with their health care provider to review the results of a test, they do so for a particular time-of-day on that date. If in the meantime the politicians change the rules of their time zone, moving the clock ahead or behind an hour or half-hour or any other amount of time, the date and time-of-day of that medical appointment remain the same. In actuality, the point of the timeline of the original appointment will be changed after the politicians change the time zone, shifting to an earlier/later point on the timeline.

For such appointmentss, we do not store the date and time-of-day as seen in UTC. We do not use the database column type TIMESTAMP WITH TIME ZONE.

For such appointments, we store the date with time-of-day without any regard to time zone. We use a database column of type TIMESTAMP WITHOUT TIME ZONE (notice WITHOUT rather than WITH). The matching type in Java is LocalDateTime.

LocalDate medicalApptDate = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime medicalApptTime = LocalTime.of( 21 , 0 ) ;
LocalDateTime medicalApptDateTime = LocalDateTime.of( medicalApptDate , medicalApptTime ) ;

Write that to the database.

myPreparedStatement.setObject( … , medicalApptDateTime ) ;

Be clear on this: a LocalDateTime object does not represent a moment, is not a specific point on the timeline. A LocalDateTime object represents a range of possible moments along about 26-27 hours of the timeline (the range of time zones around the globe). To give real meaning to a LocalDateTime, we must associate an intended time zone.

For that intended time zone, use a second column to store the zone identifier. For example, the strings Europe/Rome or America/New_York. See list of zone names.

ZoneId zoneEuropeRome = ZoneId.of( &quot;Europe/Rome&quot; ) ;

Write that to the database as text.

myPreparedStatement.setString( … , zoneEuropeRome ) ;

Retrieval. Retrieve the zone name as text, and instantiate a ZoneId object.

LocalDateTime medicalApptDateTime = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId medicalApptZone = ZoneId.of( myResultSet.getString( … ) ) ;

Put those two pieces together to determine a moment represented as a ZonedDateTime object. Do this dynamically when you need to schedule a calendar. But do not store the moment. If the politicians redefine the time zone(s) in the future, a different moment must be calculated then.

ZonedDateTime medicalApptAsSeenInCareProviderZone = ZonedDateTime.of( medicalApptDateTime , medicalApptZone ) ;

The user is traveling to New York US. They need to know when to call the health care provider in Milan Italy according to the clocks on the wall in their temporary location of New York. So adjust from one time zone to another. Same moment, different wall-clock time.

ZoneId zoneAmericaNewYork = ZoneId.of( &quot;America/New_York&quot; ) ;
ZonedDateTime medicalApptAsSeenInNewYork = medicalApptAsSeenInCareProviderZone.withZoneSameInstant( zoneAmericaNewYork ) ;

tzdata

Be aware that if the rules of your desired time zones may be changing, you must update the copy of time zone definitions on your computers.

Java contains its own copy of the tzdata, as does the Postgres database engine. And your host operating system as well. This particular code shown here requires only Java to be up-to-date. If you use Postgres to make time zone adjustments, its tzdata must also be up-to-date. And for logging and such, your host OS should be kept up-to-date. For proper clock-watching by the user, their client machine's OS must also be up-to-date.

Beware: Politicians around the world have shown a penchant for changing their time zones with surprising frequency, and often with little forewarning.


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.

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

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

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. Hibernate 5 & JPA 2.2 support java.time.

Where to obtain the java.time classes?

答案2

得分: 0

一个非常简单的解决方案是将转换交给 PostgreSQL。如果你为每个会话正确设置了timezone参数,并且使用timestamp with time zone,PostgreSQL会自动将时间戳显示为纽约用户的纽约时间。

英文:

A very simple solution would be to leave the conversion to PostgreSQL. If you set the timezone parameter correctly for each session and use timestamp with time zone PostgreSQL will automatically show the timestamp to the New York user in New York time.

huangapple
  • 本文由 发表于 2020年10月27日 05:57:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/64545555.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定