浅谈 Java 与 Oracle 中的日期与时间类型

由于种种原因,简书等第三方平台博客不再保证能够同步更新,欢迎移步 GitHub:https://github.com/kingcos/Perspective/。谢谢!

注:
由于目前个人对 Java 的理解应用仅限于皮毛,故若有不妥,望及时告知,会及时修正。

  • Info:
  • JDK 1.8
  • Eclipse EE neon
  • Oracle 10g XE

前言

暑假学校培训,因此整理一下之前在学习过程中比较困惑的地方。方便未来查阅,也使自己能够更深入了解 Java。这次来说一说日期与时间,因为数据库和 Java 本身都有许多存储日期或时间的类型,那么如何选择合适的类型,并正确的存入以及读取便很重要。网上的资料也有些参差不齐,因此我个人整理于此,并附上可以实际运行的代码。

Java 中的日期与时间类型简介

子父类关系

java.lang.Object java.lang.Object java.lang.Object
java.util.Date java.util.Date java.util.Date
- java.sql.Timestamp java.sql.Date

精度

类型 java.util.Date java.sql.Timestamp java.sql.Date
精度 年 月 日 时 分 秒 年 月 日 时 分 秒 毫微秒 年 月 日

初始化

public class TestInitTime {
    public static void main(String[] args) {
        java.util.Date utilDate_1 = new java.util.Date();
        java.util.Date utilDate_2 = new java.util.Date(System.currentTimeMillis());
        java.sql.Timestamp sqlTimestamp = new java.sql.Timestamp(System.currentTimeMillis());
        java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
        
        System.out.println("utilDate_1 =\t" + utilDate_1);
        System.out.println("utilDate_2 =\t" + utilDate_2);
        System.out.println("sqlTimestamp =\t" + sqlTimestamp);
        System.out.println("sqlDate =\t" + sqlDate);
    }
}

// Console:
// utilDate_1 = Sun Jul 17 09:26:07 CST 2016
// utilDate_2 = Sun Jul 17 09:26:07 CST 2016
// sqlTimestamp = 2016-07-17 09:26:07.342
// sqlDate = 2016-07-17

上述的初始化均使用了各自未过时的构造函数,输出打印后,可以看到明显的精度区别。

PS
System.currentTimeMillis(): 返回以毫秒为单位的当前时间。
CST 代表 China Standard Time(中国标准时间,即东八区,北京时间)

多种日期类型转换

