如何在 Android 单元测试中为 SimpleDateFormat 设置时区
How to set the timezone for a SimpleDateFormat in Android Unit test
我想测试日期实用程序 class 中的一个方法,该方法从字符串中获取 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
Date
和SimpleDateFormat
classes有设计问题,所以考虑不使用它们。你的问题的第一个版本被标记为 jodatime,如果你在你的项目中使用 Joda-Time,那已经是一个相当大的改进。 Joda-Time 为您提供了所需的所有功能,因此您的方法应该 return 来自 Joda-Time 的适当类型,而不是 old-fashioned Date
.
不过,Joda-Time 也即将退休,其继任者是 java.time,现代 Java 日期和时间 API。因此,无论您是否已经在使用 Joda-Time,后者都是您可以考虑的选项。由于您的字符串包含 UTC 偏移量,因此一种选择是 return 和 OffsetDateTime
。要测试执行此操作的方法:
assertEquals(OffsetDateTime.of(1980, 3, 26, 0, 0, 0, 0, ZoneOffset.ofHours(2)),
DateUtils.getOffsetDateTimeFromString("1980-03-26T00:00:00.000+0200"));
您的示例和代码可能给人的印象是您只是在字符串中的日期之后,而不关心一天中的时间或偏移量。如果这是正确的,您的方法可能 return a LocalDate
并以这种方式进行测试:
assertEquals(LocalDate.of(1980, Month.MARCH, 26),
DateUtils.getLocalDateFromString("1980-03-26T00:00:00.000+0200"));
后者也将使您摆脱所有时区的考虑。在这两种情况下,请注意我将 date-time objects 传递给 assertEquals
,而不是字符串。
你的单元测试是正确的:你的方法return错误的时间
您在问题中报告的失败是,虽然您的方法的预期 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 出现 built-in.
- 在 Java 6 和 7 中获取 ThreeTen Backport,新 classes 的 backport(ThreeTen 用于 JSR 310;请参阅底部的链接)。
- 在(较旧的)Android 使用 ThreeTen Backport 的 Android 版本。它叫做 ThreeTenABP。并确保使用子包从
org.threeten.bp
导入日期和时间 classes。
如果您不希望 Android 代码依赖于 third-party 库,您仍然可以在单元测试中使用 java.time。测试 return 是 old-fashioned 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);
如果使用反向端口(ThreeTen 反向端口 and/or ThreeTenABP),从 Instant
到 Date
的转换会略有不同:
Date expectedDate = DateTimeUtil.toDate(expectedInstant);
再次注意,我比较的是 Date
objects,而不是字符串。
如何为 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"));
将其放入单元测试的 beforeClass
或 before
方法中会将 JVM 的时区设置为太平洋时间。它不是 bullet-proof,因为其他代码可能会在测试完成之前将其设置为其他内容,但您可以控制它不会发生。通常我不鼓励使用过时的 TimeZone
class。它也有设计问题,但如果您正在测试的方法使用过时的 SimpleDateFormat
,那么它自然是过时的选择。问题之一是它不会报告传递的字符串是否无效。要获得正确的验证,只需通过现代 class:
TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));
或使用向后移植:
TimeZone.setDefault(DateTimeUtils.toTimeZone(ZoneId.of("America/Los_Angeles")));
链接
- Oracle tutorial: Date Time 解释如何使用
java.time
.
- Java Specification Request (JSR) 310,其中首次描述了
java.time
。
- ThreeTen Backport project,
java.time
向后移植 Java 6 和 7(JSR-310 的 ThreeTen)。
- ThreeTenABP、Android ThreeTen Backport 版
- Question: How to use ThreeTenABP in Android Project,解释的很透彻。
我想测试日期实用程序 class 中的一个方法,该方法从字符串中获取 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
Date
和SimpleDateFormat
classes有设计问题,所以考虑不使用它们。你的问题的第一个版本被标记为 jodatime,如果你在你的项目中使用 Joda-Time,那已经是一个相当大的改进。 Joda-Time 为您提供了所需的所有功能,因此您的方法应该 return 来自 Joda-Time 的适当类型,而不是 old-fashioned Date
.
Joda-Time 也即将退休,其继任者是 java.time,现代 Java 日期和时间 API。因此,无论您是否已经在使用 Joda-Time,后者都是您可以考虑的选项。由于您的字符串包含 UTC 偏移量,因此一种选择是 return 和 OffsetDateTime
。要测试执行此操作的方法:
assertEquals(OffsetDateTime.of(1980, 3, 26, 0, 0, 0, 0, ZoneOffset.ofHours(2)),
DateUtils.getOffsetDateTimeFromString("1980-03-26T00:00:00.000+0200"));
您的示例和代码可能给人的印象是您只是在字符串中的日期之后,而不关心一天中的时间或偏移量。如果这是正确的,您的方法可能 return a LocalDate
并以这种方式进行测试:
assertEquals(LocalDate.of(1980, Month.MARCH, 26),
DateUtils.getLocalDateFromString("1980-03-26T00:00:00.000+0200"));
后者也将使您摆脱所有时区的考虑。在这两种情况下,请注意我将 date-time objects 传递给 assertEquals
,而不是字符串。
你的单元测试是正确的:你的方法return错误的时间
您在问题中报告的失败是,虽然您的方法的预期 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 出现 built-in.
- 在 Java 6 和 7 中获取 ThreeTen Backport,新 classes 的 backport(ThreeTen 用于 JSR 310;请参阅底部的链接)。
- 在(较旧的)Android 使用 ThreeTen Backport 的 Android 版本。它叫做 ThreeTenABP。并确保使用子包从
org.threeten.bp
导入日期和时间 classes。
如果您不希望 Android 代码依赖于 third-party 库,您仍然可以在单元测试中使用 java.time。测试 return 是 old-fashioned 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);
如果使用反向端口(ThreeTen 反向端口 and/or ThreeTenABP),从 Instant
到 Date
的转换会略有不同:
Date expectedDate = DateTimeUtil.toDate(expectedInstant);
再次注意,我比较的是 Date
objects,而不是字符串。
如何为 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"));
将其放入单元测试的 beforeClass
或 before
方法中会将 JVM 的时区设置为太平洋时间。它不是 bullet-proof,因为其他代码可能会在测试完成之前将其设置为其他内容,但您可以控制它不会发生。通常我不鼓励使用过时的 TimeZone
class。它也有设计问题,但如果您正在测试的方法使用过时的 SimpleDateFormat
,那么它自然是过时的选择。问题之一是它不会报告传递的字符串是否无效。要获得正确的验证,只需通过现代 class:
TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));
或使用向后移植:
TimeZone.setDefault(DateTimeUtils.toTimeZone(ZoneId.of("America/Los_Angeles")));
链接
- Oracle tutorial: Date Time 解释如何使用
java.time
. - Java Specification Request (JSR) 310,其中首次描述了
java.time
。 - ThreeTen Backport project,
java.time
向后移植 Java 6 和 7(JSR-310 的 ThreeTen)。 - ThreeTenABP、Android ThreeTen Backport 版
- Question: How to use ThreeTenABP in Android Project,解释的很透彻。