如何在Android单元测试中设置SimpleDateFormat的时区



我想测试Date实用程序类中的一个方法,该类从String中获取Date。传递的字符串是1980-03-26T00:00:00.000+0200,我想将结果日期与assertEquals进行比较。测试失败,输出为:

org.junit.ComparisonFailure: 
Expected :Wed Mar 26 00:00:00 PST 1980
Actual   :Wed Mar 26 00:00:00 SGT 1980

这是我的测试:

INITIAL_DATA_DATE_FROM_STRING = "1980-03-26T00:00:00.000+0200";
EXPECTED_DATA_DATE_FROM_STRING = "Wed Mar 26 00:00:00 PST 1980";
// inside the method ...
date = DateUtils.getDateFromString(INITIAL_DATA_DATE_FROM_STRING);
assertEquals(EXPECTED_DATA_DATE_FROM_STRING, String.valueOf(date));

这是我正在测试的方法:

public static Date getDateFromString(String dateAsString) {
return getDateFromString(dateAsString, "dd/MM/yyyy");
}
public static Date getDateFromString(String dateAsString, String dateFormat) {
Date formattedDate = null;
if (StringUtils.notNull(dateAsString)) {
DateFormat format = new SimpleDateFormat(dateFormat);
try {
formattedDate = parseString(dateAsString, format);
} catch (ParseException e) {
try {
formattedDate = parseString(dateAsString, new SimpleDateFormat("yyyy-MM-dd"));
} catch (ParseException e1) {
// handle exception code
}
}
}
return formattedDate;
}

因此,单元测试不是独立于时区的。有没有为单元测试设置默认时区?

java.time或Joda time

DateSimpleDateFormat类存在设计问题,因此请考虑不使用它们。你的问题的第一个版本被标记为jodatime,如果你在项目中使用Joda Time,那已经是一个相当大的改进了。Joda Time为您提供了所需的所有功能,因此您的方法可能应该从Joda Time返回近似类型,而不是老式的Date

然而,Joda-Time也即将退休,它的继任者是现代java日期和时间API java.Time。因此,无论您是否已经使用Joda Time,后者都是您可以考虑的选项。由于字符串包含UTC偏移量,因此一种选择是返回OffsetDateTime。要测试这样做的方法:

assertEquals(OffsetDateTime.of(1980, 3, 26, 0, 0, 0, 0, ZoneOffset.ofHours(2)),
DateUtils.getOffsetDateTimeFromString("1980-03-26T00:00:00.000+0200"));

您的示例和代码可能会给人留下这样的印象:您只在字符串中的日期之后,不关心一天中的时间或偏移量。如果这是正确的,您的方法可能会返回LocalDate并以这种方式进行测试:

assertEquals(LocalDate.of(1980, Month.MARCH, 26),
DateUtils.getLocalDateFromString("1980-03-26T00:00:00.000+0200"));

后者还将使您无需考虑所有时区因素。在这两种情况下,请注意,我将日期时间对象传递给assertEquals,而不是字符串。

您的单元测试是正确的:您的方法返回错误的时间

您在问题中报告的失败是,虽然您的方法中预期的DateWed Mar 26 00:00:00 PST 1980,但实际值是Wed Mar 26 00:00:00 SGT 1980。这些不同是正确的。在下加利福尼亚州、育空地区、华盛顿州、内华达州、加利福尼亚州等地观测太平洋时间的午夜与中国的午夜不在同一时间点。

我可以在Android上使用java.time吗

是的,java.time在较旧和较新的Android设备上都能很好地工作。它只需要至少Java 6

  • 在Java 8及更高版本中,以及在较新的Android设备上(从API 26级开始(,现代API内置
  • 在Java6和7中,获得ThreeTen Backport,即新类的Backport(JSR310的ThreeTen;请参阅底部的链接(
  • 在(旧的(安卓系统上使用安卓版的ThreeTen Backport。它被称为ThreeTenABP。并确保使用子包从org.threeten.bp导入日期和时间类

如果您不希望您的Android代码依赖于第三方库,您仍然可以在单元测试中使用java.time。测试返回老式Date的方法:

Instant expectedInstant = LocalDate.of(1980, Month.MARCH, 26)
.atStartOfDay(ZoneId.systemDefault())
.toInstant();
Date expectedDate = Date.from(expectedInstant);
Date actualDate = DateUtils.getDateFromString("1980-03-26T00:00:00.000+0200");
assertEquals(expectedDate, actualDate);

如果使用backport(ThreeTen backport和/或ThreeTenABP(,则从InstantDate的转换会发生一些不同:

Date expectedDate = DateTimeUtil.toDate(expectedInstant);

再次注意,我比较的是Date对象,而不是字符串。

如何设置SimpleDateFormat的时区

要回答标题中的问题:请使用setTimeZone方法:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX");
df.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); // Pacific Time
System.out.println(df.parseObject("1980-03-26T00:00:00.000+0200"));

不过,在这种特殊情况下,它不会有任何区别,因为字符串中的偏移量是经过解析的,并且优先于格式化程序的时区。

有没有什么方法可以为单元测试设置默认时区

有几个选择。

TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));

将其放入单元测试的beforeClassbefore方法中,会将JVM的时区设置为太平洋时间。它不是防弹的,因为其他代码可能会在测试结束前将其设置为其他内容,但您可以控制这种情况不会发生。通常我会劝阻使用过时的TimeZone类。它也有设计问题,但如果您正在测试的方法使用过时的SimpleDateFormat,那么它是自然过时的选择。其中一个问题是它不会报告传递的字符串是否无效。要获得正确的验证,只需通过现代类:

TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));

或者使用后台端口:

TimeZone.setDefault(DateTimeUtils.toTimeZone(ZoneId.of("America/Los_Angeles")));

链接

  • Oracle教程:日期-时间,解释如何使用java.time
  • Java规范请求(JSR(310,其中首次描述了java.time
  • ThreeTen Backport项目,java.time到Java 6和7的Backport(JSR-310的ThreeTen(
  • ThreeTenABP,安卓版ThreeTen背包
  • 问题:如何在Android项目中使用ThreeTenABP,有一个非常彻底的解释

最新更新