SimpleDateFormat 在部署一段时间后给出错误的日期和时间

SimpleDateFormat giving wrong date and time after some time of deployment

我的代码中有 2 个文件:

文件 1 内容:

public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

文件 2 内容:

sdf.format(formatter.parse("2015-02-02")));

问题:文件 2 中的上面一行最初打印“2015-02-02 12:00:00”几个小时,但之后打印“2015-02-01 06:00:00”。 知道这里可能是什么问题。

附加信息: 我的服务器 运行 在位于美国的某台云计算机上。 new java.util.Date( ) 始终正确给出 UTC 时区值。 服务器使用命令 java -jar xyz.jar 启动。 还有其他文件正在使用 sdfformatter 变量。 我无法在本地机器上重现这个。 一旦问题开始在服务器上发生,它就会显示错误的日期时间,直到服务器重新启动。

如果你查看 Oracle 官方文档,它说

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

通过查看您的代码,您似乎在多个线程中重复使用同一个实例。这是不正确的!!!

要么维护一个格式化程序池,要么同步访问(不推荐),要么每次都创建一个新实例。

另一个 api 中的一些代码为 sdf 设置了时区,这导致 issue.Here 是在本地复制问题的示例:

import java.text.SimpleDateFormat;
import java.util.TimeZone;

public class SimpleDateFormatTExample {

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private static String timeZone = "CST";

public static void main(String[] args) {


    //-Duser.timezone=UTC


    try {
        String dateTimeString1 = sdf.format(formatter.parse("2018-01-01"));
        System.out.println("Thread Main->> " + dateTimeString1);
//output : Thread Main->> 2018-01-01 12:00:00

    } catch (Exception e) {
        e.printStackTrace();
    }


    new Thread(() -> {
        try {
            //timezone is changed by another thread
            sdf.setTimeZone(TimeZone.getTimeZone(timeZone));
            String dateTimeString = sdf.format(formatter.parse("2018-01-01"));
            System.out.println("Thread child->> " + dateTimeString);
//output : Thread child->> 2017-12-31 06:00:00
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();


    try {
        Thread.sleep(1000);
        String dateTimeString1 = sdf.format(formatter.parse("2018-02-15"));
        System.out.println("Thread Main:After timezone changes by another thread->> " + dateTimeString1);
//output : Thread Main:After timezone changes by another thread->> 2018-02-14 06:00:00

    } catch (Exception e) {
        e.printStackTrace();
    }


}

}

Nathan Hughes 和我自己的评论足以合并为一个答案:使用 java.time、现代日期时间 API,特别是 DateTimeFormatter

public static final DateTimeFormatter printFormatter
        = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");

现在您的格式可能如下所示:

    String stringToPrint = LocalDate.parse("2015-02-02")
            .atStartOfDay(ZoneOffset.UTC)
            .format(printFormatter);

    System.out.println(stringToPrint);

这会打印:

2015-02-02 00:00:00

在格式转换代码中,我利用了这样一个事实,即您的原始字符串 2015-02-02 是日期的标准 ISO 8601 格式。 LocalDate 将此格式解析为默认格式,即没有任何显式格式化程序。

你的代码出了什么问题?

从您的问题来看,您观察到的行为似乎有两种可能的解释:

  1. 服务器上使用这两个格式化程序的另一个类程序之一,设置其中一个的时区,例如America/Chicago。
  2. 两个或多个线程同时使用这些格式,这会导致其中一个线程的行为不正确。

观察到的行为,一个6小时的错误,出现后一直持续到服务器重启,似乎更符合第一个解释,你在自己的回答中也证实了这一点,谢谢你这样做那。

SimpleDateFormat 相反,现代 DateTimeFormatter 是线程安全的,可以防止任何线程问题,并且是不可变的,可以防止其他 类 修改格式化程序。所以它在这两种情况下都解决了你的问题。

顺便说一句,我想您知道您在格式模式字符串中错误地使用了小写字母 hhhh 是上午或下午从 01 到 12 的小时,而从 00 到 23 的一天中的小时需要大写 HH(这适用于 SimpleDateFormatDateTimeFormatter).

Link: Oracle tutorial: Date Time 解释如何使用 java.time.