由于种种原因,简书等第三方平台博客不再保证能够同步更新,欢迎移步 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.Date
与 java.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.Date
与 java.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]