使用Java / Postgres处理DateTime和时区

时间:2018-07-31 16:54:29

标签: java postgresql date timezone scheduled-tasks

我正在创建一个应用程序,让用户创建一个带有日期的事件。例如-生日,2018年9月25日。 (仅询问用户日期)。我想将这些事件对象存储在我的Postgres数据库中。

在该日期结束时(意味着2018年9月25日23:59:59),我希望我的应用程序在事件expired = true上设置标志。

我的最初想法是每隔一小时运行一次Scheduler,检查事件记录,如果当前服务器时间大于存储的事件时间,请设置该标志。 因此,我的第一个问题是: 调度程序创意是最好的方法吗?还是性能可能有些差,实际上有更好的方法可以做到?我想每小时运行一次的原因是由于不同的时区。总是有23:59:59某个地方。

其他问题更侧重于存储和检查日期信息本身。如何处理时区?假设来自美国的用户在给定日期添加了Birthday事件,而我的服务器位于欧洲。 如何将其存储在PostgresDB中以及如何在调度程序中执行检查?我需要以某种方式将时区存储在数据库中,然后在sscheduler中检查在给定时区中是否是给定服务器时间的该日期为23:59:59。

请帮助,我对此感到非常困惑。一个例子将是惊人的!谢谢!

2 个答案:

答案 0 :(得分:3)

如果您希望能够按时区使数据过期,则必须存储用户的时区。我的建议是不要在行过期时更改行,而是在查询时计算行是否过期:

SELECT e.event_date::timestamp
       AT TIME ZONE u.timezone
       <= current_timestamp - INTERVAL '1 day' AS expired
FROM event e JOIN user u ON ...

答案 1 :(得分:1)

Answer by Albe在我看来是正确的。这是我的一些进一步的想法。

  

我希望我的应用程序在事件expired = true上设置一个标志。

就像阿尔伯提到的那样,没有必要记录这个过期的事实。只需按日期查询即可查找日期(过期)之前或日期之后(未过期)的记录。

如果要考虑性能,请进行测试以进行验证。如果有问题,请考虑将日期列编入索引。

  

如何处理时区?

那是诀窍。我会在这里简单介绍一下,因为此操作已在此处和其姊妹站点https://dba.stackexchange.com中进行了很多次处理。

首先,请弄清楚您的业务需求是否需要日期或时间间隔。当您直觉时,日期是不精确的。在任何给定时刻,日期都会随时区在全球范围内变化。在巴黎午夜过后几分钟,法国是新的一天,而在魁北克蒙特利尔仍然是“昨天”。

如果您关心时刻,请使用类似于SQL标准TIMESTAMP WITH TIME ZONE的类型的列。理解SQL标准几乎没有涉及日期时间处理的主题。因此,不同数据库系统中的行为差异很大。在Postgres中,该列中的值始终为UTC。输入随附的任何时区或UTC偏移量信息均用于调整为UTC,然后丢弃。因此,如果您关心原始的区域/偏移,则必须手动将其存储在其他列中。

检索时,该列中的值始终为UTC。但是,您使用的访问工具可能会就如何呈现该价值提出自己的意见。一些工具会动态应用时区。我发现这种行为是不幸的设计选择。尽管有很好的意图,但这种行为会造成该区域是存储数据一部分的错觉。

在Java中,仅使用现代的 java.time 类,而不使用与最早的Java版本捆绑在一起的非常麻烦且有缺陷的旧日期时间类。

从JDBC 4.2开始,您可以使用PreparedStatement::setObjectResultSet::getObject与数据库直接交换 java.time 对象。

  

我的服务器位于欧洲

服务器的位置以及服务器OS和JVM的当前默认时区应与编程和数据库工作无关。永远不要依赖那些默认设置,因为它们不受您的控制,并且可以在运行时 随时更改。

相反,请始终明确指定所需/期望的时区。通常,您的大部分工作应使用UTC。仅在业务规则要求或向用户展示时才调整为时区。

LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 23 ) ;
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ld.atStartOfDay( z ) ;

通过提取Instant调整回UTC。 Instant是您应该与数据库交换的内容。

Instant instant = zdt.toInstant() ;

调整回一个区域。

ZonedDateTime zdtAuckland = instant.atZone( ZoneId.of( "Pacific/Auckland" ) ;  // Same moment, same point on the timeline, different wall-clock time.

要查询时刻,请搜索“堆栈溢出”以了解半开放方法,其中定义了时间范围,其中开始时间为包含,而结束时间为排他。这意味着您 使用SQL运算符BETWEEN。例如,请参见this

提示:使用符合site:stackoverflow.com标准的搜索引擎搜索这些类名和概念。不幸的是,Stack Overflow中内置的搜索功能偏向问题,而忽略了答案。例如:https://duckduckgo.com/?q=site%3Astackoverflow.com+%2B%22Half-Open%22+%2Bjava&t=osx&ia=web


关于 java.time

java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendarSimpleDateFormat

目前位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*类。

在哪里获取java.time类?

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

相关问题