【问题标题】:How to make sure test always runs in the same time zone如何确保测试始终在同一时区运行
【发布时间】:2021-11-05 07:55:13
【问题描述】:

我有一个函数可以将 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")

...但是,它们似乎都不会影响解析输出。

对此我能做些什么?我想要的只是某种方法来确保我的测试在同一时区运行,而不管它们运行的​​机器站在哪里,这样我就可以正确地测试导出。

【问题讨论】:

  • 是否有充分的理由使用Date,而不是java.time.Instant(它有自己的toString() 方法,并且总是在祖鲁语中打印?
  • (顺便说一句,我希望您只是省略代码以确保一次只有一个线程访问 SimpleDateFormat?如果没有,您需要确保,例如使用 ThreadLocal 代替。你不需要使用Instant)
  • 我建议你不要使用SimpleDateFormatDate。这些类设计不良且过时,尤其是前者,尤其是出了名的麻烦。而是使用来自java.time, the modern Java date and time APIInstant
  • 不要确保测试在特定时区运行。编写代码和测试,使它们独立于 JVM 的默认时区。

标签: java unit-testing timezone datetime-format epoch


【解决方案1】:

java.time

java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,改用modern Date-Time API*

您可以使用Instant.ofEpochMilli 将纪元毫秒转换为Instant,然后使用Instant#toString。但是,Instant#toString 如果它们为零,则省略秒部分。因此,如果您需要模式中的值,yyyy-MM-dd'T'HH:mm:ss.SSSXXX,您可以将Instant 转换为OffsetDateTime 并使用DateTimeFormatter 对其进行格式化。

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

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,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

【讨论】:

    【解决方案2】:

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

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

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


    您也可以通过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 会更容易。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 2012-06-30
      • 1970-01-01
      • 2011-08-02
      • 2015-08-09
      • 2023-03-26
      相关资源
      最近更新 更多