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

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/

相关推荐

    jdk1.6 和jdk1.8中文api

    jdk1.6 和jdk1.8中文api jdk1.6 和jdk1.8中文api jdk1.6 和jdk1.8中文api jdk1.6 和jdk1.8中文api

    jdk api 1.8_中文文档

    jdk api 1.8_中文文档 jdk api 1.8_中文文档 jdk api 1.8_中文文档

    JDK 1.8中文API文档

    JDK 1.8中文API文档

    JDK1.7中文版API

    建议官方只收1积分,汉化绝大多数方法与函数只有少数1.7少用方法没有汉化,并含使用案例,jdk api 1.7是一款JAVA1.7中文版的API帮助文档,众所周知JDK是Java语言的软件开发工具包,主要用于移动设备、嵌入式设备上的...

    jdk9/avase9 官方Api文档 中文文档

    自用最全面,稳妥的文档,jdk 1.9api文档,内部包含中英文,方便查阅。 Java9可以说是一个庞大的系统工程。Java 的方方面面,包括 JDK 编译工具,运行时,Java 公共 API 和私有代码等等,完全做了一个整体改变。 这...

    JDK API 1.8 中文

    JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 中文 JDK API 1.8 ...

    jdk1.9英文版API

    jdk1.9英文版API,小部分内容可能需要联网才能看到,大部分基础都是可以直接查看的。

    bcprov-jdk15on-1.68-API文档-中文版.zip

    包含翻译后的API文档:bcprov-jdk15on-1.68-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.bouncycastle:bcprov-jdk15on:1.68; 标签:jdk15on、bouncycastle、bcprov、jar包、java、中文文档; 使用方法:...

    jdk_api_1.8-JAVA中文版API手册

    本手册为JDK-API-1.8版本,java中文版api手册。JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具...

    JDK_API中文版全套离线

    JDK_API中文版全套离线 JDK_API中文版全套离线 JDK_API中文版全套离线

    JDK_API_1.8(中文版)JDK_API_1_8_zh_CN.zip

    JDK_API_1.8(中文版)JDK_API_1_8_zh_CN.zip

    JDK1.6API文档

    JDK1.6API。java1.6开发文档,最新官网文档。满足java开发需求

    java-jdk-8u231-windowsx64修复bug.rar

    本版本修复 JDK的Bug1–数组切割 JDK的Bug2–三元运算符 JDK中不算Bug的Bug–ArrayList可通过构造函数传入非指定泛型的List并在get时出错

    jdk_1.9_中,英文api

    jdk 1.9 api 包含中文,英文版,有需要的可以下载。。。

    JDK1.8和JDK1.6API帮助文档

    JDK1.8和JDK1.6API帮助文档

    JDK API 1.6 中文版

    JDK API 1.6 中文版........

    jdk api 1.8_中文版.rar

    jdk api 1.8_google.CHM亲测双击即可打开。之前在网上下载了很多,要么双击打开报错,要么打开后里面是空白,终于找到一款好用的chm jdk api文档,跟大家分享下。

    jdk api 1.8中文版

    JDK1.8 API 中文 谷歌翻译版 java帮助文档 JDK API java 帮助文档 谷歌翻译 JDK1.8 API 谷歌百度翻译版 java帮助文档 Java最新帮助文档 本帮助文档是使用谷歌翻译,非人工翻译。准确性不能保证,请与英文版配合使用

    jdk1.8中文api

    jdk1.8中文api压缩包jdk1.8中文api压缩包jdk1.8中文api压缩包jdk1.8中文api压缩包

    jdk8中文API文档

    jdk8中文API文档

Global site tag (gtag.js) - Google Analytics