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

Faw*_*ran 1 timezone android unit-testing simpledateformat

我想测试Date从字符串中获取 a 的 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
Run Code Online (Sandbox Code Playgroud)

这是我的测试:

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));
Run Code Online (Sandbox Code Playgroud)

这是我正在测试的方法:

    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;
    }
Run Code Online (Sandbox Code Playgroud)

所以单元测试不是时区独立的。有没有设置默认时区只是为了单元测试?

Ole*_*.V. 5

java.time 或 Joda-Time

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

不过,Joda-Time 也即将退休,它的继任者是 java.time,现代 Java 日期和时间 API。因此,无论您是否已经在使用 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"));
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

您在问题中报告的失败是,虽然Date您的方法预期为Wed 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 是内置的。
  • 在 Java 6 和 7 中获得 ThreeTen Backport,新类的向后移植(ThreeTen for JSR 310;请参阅底部的链接)。
  • 在(较旧的)Android 上使用 ThreeTen Backport 的 Android 版本。它被称为 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);
Run Code Online (Sandbox Code Playgroud)

如果使用 backport(ThreeTen Backport 和/或 ThreeTenABP),从Instantot的转换Date发生的有点不同:

    Date expectedDate = DateTimeUtil.toDate(expectedInstant);
Run Code Online (Sandbox Code Playgroud)

再次注意,我是在比较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"));
Run Code Online (Sandbox Code Playgroud)

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

有没有什么办法可以设置默认时区只用于单元测试?

有几个选项。

    TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
Run Code Online (Sandbox Code Playgroud)

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

    TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));
Run Code Online (Sandbox Code Playgroud)

或者使用反向移植:

    TimeZone.setDefault(DateTimeUtils.toTimeZone(ZoneId.of("America/Los_Angeles")));
Run Code Online (Sandbox Code Playgroud)

链接