Java DateTime转换单元测试本地正常但远程流水线执行失败求助
我有一个Quarkus应用,需要将带GMT偏移的字符串时间戳转换为Europe/Berlin时区时间,例如时间戳:"2022-08-13 10:13:56.48382+03"。相关单元测试在本地运行完全正常,但在远程流水线中执行失败。我已在应用启动时执行TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));设置默认时区,且转换逻辑中已显式指定时区,但怀疑流水线服务器的本地时区仍对测试产生影响,同时正在考虑修改流水线时区来解决问题,请问测试失败的原因是什么?
时间转换逻辑
public static Date convertTime(String timestamp) { // offset being f.e. +01, +12, -03 String gmtZone = "GMT" + getOffset(timestamp); // removes the milliseconds from time timestamp = stripMilliseconds(timestamp); DateTimeFormatter dtf = DateTimeFormatter.ofPattern(INCOMING_TIMESTAMP_FORMAT); LocalDateTime localDateTime = LocalDateTime.parse(timestamp, dtf); ZonedDateTime zdtGmt = ZonedDateTime.of(localDateTime, ZoneId.of(gmtZone)); ZonedDateTime zdtBerlin = zdtGmt.withZoneSameInstant(ZoneId.of(TIME_ZONE_EUROPE_BERLIN)); return Date.from(zdtBerlin.toInstant()); }
测试代码
@ParameterizedTest @MethodSource("timestampsWithOffsetWithCorrectlyConvertedTime") void offsetShouldBeCorrectlyConvertedToBerlinTime(String timestamp, Date expectedConvertedTime) { Date date = TimeZoneConverter.convertTime(timestamp); assertEquals(expectedConvertedTime, date); } private static Stream<Arguments> timestampsWithOffsetWithCorrectlyConvertedTime() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return Stream.of( Arguments.of("2022-08-13 10:13:56.48382+00", sdf.parse("2022-08-13 12:13:56")), Arguments.of("2022-09-20 22:33:16.4271382-03", sdf.parse("2022-09-21 03:33:16")), Arguments.of("2022-11-25 16:00:00.482+12", sdf.parse("2022-11-25 05:00:00")), Arguments.of("2022-03-03 11:33:16.483829+02", sdf.parse("2022-03-03 10:33:16")) ); }
问题原因及解决方案
核心问题:测试代码依赖默认时区
你的转换逻辑本身没有问题——用ZonedDateTime显式处理时区,最终返回的Date是无时区的瞬时时间戳。但测试代码里的SimpleDateFormat会依赖JVM默认时区:
- 本地环境你已经把默认时区设为Europe/Berlin,所以
sdf.parse()解析出来的Date和转换逻辑返回的瞬时值一致; - 流水线服务器的默认时区不是Europe/Berlin,导致解析出的
Date对应时间戳和预期不符,断言失败。
解决步骤
修改测试代码,让
SimpleDateFormat显式指定时区
不要依赖默认时区,给SimpleDateFormat设置Europe/Berlin时区:SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("Europe/Berlin")); // 新增该行这样不管JVM默认时区是什么,解析出来的
Date都是对应Berlin时区的瞬时值,和转换逻辑输出匹配。无需修改流水线时区(可选)
只要测试代码不依赖默认时区,流水线的时区设置就不会影响测试结果。如果有其他场景需要,也可以在流水线脚本中设置时区环境变量:export TZ="Europe/Berlin"但优先保证代码本身的时区独立性更可靠。
建议统一使用Java 8+日期API
转换逻辑已经用了ZonedDateTime这类新API,测试代码仍用线程不安全的SimpleDateFormat,建议替换为DateTimeFormatter:private static Stream<Arguments> timestampsWithOffsetWithCorrectlyConvertedTime() { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") .withZone(ZoneId.of("Europe/Berlin")); return Stream.of( Arguments.of("2022-08-13 10:13:56.48382+00", Date.from(formatter.parse("2022-08-13 12:13:56", Instant::from))), Arguments.of("2022-09-20 22:33:16.4271382-03", Date.from(formatter.parse("2022-09-21 03:33:16", Instant::from))), Arguments.of("2022-11-25 16:00:00.482+12", Date.from(formatter.parse("2022-11-25 05:00:00", Instant::from))), Arguments.of("2022-03-03 11:33:16.483829+02", Date.from(formatter.parse("2022-03-03 10:33:16", Instant::from))) ); }这样更安全,也彻底避免时区依赖问题。
内容的提问来源于stack exchange,提问作者bob

