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

时间:2017-02-17 21:39:14

标签: java gregorian-calendar java.util.date java.util.calendar

使用下面的代码,我注意到有时日期格式不正确。为了使它变得更奇怪,有时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

2 个答案:

答案 0 :(得分:3)

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

根据Javadoc

  

日期格式未同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问格式,则必须在外部进行同步。

请注意,这指的是对DateFormat实例的访问的外部同步,该方法。如果synchronized实例没有其他用途,则方法DateFormat只能解决此问题。

你可以:

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

后一种方法具有较低的争用,因为每个线程可以独立于其他线程继续进行。这也意味着您不会意外地省略同步。

但是,有更好的库来处理日期和时间,这些库是根据DateFormat缺乏线程安全性等问题来设计的。在Java 8中,有java.time API;对于早期版本的Java,有Jodatime。

答案 1 :(得分:1)

Answer by Turner是正确的,应该被接受。

java.time是线程安全的

java.time类通过使用不可变对象并使它们本身就是线程安全的来解决这个问题。

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

通过调用toString以标准ISO 8601格式生成字符串。对于其他格式,请使用DateTimeFormatter类。搜索Stack Overflow以获取许多示例和讨论。不用担心线程,所有线程安全。

对于UTC中的值,请提取Instant

Instant instant = zdt.toInstant() ;

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

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和&amp; SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore