如何确保测试始终在同一时区运行

How to make sure test always runs in the same time zone

我有一个函数可以将 Unix 纪元时间解析为 yyyy-MM-dd'T'HH:mm:ss.SSSXXX 格式,以便将其导出到文件中:

    public static final SimpleDateFormat REQIF_DATE_FORMAT_WITH_MILLIS
        = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

    public static String convertEpochStringToReqifDateString(String epochString) {
        Date timestamp = new Date(Long.parseLong(epochString));
        return REQIF_DATE_FORMAT_WITH_MILLIS.format(timestamp);
    }

现在,我对这个导出进行了测试,但是当它们在本地通过时,它们在服务器上却失败了,因为它显然处于不同的时区。具体来说,差异如下所示:

LAST-CHANGE="2017-03-13T21:36:44.261+01:00"
LAST-CHANGE="2017-03-13T20:36:44.261Z"

我已经在测试前尝试了运行一些事情,以确保测试始终运行在同一时区,例如:

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.setProperty("user.timezone", "UTC");

以及 JUnitPioneer 注释:

@DefaultTimeZone("UTC")

...然而,其中 none 似乎完全影响了解析输出。

我该怎么办?我想要的只是某种方法来确保我的测试 运行 在同一时区,而不管它们 运行 所在的机器在哪里,这样我就可以正确测试导出。

更好的方法是使用 java.time.Instant:

Instant instant = Instant.ofEpochMilli(Long.parseLong(epochString));
return instant.toString();

这将始终以 UTC 打印,无论 JVM 的默认时区如何。


您也可以通过 setting the time zone on the SimpleDateFormatter:

REQIF_DATE_FORMAT_WITH_MILLIS.setTimeZone(TimeZone.getTimeZone("UTC"));

请注意,您需要注意共享 SimpleDateFormat,因为它具有可变状态,该状态会被多线程访问破坏。您可以像这样为每个线程创建一个单独的实例:

static final ThreadLocal<SimpleDateFormat> REQIF_DATE_FORMAT_WITH_MILLIS =
    ThreadLocal.withInitial(() -> {
      SimpleDateFormat sdf = new SimpleDateFormat();
      sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
      return sdf;
    });

然后通过以下方式访问它:

return REQIF_DATE_FORMAT_WITH_MILLIS.get().format(timestamp);

但这变得相当混乱,不是吗?使用 Instant.

更容易

java.time

java.util 日期时间 API 及其格式 API、SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到 modern Date-Time API*.

您可以使用 Instant.ofEpochMilli to convert the epoch milliseconds into Instant and then use the Instant#toString. However, Instant#toString 省略秒部分(如果它们为零)。因此,如果您需要严格在模式 yyyy-MM-dd'T'HH:mm:ss.SSSXXX 中的值,您可以将 Instant 转换为 OffsetDateTime 并使用 DateTimeFormatter.

格式化它

解决方案使用 java.time,现代日期时间 API:

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        // An example epoch milliseconds
        long millis = 1631113620000L;

        Instant instant = Instant.ofEpochMilli(millis);
        String strDateTime = instant.toString();
        System.out.println(strDateTime);

        // If you need the value strictly in the pattern, yyyy-MM-dd'T'HH:mm:ss.SSSXXX
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
        OffsetDateTime odt = instant.atOffset(ZoneOffset.UTC);
        strDateTime = odt.format(dtf);
        System.out.println(strDateTime);
    }
}

输出:

2021-09-08T15:07:00Z
2021-09-08T15:07:00.000Z

ONLINE DEMO

Trail: Date Time.

了解有关现代日期时间 API 的更多信息

* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and