为什么我的 Java Calendar.setTime() 偶尔会设置错误的时间?

Why is my Java Calendar.setTime() sporadically setting wrong times?

使用下面的代码,我注意到有时日期格式不正确。更奇怪的是,有时 timeStamp 的日期正确,而 timeStampCopy 的日期错误,反之亦然。

   public static Timestamp method(String date, DateFormat dateFormat) throws Exception {           

        // date is always "2017-02-17"     

        // original
        GregorianCalendar gCal = new GregorianCalendar();
        gCal.setTime(dateFormat.parse(date));
        Timestamp timeStamp = new Timestamp(gCal.getTimeInMillis());

        // copy
        GregorianCalendar gCalCopy= new GregorianCalendar();
        gCalCopy.setTime(dateFormat.parse(date));
        Timestamp timeStampCopy = new Timestamp(gCalCopy.getTimeInMillis());

        if (!timeStamp.toString().contains("2017-02-17"))
            System.out.println(timeStamp.toString());   
        if (!timeStampCopy.toString().contains("2017-02-17"))
            System.out.println(timeStampCopy.toString());   

        return timeStamp;

    }

我不确定是什么原因造成的,但我使用 Date 对象进行了尝试,但遇到了同样的问题。我认为这可能是一个解析问题,但由于它做了两次相同的事情,我不确定。

以下是我得到的一些值:

timeStamp is:       2017-02-17 00:00:00.0
timeStampCopy is:   1700-02-17 00:00:00.0

你说你在线程之间共享 DateFormat 实例。

根据 Javadoc:

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.

注意这里指的是访问DateFormat实例的外部同步,不是方法。如果 DateFormat 实例没有其他用途,则制作方法 synchronized 只会解决此问题。

您可以:

  • 使用 DateFormat 实例显式同步所有代码(值得向变量添加 @GuardedBy 注释,以便记录您希望在使用它之前持有锁) ;
  • 将变量类型更改为 ThreadLocal<DateFormat>(并适当地初始化共享变量),这确保每个线程都有自己的 DateFormat.
  • 副本

后一种方法的争用较少,因为每个线程都可以独立于其他线程进行处理。这也意味着您不能不小心忽略同步。

但是,有更好的库来处理日期和时间,这些库的设计考虑到了 DateFormat 缺乏线程安全等问题。在Java8中,有java.timeAPI;对于 Java 的早期版本,有 Jodatime。

正确,应该接受。

java.time 是 thread-safe

java.time class 解决了这个问题,方法是使用不可变对象并使它们固有 thread-safe。

LocalDate ld = LocalDate.of( "2017-02-17" );
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ld.atStartOfDay( z );

以标准 ISO 8601 format by calling toString. For other formats, use DateTimeFormatter class 生成字符串。搜索 Stack Overflow 以获取许多示例和讨论。线程不用愁,都thread-safe。

对于 UTC 中的值,提取 Instant

Instant instant = zdt.toInstant() ;

不需要使用java.sql.Timestamp。现代 JDBC 驱动程序可以通过 toObject 和 setObject 方法处理 java.time 类型。对于较旧的驱动程序,使用添加到旧 classes 的新方法进行转换。


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

从哪里获得java.time classes?

  • Java SE 8 and SE 9 及更高版本
    • Built-in。
    • 标准 Java API 的一部分,带有捆绑实施。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6 and SE 7
    • java.time 的大部分功能是 back-ported 到 Java ThreeTen-Backport 中的 6 和 7。
  • Android
    • ThreeTenABP项目专门为Android改编ThreeTen-Backport(上面提到的)。
    • 参见

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.