2

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

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

10 月 27 日上午 6 点至 10 月 28 日上午 6 点。在此期间执行从 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();

但在这种情况下,结果 startDate 是 CEST 10 月 27 日 9:00,endDate 是 CET 10 月 28 日 8:00,所以即使在我们这边测试也失败了。

先感谢您。

4

3 回答 3

8

指定时区

我们如何能够独立于机器的时区设计我们的单元测试?

始终指定时区

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

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

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

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

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

相反,请使用正确的时区名称。大多数名称是大陆和时区主要城市的组合,用纯 ASCII 字符(不带变音符号)编写。例如,Europe/Chisinau

Joda-Time中,那将是……</p>

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

思考/使用 UTC

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

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

24 小时与 1 天

所以,如果你真的想要 24 小时后,那就加上 24 小时,让挂钟时间下降到可能的地方。如果您想要“第二天”,这意味着用户希望在时钟上看到相同的时间,然后添加 1 天(结果可能是 25 小时后)。Joda-Time 支持这两种逻辑。

请注意下面的示例代码,Joda-Time 支持以 24 小时为单位计算一天,或者由于夏令时或其他异常情况而调整时间以匹配相同的挂钟时间。

示例代码

这是一些使用 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 );

转储到控制台...</p>

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 );

运行时……</p>

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
于 2014-03-24T06:50:22.620 回答
6

据我了解您的问题,您希望在服务器上设置一个时区,以测试白天时间切换期间的正确时间,而不是使用标准服务器时区。为此,时区需要能够使用夏令时偏移。

您在示例中的服务器上使用的时区

TimeZone zone = TimeZone.getTimeZone("GMT");

不使用夏令时:zone.useDaylightTime()返回false.

尝试使用特定的时区,例如

TimeZone zone = TimeZone.getTimeZone("Europe/Chisinau");

有夏令时偏移。

您还可以在 Joda Time 的 DateTime 类中使用时区,并避免使用 Java 的 Calendar 和 Date 类。

于 2012-10-26T20:52:16.997 回答
-2

为什么不使用普通的 java.util.Calendar?

    final Calendar startTime = new GregorianCalendar( 2012, Calendar.OCTOBER, 27, 2, 12, 0 );
    startTime.setTimeZone( TimeZone.getTimeZone( strTimeZone ) );
    System.out.println( startTime.getTimeInMillis() );
于 2012-10-26T05:59:55.223 回答