java - Joda-Time,夏令时计算,时区独立测试

标签 java jodatime dst period

我们在应用程序中使用固定时间段。当用户添加新的时间段时,默认应该是早上 6:00 到次日早上 6:00。

通常是 24 小时,但存在一个问题:执行夏令时更改时,该时段的长度会发生变化。例如:

10 月 27 日上午 6:00 至 10 月 28 日上午 6:00。在此期间执行从 CEST 到 CET 时区的更改。因此,这段时间包含 25 个小时:

From 27 October 6:00 AM to 28 October 3:00 AM - there are 21 hours
at 3:00 am the time is shifted back by 1 hour, so there are 4 hours until 28 October 6:00 AM.

我们遇到了这个问题,并尝试编写一个单元测试来防止它再次出现。测试在我们的机器上成功通过,但在 CI 服务器上失败(它在另一个时区)。

问题是:我们如何才能独立于机器的时区来设计我们的单元测试?

目前,小时跨度的计算是使用Joda-Time计算的:

    if ((aStartDate == null) || (aEndDate == null)) {
        return 0;
    }
    final DateTime startDate = new DateTime(aStartDate);
    final DateTime endDate = new DateTime(aEndDate);
    return Hours.hoursBetween(startDate, endDate).getHours();

在我们这边通过但在 CI 服务器上失败的单元测试:

    Calendar calendar = Calendar.getInstance();
    calendar.set(2012, Calendar.OCTOBER, 27, 6, 0);
    startDate= calendar.getTime();
    calendar.set(2012, Calendar.OCTOBER, 28, 6, 0);
    endDate= calendar.getTime();

我们尝试为日历使用时区:

    TimeZone.setDefault(TimeZone.getTimeZone(TimeZone.getTimeZone("GMT").getID()));
    Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(TimeZone.getTimeZone("GMT").getID()));
    calendar.set(2012, Calendar.OCTOBER, 27, 6, 0, 0);
    startDate= calendar.getTime();
    calendar.set(2012, Calendar.OCTOBER, 28, 6, 0, 0);
    endDate= calendar.getTime();

但在这种情况下,结果开始日期是 10 月 27 日 9:00 CEST,结束日期是 10 月 28 日 8:00 CET,因此即使在我们这边测试也失败了。

提前谢谢你。

最佳答案

指定时区

how can we possible design our unit test independently from the machine's time zone ?

始终指定时区

几乎总是,您的代码应该指定一个时区。仅当您确实需要用户/服务器的默认时区时才省略时区;即使那样,您也应该明确访问默认时区以使您的代码 self 记录。

如果省略时区,将使用 JVM 的默认时区。

Joda-Time 有很多地方可以传递 DateTimeZone对象或调用 withZone 方法。

避免使用 3-4 个字母的时区代码

避免使用时区代码,例如 CESTCET。它们既不是标准化的也不是唯一的。在混淆/忽略夏令时 (DST) 时,它们经常被误用。

相反,使用 proper time zone names .大多数名称是大陆和时区主要城市的组合,以纯 ASCII 字符(无变音符号)书写。例如,Europe/Chisinau .

Joda-Time ,那就是……

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Chisinau" );

考虑/使用 UTC

您真正的问题是根据本地日期时间值进行思考,或者尝试使用本地时间执行业务逻辑。这样做非常困难,因为通常采用夏令时及其频繁变化以及其他异常情况。

相反,请根据宇宙的时间线而不是挂钟时间来思考。将日期时间值存储和处理为 UTC (GMT) 值尽可能。转换为本地分区时间以呈现给用户(就像本地化字符串一样)以及出于业务目的需要的地方,例如确定特定时区定义的“一天”的开始。

24 小时与 1 天

因此,如果您真的想要晚 24 小时,则加上 24 小时并让挂钟时间落在可能的位置。如果您想要“第二天”,这意味着用户希望在时钟上看到相同的时间,则添加 1 天(结果可能是 25 小时后)。 Joda-Time 支持这两种逻辑。

请注意下面的示例代码,Joda-Time 支持按 24 小时计算一天或通过调整时间以匹配相同的 wall-clock time由于夏令时或其他异常。

示例代码

这是一些使用 Joda-Time 2.3 的示例代码。 Java 8 中新的 java.time 包应该具有类似的功能,因为它受到了 Joda-Time 的启发。

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Chisinau" );
DateTime start = new DateTime( 2012, 10, 27, 6, 0, 0, timeZone );
DateTime stop = start.plusHours( 24 ); // Time will be an hour off because of Daylight Saving Time (DST) anomaly.

DateTime nextDay = start.plusDays( 1 ); // A different behavior (25 hours).

Interval interval = new Interval( start, stop );
Period period = new Period( start, stop );
int hoursBetween = Hours.hoursBetween( start, stop ).getHours();

DateTime startUtc = start.withZone( DateTimeZone.UTC );
DateTime stopUtc = stop.withZone( DateTimeZone.UTC );

转储到控制台...

System.out.println( "start: " + start );
System.out.println( "stop: " + stop );
System.out.println( "nextDay: " + nextDay );
System.out.println( "interval: " + interval );
System.out.println( "period: " + period );
System.out.println( "hoursBetween: " + hoursBetween );
System.out.println( "startUtc: " + startUtc );
System.out.println( "stopUtc: " + stopUtc );

运行时...

start: 2012-10-27T06:00:00.000+03:00
stop: 2012-10-28T05:00:00.000+02:00
nextDay: 2012-10-28T06:00:00.000+02:00
interval: 2012-10-27T06:00:00.000+03:00/2012-10-28T05:00:00.000+02:00
period: PT24H
hoursBetween: 24
startUtc: 2012-10-27T03:00:00.000Z
stopUtc: 2012-10-28T03:00:00.000Z

关于java - Joda-Time,夏令时计算,时区独立测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13081411/

相关文章:

java - 返回空的不可变集合而不是分配一个新的空不可变集合有什么缺点?

java - 两个 joda 日期之间的天数,包括假期

Oracle 日期比较因 DST 而损坏

java - 为什么 inDaylightTime() 方法在时区中返回错误?

使用枚举、方法和用户输入更新的 Java Suit 应用程序

java - Cloudflare 免费 SSL ConnectException

java - 使用 ProGuard 将字节码转换为 dex 时出错

java - 将 2 个日期与一种格式结合起来?

java - 计算相对于没有夏令时的时区的正确时间

c++ - 如何使用 Windows API 更改时区设置