Joda-Time 处于维护模式
自从您提出问题后,Joda-Time 的创建者继续领导 JSR 310 为 Java 带来了一组新的日期时间类。这些类在 Java 8 中出现。
从那时起,Joda-Time项目进入维护模式,项目建议迁移到java.time。
比较时区需要一点时间
在不输入任何日期时间的情况下获取两个时区之间的时差。
这没有任何意义。您必须指定一个时刻,即时间线上的一个点,以比较两个不同时区的有效偏移量。
了解offset 仅比计时本初子午线UTC 提前或落后几个小时-分钟-秒。
time zone 更多。时区是过去、现在和未来对特定地区人民使用的偏移量变化的历史记录,这些变化由政治家决定。
根据定义,对于任何特定时区,使用的偏移量都会随时间而变化。因此,在没有特定时间点的情况下尝试比较时区是没有意义的。
我现在正在做的是从一个时区创建一个临时日期(午夜),然后转换为 utz,然后再次转换为另一个时区,然后计算两者的持续时间。
我不确定你的目标,但这里是做类似事情的代码。
指定您感兴趣的一对时区。
ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
ZoneId zEdmonton = ZoneId.of( "America/Edmonton" ) ;
指定日期。
LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 ) ;
确定该日期在东京时区的开始时间。不要假设一天从 00:00 开始。某些地区的某些日期可能开始于不同的时间。让 java.time 确定一天中的第一个时刻。
ZonedDateTime firstMomentOfDayTokyo = ld.atStartOfDay( zTokyo ) ;
调整一下,看看加拿大艾伯塔省埃德蒙顿挂钟时间的某个时刻。
ZonedDateTime zdtEdmonton = firstMomentOfDayTokyo.atZoneSameInstant( zEdmonton ) ;
通过提取Instant 对象以零偏移量再次查看同一时刻。
Instant instant = firstMomentOfDayTokyo.toInstant() ;
这是要理解的关键点:firstMomentOfDayTokyo、zdtEdmonton 和instant 都代表同一时刻,即时间线上的同时同一点。想象一下来自日本、加拿大和冰岛(他们的时区始终为零偏移)的电话会议中的三个人。如果他们都同时抬头看挂在各自墙上的时钟,他们都会看到不同的当地时间,但同时经历相同的时刻。
现在我们手头有一个 Instant 对象,我们可以着手解决您的挑战:获取这两个时区中每个时区的使用偏移量。
获取每个的时区规则。
ZoneRules rulesTokyo = zTokyo.getRules() ;
ZoneRules rulesEdmonton = zEdmonton.getRules() ;
获取此时每个区域中有效的偏移量。注意这里使用ZoneOffset 类而不是ZoneId。
ZoneOffset offsetTokyo = rulesTokyo.getOffset( instant ) ;
ZoneOffset offsetEdmonton = rulesEdmonton.getOffset( instant ) ;
计算每个区域之间的差异。
Duration d = Duration.ofSeconds( offsetTokyo.getTotalSeconds() - offsetEdmonton.getTotalSeconds() ) ;
但时区之间的这种偏移比较不是解决您的潜在问题的最佳方法。继续阅读以获得更好的解决方案。
查询数据库一天
你说:
服务器处于差异状态。时区
您应该始终以不受服务器当前默认时区影响的方式进行 Java 编程。始终为各种与日期时间相关的 Java 方法指定其他可选的时区(或偏移量)参数。
以UTC格式保存在数据库中
很好。通常最好在适应 UTC 后存储时刻。一些关系数据库(例如 Postgres)在接收到类似于 SQL 标准类型TIMESTAMP WITH TIME ZONE 的类型的列的输入时会进行这样的调整。
在 JDBC 4.2 及更高版本中使用 java.time 类检索此类值。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
将这样的值写入数据库。
myPreparedStatement.setObject( … , odt ) ;
你说:
当客户端请求某个日期的交易历史时会出现问题。看来这不是将请求日期(客户端时区)简单转换为utc,我需要将服务器时区和客户端时区的时差添加到转换后的utc时间才能得到正确的日期。
不,不是最好的方法。您正在考虑当地时区。
当您以程序员、DBA 或系统管理员的身份工作时,您应该忘记您的本地时区。学会用 UTC 来思考,偏移量为零。日志记录、数据存储、数据交换和大部分业务逻辑都应该在 UTC 中完成。将桌面上的第二个时钟设置为 UTC;我是认真的,你的生活会更轻松。
例如,假设您的用户想要查询所有记录,其时间戳发生在日本东京一整天中的某个时间点。
要查询一天的记录,与我们在上面看到的大致相同。我们在东京度过了一天的第一刻。
ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 ) ;
ZonedDateTime firstMomentOfDayTokyo = ld.atStartOfDay( zTokyo ) ;
这是我们要查询的时间跨度(一天)的开始。
通常最好使用半开方法来定义时间跨度。在这种方法中,开头是inclusive,而结尾是exclusive。因此,一天从日期的第一刻开始,一直到(但不包括)下一个日期的第一刻。
ZonedDateTime firstMomentOfFollowingDayTokyo = ld.plusDays( 1 ).atStartOfDay( zTokyo ) ;
OffsetDateTime 是映射到标准 SQL 中定义的 TIMESTAMP WITH TIME ZONE 类型的类。所以从每个中提取一个OffsetDateTime。
OffsetDateTime start = firstMomentOfDayTokyo.toOffsetDateTime() ;
OffsetDateTime end = firstMomentOfFollowingDayTokyo.toOffsetDateTime() ;
这样编写你的 SQL:
SELECT *
FROM event_
WHERE when_ >= ?
AND when_ < ?
;
不要使用 SQL 命令BETWEEN 进行这项工作。该命令是完全关闭的而不是半打开的。
在 JDBC 中:
myPreparedStatement.setObject( 1 , start ) ;
myPreparedStatement.setObject( 2 , end ) ;
请记住,特定时区的一整天不一定是 24 小时。由于政治时间异常,例如Daylight Saving Time (DST),一天可能是 23、23.5、25 或其他一些小时数。