Bas*_*que 5 java database postgresql timezone java-time
假设我们在 01/23/2021 21:00 "Europe/Rome"有一个在意大利米兰的约会。此约会以 UTC 格式保存到数据库中类似于 SQL 标准类型的列中TIMESTAMP WITH TIME ZONE。
现在,居住在美国纽约的用户需要了解此约会何时进行。我们可以向用户显示转换为“America/New_York”时区的日期时间,或者改为“Europe/Rome”TZ。一旦用户从纽约飞往米兰,他就会发现这两个信息都很有用。
关键是存储转换为相同 TZ 引用 (UTC) 的所有内容,并根据您使用与现代 Java 捆绑的java.time框架的目标来操作日期时间。
有道理还是有什么问题/遗漏?
有道理还是有什么问题/遗漏?
这取决于约会的类型。
有两种类型的约会:
例如,如果我们预订火箭发射,我们并不关心日期和时间。我们只关心 (a) 天空对齐的时刻,以及 (b) 我们期待有利天气的时刻。
如果在此期间政客改变了我们发射场或办公室使用的时区规则,这对我们的发射预约没有影响。如果管理我们发射场的政客采用夏令时 (DST),我们的发射时刻将保持不变。如果管理我们办公室的政客因为与邻国的外交关系决定提前半小时更改时钟,我们的发射时刻保持不变。
对于这样的约会,是的,您的方法是正确的。您将使用类型为 的列以UTC记录约会TIMESTAMP WITH TIME ZONE。检索后,调整到用户喜欢的任何时区。
诸如Postgres 之类的数据库使用伴随输入的任何时区信息来调整为 UTC,然后处理该时区信息。当您从 Postgres 检索值时,它将始终表示一个日期,如 UTC 中所见。请注意,某些工具或中间件可能具有在从数据库检索和交付给程序员之间应用某些默认时区的反特性。但要清楚:Postgres 总是保存和检索TIMESTAMP WITH TIME ZONEUTC类型的值,总是 UTC,以及零时分秒的 UTC 偏移量。
下面是一些示例 Java 代码。
LocalDate launchDateAsSeenInRome = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime launchTimeAsSeenInRome = LocalTime.of( 21 , 0 ) ;
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
// Assemble those three parts to determine a moment.
ZonedDateTime launchMomentAsSeenInRome = ZonedDateTime.of( launchDateAsSeenInRome , launchTimeAsSeenInRome , zoneEuropeRome ) ;
Run Code Online (Sandbox Code Playgroud)
launchMomentAsSeenInRome.toString(): 2021-01-23T21:00+01:00[欧洲/罗马]
要在 UTC 中查看同一时刻,请转换为Instant. 一个Instant对象总是代表在 UTC 中看到的一个时刻。
Instant launchInstant = launchMomentAsSeenInRome.toInstant() ; // Adjust from Rome time zone to UTC.
Run Code Online (Sandbox Code Playgroud)
launchInstant.toString(): 2021-01-23T20:00:00Z
在Z上述串例子的端部为UTC标准表示法,并且被读作“祖鲁”。
不幸的是,JDBC 4.2 团队忽略了需要支持Instant或ZonedDateTime类型。因此,您的JDBC 驱动程序可能会也可能不会将此类对象读/写到您的数据库中。如果没有,只需转换为OffsetDateTime. 所有三种类型都代表一个时刻,时间轴上的一个特定点。但是由于我无法理解的原因,它OffsetDateTime具有JDBC 4.2所需的支持。
OffsetDateTime odtLaunchAsSeenInRome = launchMomentAsSeenInRome.toOffsetDateTime() ;
Run Code Online (Sandbox Code Playgroud)
写入数据库。
myPreparedStatement.setObject( … , odtLaunchAsSeenInRome ) ;
Run Code Online (Sandbox Code Playgroud)
从数据库中检索。
OffsetDateTime launchMoment = myResultSet.getObject( … , OffsetDateTime.class ) ;
Run Code Online (Sandbox Code Playgroud)
调整到您的用户所需的纽约时区。
ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime launchAsSeenInNewYork = launchMoment.atZoneSameInstant( zoneAmericaNewYork ) ;
Run Code Online (Sandbox Code Playgroud)
launchAsSeenInNewYork.toString(): 2021-01-23T15:00-05:00[America/New_York]
您可以在 IdeOne.com 上实时查看上述所有代码。
顺便说一下,跟踪过去的事件也被视为一个时刻。患者何时实际到达预约,客户何时支付发票,新员工何时签署他们的文件,服务器何时崩溃......所有这些都被作为UTC中的时刻进行跟踪。如上所述,通常这将是一个Instant,虽然ZonedDateTime&OffsetDateTime也代表一个时刻。对于数据库使用TIMESTAMP WITH TIME ZONE(不是WITHOUT)。
我希望大多数面向业务的应用程序都专注于其他类型的约会,我们的目标是具有一天中的某个时间而不是特定时刻的约会。
如果用户与他们的医疗保健提供者预约以查看测试结果,他们会在该日期的特定时间这样做。如果在此期间政客改变他们的时区规则,将时钟向前或向后移动一小时或半小时或任何其他时间量,则该医疗预约的日期和时间保持不变。实际上,政客更改时区后,原始任命的时间线点将更改,移到时间线上的较早/较晚点。
对于此类约会,我们不会存储以 UTC 显示的日期和时间。我们不使用数据库列类型TIMESTAMP WITH TIME ZONE。
对于此类约会,我们将日期与时间存储在一起,而不考虑时区。我们使用类型为TIMESTAMP WITHOUT TIME ZONE(noticeWITHOUT而不是WITH)的数据库列。Java 中的匹配类型是LocalDateTime.
LocalDate medicalApptDate = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime medicalApptTime = LocalTime.of( 21 , 0 ) ;
LocalDateTime medicalApptDateTime = LocalDateTime.of( medicalApptDate , medicalApptTime ) ;
Run Code Online (Sandbox Code Playgroud)
将其写入数据库。
myPreparedStatement.setObject( … , medicalApptDateTime ) ;
Run Code Online (Sandbox Code Playgroud)
要明确这一点:一个LocalDateTime对象并不能代表一个时刻,是不是在时间轴上的特定点。一个LocalDateTime对象代表了大约 26-27 小时时间线(全球时区范围)的一系列可能时刻。为了赋予 a 真正的意义LocalDateTime,我们必须关联一个预期的时区。
对于该预期时区,使用第二列来存储区域标识符。例如,字符串Europe/Rome或America/New_York. 请参阅区域名称列表。
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
Run Code Online (Sandbox Code Playgroud)
将其作为文本写入数据库。
myPreparedStatement.setString( … , zoneEuropeRome ) ;
Run Code Online (Sandbox Code Playgroud)
恢复。以文本形式检索区域名称,并实例化一个ZoneId对象。
LocalDateTime medicalApptDateTime = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId medicalApptZone = ZoneId.of( myResultSet.getString( … ) ) ;
Run Code Online (Sandbox Code Playgroud)
将这两个部分放在一起以确定表示为ZonedDateTime对象的时刻。当您需要安排日历时动态执行此操作。但千万不能存储的时刻。如果政治家在未来重新定义时区,则必须计算不同的时刻。
ZonedDateTime medicalApptAsSeenInCareProviderZone = ZonedDateTime.of( medicalApptDateTime , medicalApptZone ) ;
Run Code Online (Sandbox Code Playgroud)
用户正在前往美国纽约。他们需要根据纽约临时住所墙上的时钟,知道何时给意大利米兰的医疗保健提供者打电话。所以从一个时区调整到另一个时区。同一时刻,不同的挂钟时间。
ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime medicalApptAsSeenInNewYork = medicalApptAsSeenInCareProviderZone.withZoneSameInstant( zoneAmericaNewYork ) ;
Run Code Online (Sandbox Code Playgroud)
请注意,如果所需时区的规则可能会发生变化,则必须更新计算机上时区定义的副本。
Java 包含它自己的tzdata副本,Postgres 数据库引擎也是如此。以及您的主机操作系统。此处显示的此特定代码只需要 Java 是最新的。如果您使用 Postgres 进行时区调整,则其tzdata也必须是最新的。对于日志记录等,您的主机操作系统应该保持最新。为了用户正确观看时钟,他们的客户端机器的操作系统也必须是最新的。
当心:世界各地的政客都表现出以惊人的频率改变时区的倾向,而且往往几乎没有预先警告。
该java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat。
要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310。
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
您可以直接与您的数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC 驱动程序。不需要字符串,不需要类。Hibernate 5 & JPA 2.2 支持java.time。java.sql.*
从哪里获得 java.time 类?
| 归档时间: |
|
| 查看次数: |
221 次 |
| 最近记录: |