String -> 时间

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TransformDateOrTime {

    public static void main(String[] args) {
        String date = "2016-7-17 14:30:05";
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        java.util.Date utilDate = null;
        try {
            utilDate = dateFormat.parse(date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println("utilDate: " + utilDate);

        java.sql.Timestamp sqlTimestamp = java.sql.Timestamp.valueOf("2016-7-17 14:30:05");
        java.sql.Date sqlDate = java.sql.Date.valueOf("2016-7-17");

        System.out.println("sqlTimestamp: " + sqlTimestamp);
        System.out.println("sqlDate: " + sqlDate);  
    }
}

// Console:
// utilDate: Sun Jul 17 14:30:05 CST 2016
// sqlTimestamp: 2016-07-17 14:30:05.0
// sqlDate: 2016-07-17

java.util.Datejava.sql.Timestamp

getTime(): 返回调用对象表示的自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数。

// java.util.Date -> java.sql.Timestamp
java.util.Date utilDate_1 = null;
try {
    utilDate_1 = dateFormat.parse(date);
} catch (ParseException e) {
    e.printStackTrace();
}
java.sql.Timestamp sqlTimestamp_1 = new java.sql.Timestamp(utilDate_1.getTime());
System.out.println(utilDate_1 + " 转换为 " + sqlTimestamp_1);
                
// java.util.Date <- java.sql.Timestamp (精度丢失)
java.sql.Timestamp sqlTimestamp_2 = new java.sql.Timestamp(System.currentTimeMillis());
java.util.Date utilDate_2 = new java.util.Date(sqlTimestamp_2.getTime());
System.out.println(sqlTimestamp_2 + " 转换为 " + utilDate_2);

// Console:
// Sun Jul 17 14:30:05 CST 2016 转换为 2016-07-17 14:30:05.0
// 2016-07-17 10:09:38.736 转换为 Sun Jul 17 10:09:38 CST 2016

java.util.Datejava.sql.Date

// java.util.Date -> java.sql.Date
java.sql.Date sqlDate_1 = new java.sql.Date(utilDate_1.getTime());
System.out.println(utilDate_1 + " 转换为 " + sqlDate_1);
        
// java.util.Date <- java.sql.Date
java.sql.Date sqlDate_2 = new java.sql.Date(System.currentTimeMillis());
java.util.Date utilDate_3 = new java.util.Date(sqlDate_2.getTime());
System.out.println(sqlDate_2 + " 转换为 " + utilDate_3);

// Console:
// Sun Jul 17 14:30:05 CST 2016 转换为 2016-07-17
// 2016-07-17 转换为 Sun Jul 17 11:14:15 CST 2016

如何选择?

在上面最后由 java.sql.Date 转换为 java.util.Date 中,虽然我们之前查到 java.sql.Date 只能保存年月日,但是这里却可以转换为带有时分秒java.util.Date。而 java.sql.Date 中的 getHours()getMinutes()getSeconds()(也包括对应的 setter)方法均已过时,如果调用会有 java.lang.IllegalArgumentException 异常。所以 java.sql.Date 只是屏蔽了时间中的时分秒,为了和数据库中的 DATE 类型匹配,查看其源代码就可以得知,java.sql.Date 继承但没有重写 getTime() 方法,而本身的 public Date(long date) 构造方法也是调用了父类的构造方法。

而与此不同的是 java.sql.Timestamp,其对父类做了扩充,通过查看其源代码,我们可以发现,其 getTime() 中增加了纳秒(1s = 1E9nanos),而且单独增加了 getNanos() 方法。

因此 java.sql.Date 只是屏蔽年月日,而不是移除,而 java.sql.Timestamp 对父类进行了扩充。在下面的 Demo 中,会实际操作数据库,这样一存一取就可以将其特点展现。

在这里以 Oracle 数据库为例,Oracle 中有两种主要日期与时间类型,DATE 以及 TIMESTAMP

DATE: 仅存 年 月 日
TIMESTAMP: 保存 年 月 日 时 分 秒 纳秒

所以对应 Java 中,我们就应该在保存合适精度的时间下,选择合适的类型。Java 中的 java.util.Date 更为灵活,我们可以在恰当的时候将其转为合适的类型存入数据库,或者在取出时转为该类型。

Demo

SQL

-- 建表
DROP TABLE T_TIME;
CREATE TABLE T_TIME (
    ID NUMBER(10,0) PRIMARY KEY,
    date_1 DATE, 
    timestamp_1 TIMESTAMP, 
    date_2 DATE, 
    timestamp_2 TIMESTAMP
);

-- 创建自增序列
drop sequence time_id;
create sequence time_id
increment by 1  
start with 1 
nomaxvalue 
nominvalue 
nocache

实体类:TimeEntity.java

public class TimeEntity {
    private int id;
    
    private java.util.Date date_1;
    private java.sql.Date date_2;
    
    private java.util.Date timestamp_1;
    private java.sql.Timestamp timestamp_2;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public java.util.Date getDate_1() {
        return date_1;
    }
    public void setDate_1(java.util.Date date_1) {
        this.date_1 = date_1;
    }
    public java.sql.Date getDate_2() {
        return date_2;
    }
    public void setDate_2(java.sql.Date date_2) {
        this.date_2 = date_2;
    }
    public java.util.Date getTimestamp_1() {
        return timestamp_1;
    }
    public void setTimestamp_1(java.util.Date timestamp_1) {
        this.timestamp_1 = timestamp_1;
    }
    public java.sql.Timestamp getTimestamp_2() {
        return timestamp_2;
    }
    public void setTimestamp_2(java.sql.Timestamp timestamp_2) {
        this.timestamp_2 = timestamp_2;
    }
    
    public String toString() {
        return "TimeEntity [id=" + id + ", date_1=" + date_1 + ", date_2=" + date_2 + ", timestamp_1=" + timestamp_1
                + ", timestamp_2=" + timestamp_2 + "]";
    }
    
}

测试类:TestTimeDateType.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class TestTimeDateType {
    public static void main(String[] args) {
        try {
            Class.forName("oracle.jdbc.OracleDriver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // url 中的地址要替换为自己数据的地址
        String url = "jdbc:oracle:thin:@localhost:1521:XE";
        // 数据库用户名及密码需要设置为自己的
        String user = "demo";
        String password = "123456";
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
            String sql = "insert into t_time values (time_id.nextVal, ?, ?, ?, ?)";
            ps = connection.prepareStatement(sql);
            
            ps = setAll(ps);
            
            ps.executeUpdate();
            
            // 这里我们只运行一次,为方便起见,因此仅查询 id 为 1 的记录
            sql = "select * from t_time where id = 1";
            ps = connection.prepareStatement(sql);
            TimeEntity te = new TimeEntity();
            rs = ps.executeQuery();
            while (rs.next()) {
                te.setId(rs.getInt(1));
                te.setDate_1(rs.getDate(2));
                te.setTimestamp_1(rs.getTimestamp(3));
                
                te.setDate_2(rs.getDate(4));
                te.setTimestamp_2(rs.getTimestamp(5));
            }
            System.out.println(te);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
                ps.close();
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    static PreparedStatement setAll(PreparedStatement ps) throws Exception {
        ps.setDate(1, returnSqlDateWithSqlDate());
        ps.setTimestamp(2, returnTimestampWithTimestamp());
        ps.setDate(3, new java.sql.Date(returnSqlDateWithUtilDate().getTime()));
        ps.setTimestamp(4,new java.sql.Timestamp(returnTimestampWithUtilDate().getTime()));
        
        return ps;
    }
    
    static java.sql.Date returnSqlDateWithSqlDate() {
        java.sql.Date sqlDate = java.sql.Date.valueOf("2012-2-2");
        return sqlDate;
    }
    
    static java.sql.Timestamp returnTimestampWithTimestamp() {
        java.sql.Timestamp timestamp = java.sql.Timestamp.valueOf("2015-5-5 5:55:55.555");
        return timestamp;
    }
    
    static java.util.Date returnSqlDateWithUtilDate() throws Exception {
        String date = "2013-3-3 3:33:33";
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        java.util.Date utilDate = dateFormat.parse(date);
        return utilDate;
    }
    
    static java.util.Date returnTimestampWithUtilDate() throws Exception {
        String date = "2016-6-6 6:6:6";
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        java.util.Date utilDate = dateFormat.parse(date);
        return utilDate;
    }
}

// Console:
// TimeEntity [id=1, date_1=2012-02-02, date_2=2013-03-03, timestamp_1=2015-05-05 05:55:55.555, timestamp_2=2016-06-06 06:06:06.0]

小结

从上面实例中,就可以基本清楚这几个类型的差别,以及其中的转换,因此在实际使用中便可以通过需要的不同精度,来确定所选的类型即可。

java.util.Date 与 Calendar

java.util.Date 中有许多过时方法,查看其注释,有许多都被 Calendar 所代替。由于在现实中,java.util.Date 不再能胜任国际化的操作,因此建议使用 Calendar 进行日期与时间处理。由于 Calendar 类是抽象类,且 Calendar 类的构造方法是 protected 的,所以无法使用Calendar类的构造方法来创建对象,但提供了 getInstance() 静态方法来创建对象。

转化

测试类:TestCalendar.java

import java.util.Calendar;
import java.util.Date;

public class TestCalendar {
    public static void main(String[] args) {
        // Calendar 转化为 Date
        Calendar calendar_1 = Calendar.getInstance();
        System.out.println(calendar_1.getTimeInMillis());
        Date date_1 = calendar_1.getTime();
        System.out.println("Calendar -> Date" + date_1);
        
        // Date 转化为 Calendar
        Date date_2 = new Date();
        Calendar calendar_2 = Calendar.getInstance();
        calendar_2.setTime(date_2);
        System.out.println("Date -> Calendar " + calendar_2);
    }
}

// Console:
1468741779943
Calendar -> DateSun Jul 17 15:49:39 CST 2016
Date -> Calendar java.util.GregorianCalendar[time=1468741779978,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2016,MONTH=6,WEEK_OF_YEAR=30,WEEK_OF_MONTH=4,DAY_OF_MONTH=17,DAY_OF_YEAR=199,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=3,HOUR_OF_DAY=15,MINUTE=49,SECOND=39,MILLISECOND=978,ZONE_OFFSET=28800000,DST_OFFSET=0]

参考资料

本人博客:https://maimieng.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home...
    光剑书架上的书阅读 3,856评论 2 8
  • 一、Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计...
    子非鱼_t_阅读 4,153评论 1 44
  • Episode 11 运营数据的一二三 谈完了内容运营、活动运营、用户运营的基础知识,我们来聊聊运营数据的一些事儿...
    songshu阅读 289评论 0 2
  • 画魂(一) 秀芬婶死的这一年,院子里颇不宁静。 院子里有棵硕大的梧桐树,不知道怎么回事,树干上的树皮一夜时间脱落了...
    三千晚风阅读 1,248评论 0 5
  • M号小姐生活在一个普通的家庭里,她的“母后”一个极具有控制欲的女王;万事都要打着“为你好”的名义干涉着你的事;她想...
    m号小姐阅读 178评论 0 0