【问题标题】:Testing correct timezone handling测试正确的时区处理
【发布时间】:2010-10-03 10:33:49
【问题描述】:

我们正在处理大量数据,所有数据都以 UTC(Java)标记。在读取这些数据、将其存储在数据库中并再次将其取出之间,发生了一些数据在夏令时关闭了一小时。由于 UTC 没有夏令时的概念,这显然是软件中的一个错误。一旦知道,就很容易修复。

但是,如果有一些单元/集成测试不管当前时差如何都能正常工作,那就太好了 - 例如我想更改本地时区并在这些不同的时区一遍又一遍地运行一些方法,以确保正确处理 UTC。

由于测试应该自动运行,并且最好在一个测试套件中运行,我想知道如何最好地测试正确的行为。在重启 JVM 时更改本地设置(如时区)很容易,但在测试套件中运行它并不容易。

有人知道支持这种场景的测试环境、库或模式吗?我们通常使用 JUnit,但如果它有助于摆脱此类问题,我们也愿意添加其他环境/技术。我想这更像是一个集成而不是单元测试。

编辑:已经有两个非常有用的答案,但我想肯定还有更多的技巧。是否有人掌握有关何时/多久调用 TimeZone.getDefault 的权威信息(请参阅 cmets 以获取 Jon Skeets 的答案)?

注意:尽管这个问题有一个可接受的答案,但我并不完全确定该接受哪个答案。即使有这种接受,我也希望看到更多的想法和技术。

感谢您的意见!

【问题讨论】:

标签: java timezone utc


【解决方案1】:

Java 允许您设置默认时区 (java.util.TimeZone.setDefault)。我之前写过测试,将时区设置为各种不同的选项,并检查一切是否仍然有效。不过要小心 - 如果您要并行化大部分单元测试,则需要使这些单元测试按顺序排列。

我建议您在某些适用夏令时的时区进行测试,而有些则不适用。使用澳大利亚时区也很好,因为 DST 在一年中的相反时间适用于北半球。

【讨论】:

  • 谢谢。我不知道我是否可以相信一遍又一遍地更改默认值,或者在 VM 的生命周期中读取默认时区时。你说我可以吗?我为时区找到的文档没有包含太多信息。 Javadoc 几乎没有这方面的信息...
  • 这取决于获取默认时区的内容。有些东西可能会缓存,但我怀疑大多数东西每次都会调用 getDefault。
  • 谢谢 - 我总是怀疑最坏的情况,尤其是当我试图依赖自动测试的结果时。也许在这种情况下我应该更乐观......
【解决方案2】:

我建议您查看JodaTime,它提供了一些糖分来帮助您在代码中更清晰地管理日期/时间/时区类型问题。

我们在整个测试和生产过程中都使用这些,因为它对日期/时间问题的原生 Java API 的提升是无与伦比的。在 JUnit 中使用这些在测试中可以正常工作

【讨论】:

【解决方案3】:

您的一小时休息问题确实很可能是由于应用了时区调整。我猜你是 (a) 与数据库交换字符串而不是对象,(b) 使用糟糕的旧日期时间类,或者 (c) 使用的工具或中间件对你从数据库中检索到的值撒谎因为在从数据库检索之后和​​向您显示/报告之前应用默认时区的令人困惑的反特性是善意的。

java.time

解决方案是始终使用现代 java.time 类,并使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。

UTC 中捕捉当前时刻。

Instant instant = Instant.now() ;  

您的 JDBC 驱动程序可能支持也可能不支持 Instant。 JDBC 4.2 规范莫名其妙地需要支持OffsetDateTime,而不是更常用的两种类型InstantZonedDateTime。没问题,我们可以轻松转换。

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

这个OffsetDateTime 仍然是UTC(零时分秒的偏移量),因为我们指定了ZoneOffset.UTC 常量。

保存到数据库。

myPreparedStatement.setObject( … , odt ) ;

检索。

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

调整时区以向用户展示。

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant​( z ) ;

为用户界面生成输出,automatically localized

Locale locale = Locale.JAPAN ;  // Specify a `Locale` to determine 
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG ).withLocale( locale ) ;
String output = zdt.format( f ) ;

请注意,在这段代码中,我们都没有使用String 作为日期时间值。如果您与数据库交换这些智能对象而不是哑字符串,您将不会注入时区调整。


关于java.time

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

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

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

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

从哪里获得 java.time 类?

【讨论】:

    猜你喜欢
    • 2016-02-26
    • 2012-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-22
    • 1970-01-01
    • 2015-10-14
    • 2022-08-13
    相关资源
    最近更新 更多