0%

Java中的时间和日期组件

在一个项目中需要获取数据库中某天0时0分0秒到第二天0时0分0秒之间的记录,一开始想到的是之间使用Date对象的getTime()方法获得当前的微秒数,然后截掉小时、分钟和微秒得到该日期的0时0分0秒,代码如下:

1
2
3
4
5
6
final long MICROSECONDS_PER_DAY = 24 * 60 * 60 * 1000;
long curMillis = System.currentTimeMillis();
//当天的0时0分0秒
Date today = new Date(curMillis - curMillis % MICROSECONDS_PER_DAY);
//昨天的0时0分0秒
Date yesterday = new Date(today.getTime() - MICROSECONDS_PER_DAY);

一切看上去都很reasonable,日期也是对的,不过时间并不是0时0分0秒,而是8时0分0秒

1
2
System.out.println(today);  //Wed Jun 01 08:00:00 CST 2016
System.out.println(yesterday); //Tue May 31 08:00:00 CST 2016

为了搞清楚问题出在哪里,对Java中与date、time相关概念做了一次梳理。

时区(TimeZone)

时区(Time Zone)是地球上的区域使用同一个时间的定义,1884年在华盛顿召开国际经度会议时,为了克服时间上的混乱,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1-12区,西1-12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差1小时。例如,中国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间迟1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。凡向西走,每过一个时区,就要把表拨慢1小时(比如2点拨到1点);凡向东走,每过一个时区,就要把表拨快1小时(比如1点拨到2点)。并且规定英国(格林尼治天文台旧址)为本初子午线,即零时(24时)经线。在中国采用首都北京所在地东八区的时间为全国统一使用时间。
Java中的TimeZone对应现实世界中的时区,可以使用getTimeZone(String ID)获得某个时区,例如使用TimeZone.getTimeZone(“Asia/Shanghai”)可以得到中国标准时间的时区,使用TimeZone.getDefault()可以得到系统标准时区。

Date

在Java 7中,Date类除了Date(long milliseconds)和Date()两个构造器外,其它构造器都废弃了。这两个构造器都基于从1970年1月1日00:00:00GMT算起的微秒数,默认构造器以系统当前的微秒数构造对象。

1
2
3
4
5
6
7
//Date类的构造器
public Date(long date) {
fastTime = date;
}
public Date() {
this(System.currentTimeMillis());
}

Date对象常用 after(Date when)、before(Date when)和compareTo(Date anotherDate)这三个比较函数,这些比较都是基于上述的微秒数的。另外的一个常用函数getTime()用来获取Date对象的微秒数。
值得一提的是Date对象的toString()方法,这个方法在将Date转化为String过程中,会将微秒数表示的格林尼治时间转化到系统的默认时区时间。这就不难解释前文中System.out.println(today)会输出Wed Jun 01 08:00:00 CST 2016了:Date today = new Date(curMillis - curMillis % MICROSECONDS_PER_DAY)得到的是格林威治时间的零点,在输出时根据当前时区(东八区)加了8个小时。

Calendar

Calendar是Java提供的一个日期时间类,相对于Date类中废弃的大部分构造器和函数,Calendar类提供了更多的功能。通常使用Calendar now = Calendar.getInstance();获取当前日期和时间。Calendar也是基于与1970年1月1日 00:00:00的毫秒数计时,getInstance()方法中也使用了System.curentMilliseconds()作为当前毫秒数。
可以通过Calendar对象的getTime()方法获得对应的Date对象,使用getTimeInMillis()方法获得对应的毫秒数。Calendar对象可以通过setTime()方法设置日期和时间。

DataFormat

Date可以按照指定的格式输出对应的字符串,也可以将字符串表示的日期和时间转化为Date对象,它们之间转换的桥梁是DateFormat,常用的DateFormat是SimpleDataFormat。

1
2
3
DateFormat dateFormat = new SimapleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println(dateFormat.format(date)); //2016-06-02 08:31:50
1
2
3
4
5
6
7
DateFormat dateFormat = new SimpleDateFormat("yy-M-d hh:mm:s");
try {
System.out.println(dateFormat.parse("16-6-2 08:35:12"));
//Thu Jun 02 08:35:12 CST 2016
} catch (ParseException e) {
e.printStackTrace();
}

值得注意的是:Calendar.HOUR表示12小时制,对应DateFormat格式小时位置的”hh”;Calendar.HOUR_OF_DAY表示24小时制,对应DateFormat格式小时位置的”HH”。Calendar.HOUR的表示范围是1-12,Calendar.HOUR_OF_DAY的表示范围是0-23。如果通过Calendar.HOUR设置小时为0点,并通过设置为”hh”的DateFormat打印,小时位将显示12.

通过以上分析,需要对日期和时间进行操作时,Calendar提供了最为完善的支持,有些操作只能通过Calendar进行,例如涉及到地域和时区的操作等。Date一般用于在数据库、前端等场合对日期时间进行传输,类似于Java中日期和时间的DTO对象。Calendar与Date之间、Date与String之间可以进行非常方便的转换。

针对本文开头提出的需求,可以通过Calendar对象获取前一天的第一刻和最后一刻。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Date getFirstMoment(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.Date, -1);
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}

public Date getLastMoment(Date date){
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.Date, -1);
calendar.set(Calendar.HOUR, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTime();
}

参考资料

  1. 百度百科-时区

  2. Java 7 Doc

  3. SimpleDateFormat