我们在应用程序中使用固定时间段。当用户添加新的时间段时,默认应该是早上 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 个字母的时区代码
避免使用时区代码,例如 CEST
和 CET
。它们既不是标准化的也不是唯一的。在混淆/忽略夏令时 (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/