Joda-Time,夏令时计算,时区独立测试
我们在应用程序中使用固定时间段。 当用户添加新的时段时,默认情况下应该是第二天的上午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.
我们遇到了这个问题并尝试编写unit testing以防止它再次出现。 测试在我们的计算机上成功通过,但在CI服务器上失败(它在另一个时区)。
问题是:我们怎样才能独立于机器的时区设计我们的unit testing?
目前,小时跨度的计算是使用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服务器上失败的unit testing:
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是10月27日9:00 CEST,endDate是10月28日8:00 CET,所以测试甚至不在我们这边。
先谢谢你。
据我了解您的问题,您希望在服务器上设置一个时区,以便在白天时间切换期间测试正确的小时数,而不是使用标准服务器时区。 为此,时区需要能够使用夏令时偏移。
您在示例中在服务器上使用的时区
TimeZone zone = TimeZone.getTimeZone("GMT");
不使用日光时间: zone.useDaylightTime()
返回false
。
请尝试使用特定的时区,例如
TimeZone zone = TimeZone.getTimeZone("Europe/Chisinau");
有夏令时抵消。
您还可以在Joda Time的DateTime类中使用时区,并避免使用Java的Calendar和Date类。
指定时区
我们怎样才能独立于机器的时区设计我们的unit testing?
始终指定时区 。
几乎总是,您的代码应指定时区。 仅当您真正需要用户/服务器的默认时区时才省略时区; 即使这样,您也应该明确访问默认时区以使代码自我记录。
如果省略时区,将使用JVM的默认时区。
Joda-Time有多个地方可以传递DateTimeZone
对象或者调用withZone
方法。
避免3-4个字母时区代码
避免使用CEST
和CET
等时区代码。 它们既不标准也不独特。 当混淆/忽略夏令时(DST)时,它们经常被错误地使用。
而是使用适当的时区名称 。 大多数名称是时区区域的大陆和主要城市的组合,用纯ASCII字符(没有变音符号)编写。 例如, Europe/Chisinau
。
在Joda-Time ,那将是……
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Chisinau" );
思考/使用UTC
您真正的问题是考虑本地日期时间值,或尝试使用本地时间执行业务逻辑。 这样做非常困难,因为一般的夏令时及其频繁的变化以及其他exception情况。
相反,要考虑宇宙的时间线而不是壁钟时间。 尽可能将日期时间值存储和处理为UTC (GMT)值。 转换为本地分区时间以呈现给用户(就像您将本地化字符串一样)以及用于业务目的所需的位置,例如确定由特定时区定义的“日期”的开始。
24小时与1天
所以,如果你真的想要24小时之后,那就加上24小时,让挂钟时间降到最低点。 如果您想要“第二天”,意味着用户希望在时钟上看到相同的时间,那么添加1天(可能会在25小时之后)。 Joda-Time支持两种逻辑。
请注意下面的示例代码,Joda-Time支持计算一天24小时或通过调整时间来匹配相同的挂钟时间,因为夏令时或其他exception。
示例代码
以下是使用Joda-Time 2.3的一些示例代码。 Java 8中的新java.time包应该具有与Joda-Time相似的function。
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.util.Calendar?
final Calendar startTime = new GregorianCalendar( 2012, Calendar.OCTOBER, 27, 2, 12, 0 ); startTime.setTimeZone( TimeZone.getTimeZone( strTimeZone ) ); System.out.println( startTime.getTimeInMillis() );