`
jinnianshilongnian
  • 浏览: 21513947 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2420533
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:3010263
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5640610
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:260227
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1597955
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:250371
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5860620
Group-logo
跟我学Nginx+Lua开...
浏览量:702889
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:785761
社区版块
存档分类
最新评论

JDK BUG吗? 混乱的日期API

 
阅读更多

首先看一个测试用例:

import org.junit.Assert;
import org.junit.Test;

import java.sql.Time;
import java.sql.Timestamp;
import java.util.Date;

/**
 * <p>User: Zhang Kaitao
 * <p>Date: 13-5-26 下午5:43
 * <p>Version: 1.0
 */
public class DateTest {
    //when only millisecond part is different

    @Test
    public void testDateAfter() {
        Date d1 = new Date(1369461400000L);
        Date d2 = new Date(1369461400001L);
        Assert.assertTrue(d2.after(d1));
    }


    @Test
    public void testTimestampAfterOK() {
        Timestamp d1 = new Timestamp(1369461400000L);
        Timestamp d2 = new Timestamp(1369461400001L);
        Assert.assertTrue(d2.after(d1));
    }


    @Test
    public void testTimestampCastToDateAfterFail() {
        Date d1 = new Timestamp(1369461400000L);
        Date d2 = new Timestamp(1369461400001L);
        Assert.assertFalse(d2.after(d1));
    }

    @Test
    public void testDateCompare() {
        Date d1 = new Date(1369461400000L);
        Date d2 = new Date(1369461400001L);
        Assert.assertTrue(d2.compareTo(d1) == 1);
    }



    @Test
    public void testTimestampCompareOK() {
        Timestamp d1 = new Timestamp(1369461400000L);
        Timestamp d2 = new Timestamp(1369461400001L);
        Assert.assertTrue(d2.compareTo(d1) == 1);
    }


    @Test
    public void testTimestampCastToDateCompareOK() {
        Date d1 = new Timestamp(1369461400000L);
        Date d2 = new Timestamp(1369461400001L);
        Assert.assertTrue(d2.compareTo(d1) == 1);
    }

}

大家可能看到testTimestampCastToDateAfterFail测试用例,d2.after(d1) 是false。

 

 

从网络上找了下,类似的bug如下:

http://bugs.sun.com/view_bug.do?bug_id=5008227

写道
2004-06-14
EVALUATION

This is a side effect caused by the 4340146 fix. Because Date.after() no longer calls getTime(), after() and equals() in Timestamp work compare different time values.

Timestamp.after and before should call compareTo which works correctly.
###@###.### 2004-03-15

其也是建议使用compareTo,而不是after/before。

 

还一篇是在stackoverflow上的:

http://stackoverflow.com/questions/15629222/java-sql-timestamp-comparison-bug

 

有一个compareTo的,也有过类似的问题,不过1.5已经修复。

http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=676 

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103041

 

其中的主要问题是:

Timestamp没有重载(public boolean after(Date d) );

Date中的fastTime 存储了毫秒值;但Timestamp的fastTime只存储到秒,毫秒值部分存储到nanos部分。具体细节可参考jdk代码。

 

 

有细心的朋友可能注意到了:我的d1 和 d2 实际上是Timestamp类型啊 

    @Test
    public void testTimestampCastToDateAfterFail() {
        Date d1 = new Timestamp(1369461400000L);
        Date d2 = new Timestamp(1369461400001L);
        Assert.assertFalse(d2.after(d1));
    }

那比较的时候,怎么会发生这种事情?而且有朋友还注意到了compareTo就没有这个问题。

 

首先看下jdk的文档:

注:此类型由 java.util.Date 和单独的毫微秒值组成。只有整数秒才会存储在 java.util.Date 组件中。小数秒(毫微秒)是独立存在的。传递不是 java.sql.Timestamp 实例的对象时,Timestamp.equals(Object) 方法永远不会返回 true,因为日期的毫微秒组件是未知的。因此,相对于 java.util.Date.equals(Object) 方法而言,Timestamp.equals(Object) 方法是不对称的。此外,hashcode 方法使用底层 java.util.Date 实现并因此在其计算中不包括毫微秒。 

 

鉴于 Timestamp 类和上述 java.util.Date 类之间的不同,建议代码一般不要将 Timestamp 值视为 java.util.Date 的实例。Timestamp 和 java.util.Date 之间的继承关系实际上指的是实现继承,而不是类型继承。 

 

此处 可能已经注意到了:

1、Timestamp 和 java.util.Date 之间的继承关系实际上指的是实现继承,而不是类型继承。 

2、Timestamp.equals(Object) 方法是不对称的。此外,hashcode 方法使用底层 java.util.Date 实现并因此在其计算中不包括毫微秒。

 

此处我们大体能概括出来:

1、Date和Timestamp并不是继承关系。。。。。

2、after方法也是双向不对称的。。。。

 

 

关于不对称,再来看两个测试用例:

    @Test
    public void testTimestampAfterOK2() {
        Date d1 = new Timestamp(1369461400000L);
        Timestamp d2 = new Timestamp(1369461400001L);
        Assert.assertFalse(d2.after(d1));
    }

    @Test
    public void testTimestampAfterOK3() {
        Timestamp d1 = new Timestamp(1369461400000L);
        Date d2 = new Timestamp(1369461400001L);
        Assert.assertFalse(d2.after(d1));
    }

 

主要原因是Timestamp没有重载(public boolean after(Date d) );而且仔细思考了下,如果从JDK文档上总结的话,不应该算作bug;但是从compareTo上看那就应该是bug。

 

 

真是混乱啊。。。 JSR 310 新的日期和时间API 在JDK8会添加进去

 

更好的选择是使用如joda-time/或者使用JSR-310。

 

比较时应该注意自己的情况,如果不知道当前类型(Date/Timestamp)那么请使用compareTo;是什么使用日期,应该要做好单元测试。有了单元测试,才有了保险。。。。

 

此处如果你的java.sql.Time,JDK也没有提供只比较Time部分的API。。。。。。。

 

commons-lang也没有提供类似的API,不过commons-lang也在犹豫是否添加:

写道
DateUtils.isBeforeDay
DateUtils.isAfterDay

https://issues.apache.org/jira/browse/LANG-400

 

 

=================分割线==================================================

关于mysql的Timestamp:

假设表结构是:

create table `personal_message`(
  `id`               bigint not null auto_increment,
  `sender_id`        bigint,
  `receiver_id`      bigint,
  `send_date`        timestamp,
}

 如果有人执行:

 

update receiver_id=1 where id=?

你可能会发现:你的send_date改成了当前时间!具体原因仔细看mysql官方文档,官方文档说的很明白:

http://dev.mysql.com/doc/refman/5.6/en/timestamp-initialization.html

 

因为像send_date发送时间,一旦确定是不需要改的,解决方案是只加个默认值:

`send_date`        timestamp default 0,

 

而且mysql还一个问题是timestamp不是存储到毫秒值,所以如果想存到毫秒值级别 请使用如bigint直接存储毫秒值。

 

=================分割线==================================================

jpa中映射日期类型,可以使用:

@Temporal(TemporalType.TIMESTAMP)
private Date sendDate;

 TemporalType表示日期类型,分别对应:

public enum TemporalType {
	/**
	 * Map as <code>java.sql.Date</code>
	 */
	DATE,

	/**
	 * Map as <code>java.sql.Time</code>
	 */
	TIME,

	/**
	 * Map as <code>java.sql.Timestamp</code>
	 */
	TIMESTAMP
}

 

如果想在hibernate中映射其他日期类型,如Calendar:

可以使用hibernate的@org.hibernate.annotations.Type,如@Type(type = "timestamp"),默认支持的是:

写道
date, time, timestamp
Type mappings from java.util.Date and its subclasses to SQL types DATE, TIME and TIMESTAMP (or equivalent).

calendar, calendar_date
Type mappings from java.util.Calendar to SQL types TIMESTAMP and DATE (or equivalent).

 http://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html_single/#mapping-types 

 

当然,你也可以选择如joda-time,已经有hibernate集成了:

https://github.com/JodaOrg/joda-time-hibernate

 

如果你存储到数据库的是毫秒值,取回来想变成日期,可以自定义UserType,这个可以参考:

Hibernate自定义类型 集合--->字符串 存储

 

对于hibernate 写原生SQL时,还需要注意这个问题:《hibernate createSQLQuery的问题》,解决方案是:

如果hibernate4  addScalar("m_apiendtime ",TimestampType.INSTANCE)
其他 addScalar("m_apiendtime ",Hibernate.TIMESTAMP) 

 

 

如果有些拿不准的,可以考虑上单元测试,好处多多。

 

 

15
9
分享到:
评论
10 楼 jinnianshilongnian 2013-05-27  
caiwenhn2008 写道
Date
jinnianshilongnian 写道
icanfly 写道
我在JDK 1.6上试了楼主的测试用例,,,是OK的,无失败用例。


仔细观察下
@Test 
    public void testTimestampCastToDateAfterFail() { 
        Date d1 = new Timestamp(1369461400000L); 
        Date d2 = new Timestamp(1369461400001L); 
        Assert.assertFalse(d2.after(d1)); 
    } 

d2.after(d1) 应该为true  但是实际执行是false  所以失败了 即Assert成功了



这种逻辑应该写成  Assert.assertTrue(d1.before(d2)); 
转两道弯不好读。


此处就是因为d2.after(d1) 我们期待true  但是实际是false  失败了(不过写单元测试就是让其成为绿条)

即 盼望的(false,因为api的问题) == 实际的(false)
9 楼 caiwenhn2008 2013-05-27  
Date
jinnianshilongnian 写道
icanfly 写道
我在JDK 1.6上试了楼主的测试用例,,,是OK的,无失败用例。


仔细观察下
@Test 
    public void testTimestampCastToDateAfterFail() { 
        Date d1 = new Timestamp(1369461400000L); 
        Date d2 = new Timestamp(1369461400001L); 
        Assert.assertFalse(d2.after(d1)); 
    } 

d2.after(d1) 应该为true  但是实际执行是false  所以失败了 即Assert成功了



这种逻辑应该写成  Assert.assertTrue(d1.before(d2)); 
转两道弯不好读。
8 楼 jinnianshilongnian 2013-05-27  
icanfly 写道
我在JDK 1.6上试了楼主的测试用例,,,是OK的,无失败用例。


仔细观察下
@Test 
    public void testTimestampCastToDateAfterFail() { 
        Date d1 = new Timestamp(1369461400000L); 
        Date d2 = new Timestamp(1369461400001L); 
        Assert.assertFalse(d2.after(d1)); 
    } 

d2.after(d1) 应该为true  但是实际执行是false  所以失败了 即Assert成功了
7 楼 icanfly 2013-05-27  
我在JDK 1.6上试了楼主的测试用例,,,是OK的,无失败用例。
6 楼 jinnianshilongnian 2013-05-27  
chenhailong 写道
good question and best solution

官网建议类型好统一

比如
Timestamp 要和 Timestamp一起用,不要和Date一起用.

是的,不过有时候如使用hibernate 可能连自己都混乱了
5 楼 chenhailong 2013-05-27  
good question and best solution

官网建议类型好统一

比如
Timestamp 要和 Timestamp一起用,不要和Date一起用.
4 楼 jinnianshilongnian 2013-05-27  
还有几篇关于jdbc+oracle的,因为没有使用过 所以只给出链接:
http://www.myexception.cn/sql/1037430.html
http://liufeng-king.iteye.com/blog/1487326
http://hidba.org/?p=280

如果你使用hibernate 最好按照本文中介绍的强制注明你的类型
2 楼 jinnianshilongnian 2013-05-27  
还一个类似的,但是关于性能的
6912866 : (date) java.util.Date.before / after may be expensive
http://bugs.sun.com/view_bug.do?bug_id=6912866

建议直接使用compareTo比较。。。
1 楼 jinnianshilongnian 2013-05-27  
一篇类似的老帖子《java.sql.Date is not a real date》
http://thunderguy.com/semicolon/2003/08/14/java-sql-date-is-not-a-real-date/

相关推荐

    spring5.0和4.2.0 jdk8和jdk7 对应的版本

    这个版本的主要目标是充分利用JDK 8的新特性,如lambda表达式、Stream API和新日期时间API。Spring 5.0对反应式编程的支持是一大亮点,它引入了Spring WebFlux模块,这使得开发者可以构建非阻塞、高性能的Web应用...

    jdk api 1.8、jQuery3.1-api、jQuery3.3.1-api、jqueryapi2.2

    标题中的"jdk api 1.8、jQuery3.1-api、jQuery3.3.1-api、jqueryapi2.2"代表了两个主要的IT技术领域:Java开发工具包(Java Development Kit, JDK)和jQuery JavaScript库的不同版本。这些API文档是开发者在编写代码...

    jdk1.8.0_212 32位,当前官网最新版本

    4. **Date和Time API的增强**:用`java.time`包取代了过时的`java.util.Date`和`java.util.Calendar`,提供了更强大且易用的时间日期处理功能。 5. **方法引用**:允许直接引用类或对象的方法,进一步减少了代码量...

    jdk-8u25、jdk-8u111、jdk-8u131、jdk-8u181、jdk-8u201

    每个JDK的更新(如u25、u111等)通常会修复之前版本中的bug,提高性能,以及添加一些新的特性和功能。例如,u181可能是为了修复安全漏洞或性能优化,而u201可能包含了一些重要的安全更新。这些版本的JDK适用于...

    jdk1.8.0_91版本

    4. Date/Time API更新:JDK 8对日期和时间API进行了重构,提供了新的java.time包,包括LocalDate、LocalTime、LocalDateTime和ZonedDateTime等类,更加符合现代编程需求。 5. 默认方法:接口中可以定义具有实现的...

    jdk1.8.0-161

    Java 8对日期和时间API进行了重大重构,引入了`java.time`包,提供了更强大、更易用的日期和时间处理类,如LocalDate、LocalTime、LocalDateTime等。 ### 2. JDK 1.8.0_161的更新与改进 JDK 1.8.0_161是一个重要的...

    jdk1.8.0_152

    5. **Date与Time API的改进**:Java 8对日期和时间API进行了全面改革,引入了`java.time`包,提供了更强大、更易用的日期、时间和时区处理功能。 6. **新的 Nashorn JavaScript引擎**:JDK 1.8包含了一个新的...

    jdk1.8,32位和64位版本,jdk1.8.0_131

    5. Date和Time API改进:Java 8对日期和时间的API进行了全面升级,提供了更加强大和易于使用的类,如LocalDate、LocalTime和ZonedDateTime。 6. Optional类:这个类用于表示可能为空的值,有助于防止...

    Java:jdk1.8.0_25(少个jre)

    4. **日期与时间API的增强**:Java 8引入了全新的java.time包,替代了之前不完善的日期和时间API,提供了更强大的日期、时间和时区处理功能。 5. **默认方法**:接口中新增了默认方法,允许接口定义方法的实现,这...

    tomcat6.0.44+jdk1.7或jdk1.8或jdk1.6

    **JDK 1.8** (也称为Java 8)是另一个重大升级,带来了诸如Lambda表达式、函数式编程接口(如java.util.Function)、日期和时间API(java.time包)、默认方法在接口中、新的Stream API、改进的并发库等革新。...

    jdk1.8.0-101

    总的来说,JDK 1.8.0_101是Java 8的一个重要版本,它带来了Lambda表达式、Stream API、接口默认方法和新的日期时间API等强大功能,同时持续进行安全性和性能的改进。对于Java开发者而言,理解和掌握这些特性对于提高...

    jdk-8u221-windows-x64.zip

    在日期和时间处理上,引入了新的java.time包,替换了原有的日期和时间API,提供了更强大且易于使用的类。 JDK1.8U221作为该版本的一个更新,通常会包含之前版本的bug修复和性能提升。这些更新可能涉及到JVM的优化,...

    linux jdk 1.8 141

    Java 8是Java历史上的一个里程碑,引入了诸多新特性,如lambda表达式、函数式接口、流API(Stream API)、日期与时间API、默认方法以及新的并发改进等。这些特性极大地提升了Java的编程效率和灵活性。 Lambda表达式...

    jdk1.8.0_241.zip

    在JDK 1.8.0_241这个特定版本中,可能包括了一些安全更新、性能优化和bug修复,这些都是Oracle对Java平台持续维护和改进的一部分。对于开发者来说,及时更新到最新的JDK版本可以确保程序的稳定性和安全性。 关于...

    jdk1.6.0_45下载jdk-6u45-windows-x64

    附带的`jdk1.6.txt`文件可能包含了关于这个版本的详细信息,比如发行日期、已知问题、修复的Bug列表以及使用指南等。开发者应该仔细阅读这些文档,以便更好地理解和使用JDK 1.6.0_45。 总的来说,JDK 1.6.0_45对于...

    jdk1.8.0_131

    5. **日期和时间API**:Java 8对日期和时间API进行了全面升级,引入了`java.time`包,替代了之前复杂的`java.util.Date`和`java.util.Calendar`。 6. **新的类型接口**:如`Optional&lt;T&gt;`,它是一个容器对象,可能...

    jdk1.8安装包:jdk-8u161-windows-x64

    4. **日期和时间API**:Java 8提供了新的java.time包,替代了过时的java.util.Date和java.util.Calendar,使日期和时间操作更为简单直观。 5. **默认方法**:接口中可以定义带实现的方法,使得接口可以扩展而不会...

    jdk-8u231-windows-x64 JDK镜像

    - **日期和时间API**:Java 8改进了日期和时间的处理,提供了`java.time`包,取代了过时的`java.util.Date`和`java.util.Calendar`。 - **默认方法**:接口中可以定义带有实现的方法,这使得接口在不破坏向后兼容性...

    JDK1.8源码完整版

    在日期时间API方面,JDK1.8用java.time包取代了旧的java.util.Date和Calendar,提供了更直观、线程安全的API,使得日期和时间的操作更加简单和易用。 最后,JDK1.8还引入了Method References,它允许直接引用已有...

    JDK1.8最新版本【jdk-8u371-windows-x64】资源

    6. **增强功能**: JDK 1.8引入了一些重要特性,如Lambda表达式、函数式编程、Stream API、默认方法、新的日期和时间API (`java.time`包)等,这些都极大地提升了开发效率和代码可读性。 7. **安全更新**: 更新371...

Global site tag (gtag.js) - Google Analytics