Date、Calendar和TimeZone
# 51.Date、Calendar和TimeZone
简单聊聊Date、Calendar和TimeZone
# Date
基本使用
前面我们说过,Java使用long类型来存储时间戳,我们来看看Date的源码,验证下:
public class Date implements java.io.Serializable, Cloneable, Comparable<Date>{
private transient long fastTime;
}
2
3
现在我们演示下其基本用法,比较简单,直接列出代码:
Date date = new Date();
System.out.println(date.getYear()); //返回年份
System.out.println(date.getMonth()); //返回月份
System.out.println(date.getDate()); //返回天数
System.out.println(date.toString()); //时间的String类型
System.out.println(date.toGMTString()); //GMT时区下的时间
System.out.println(date.toLocaleString()); //当地时间
2
3
4
5
6
7
编译和运行(笔者运行的时候是2022-12-21 20:43左右):
> javac .\TestDate.java
注: .\TestDate.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
> java TestDate
122
11
21
Wed Dec 21 20:43:18 CST 2022
21 Dec 2022 12:43:18 GMT
2022-12-21 20:43:18
2
3
4
5
6
7
8
9
10
11
首先我们看到,编译的时候就提示我们用了已过时的API。
接下来我们来看看输出的内容,并讲解。
# date.getYear()
首先是date.getYear()
的输出,顾名思义,是输出年份的,但为什么是122?运行的时候不是2022年吗?相差了1900年!我们可以看看其源码和注释:
/**
* Returns a value that is the result of subtracting 1900 from the
* year that contains or begins with the instant in time represented
* by this Date object, as interpreted in the local
* time zone.
*
* @return the year represented by this date, minus 1900.
* @see java.util.Calendar
* @deprecated As of JDK version 1.1,
* replaced by Calendar.get(Calendar.YEAR) - 1900.
*/
@Deprecated
public int getYear() {
return normalize().getYear() - 1900;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以看到其返回的是一个整数,并且是根据从1900年开始到今年,过了多少年来计算的(是不是很令人无语的设计🤔)。所以我们如果要获取当前年份,得加上1900:
date.getYear() + 1900;
# date.getMonth()
下面我们来看看date.getMonth()
,其用来输出当前月份,为什么是输出11?运行的时候明明是12月! 老规矩,我们先看看注释和源码
/**
* Returns a number representing the month that contains or begins
* with the instant in time represented by this <tt>Date</tt> object.
* The value returned is between 0 and 11,
* with the value 0 representing January.
*
* @return the month represented by this date.
* @see java.util.Calendar
* @deprecated As of JDK version 1.1,
* replaced by <code>Calendar.get(Calendar.MONTH)</code>.
*/
@Deprecated
public int getMonth() {
return normalize().getMonth() - 1; // adjust 1-based to 0-based
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注释的大意是说,0表示1月,1表示2月…… 11表示12月 (这设计就更离谱了吧🙄,跟JavaScript里的时间的设计有的比)。所以我们如果要获取当前月份,得加上1:
date.getMont() + 1;
# date.getDate()
date.getDate()
是输出天数,这里输出的是21,和运行的时候2022年12月21日一致,返回的日期范围是1
~31
,不做多解释。
# date.toString()
date.toString()
就是返回一串字符串,其格式在不同计算机下可能不同。我们可以使用一个格式化工具类SimpleDateFormat ,精确的控制其格式,具体我们后面再说。
# date.toGMTString()
date.toGMTString()
返回的是格林尼治时间的时间字符串,这里不多解释。
# Date.parse()
获取毫秒
可以通过Date.parse()
来获取毫秒
long t = Date.parse("Mon 6 Jan 1997 13:3:00");
System.out.println(t); //852526980000
2
Date.parse接受什么格式的字符串呢?很多,规则也很复杂,读者记住一两个格式就行。感兴趣的可以看看官网文档Date (Java Platform SE 8 ) (opens new window),
如果输入的格式有问题,会抛出参数不合法的异常,注意捕获:
Exception in thread "main" java.lang.IllegalArgumentException
at java.util.Date.parse(Date.java:617)
at TestDate.main(TestDate.java:19)
2
3
# 其他构造方法
还可以通过如下方式创建Date对象:
Date(int year, int month, int date);
Date(int year, int month, int date, int hrs, int min)
Date(int year, int month, int date, int hrs, int min, int sec)
Date(long date) //毫秒数,可以通过Date.parse()来获取毫秒
Date(String s) //s - 日期的字符串表示形式,和Date.parse(String s)中s的格式相同
2
3
4
5
注意
- year的值需要-1900. 例如你想设置2022年,得传入2022-1900 = 122年
- month的值域为0~11,0代表1月,11表代表12月
- date的值域在1~31之间,min和sec的值域在0~59之间
# Date类小结
一番演示下来,可以看到除了其API不好用之外,其实Date
对象有几个严重的问题:它不能转换时区,除了toGMTString()
可以按GMT+0:00
输出外,Date总是以当前计算机系统的默认时区为基础进行输出。
另外,Date对象没有提供时间相关运算的API,例如对日期和时间进行加减、计算相差的天数等
# Calendar基本使用
Calendar
可以用于获取并设置年、月、日、时、分、秒,它和Date
比,主要多了一个可以做简单的日期和时间运算的功能。
我们还是先演示下其基本用法:
Calendar c = Calendar.getInstance(); //创建Calendar实例
int y = c.get(Calendar.YEAR); //获取年份
int m = c.get(Calendar.MONTH) + 1; //获取月份
int d = c.get(Calendar.DAY_OF_MONTH); //天数
int w = c.get(Calendar.DAY_OF_WEEK); //星期几
int hh = c.get(Calendar.HOUR_OF_DAY); //小时
int mm = c.get(Calendar.MINUTE); //分钟
int ss = c.get(Calendar.SECOND); //秒数
int ms = c.get(Calendar.MILLISECOND); //毫秒数
System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms);
2
3
4
5
6
7
8
9
10
输出:
2022-12-21 4 21:51:21.155
这里就不一一说明各个方法了,很多方法看名字就知道其用处,也加了注释。
这里需要注意的:
-
Calendar
只有一种方式获取,即Calendar.getInstance()
,而且一获取到就是当前时间... 其构造方法是protected,无法new。 - 返回的月份仍然要加1…… 😒
- DAY_OF_WEEK返回的星期几,但是其值范围是
1
~7
分别表示周日,周一,……,周六。所以2022-12-21是星期三,但是输出的4 (还是令人无语的设计😶)
# 设置Calendar
由于Calendar
只有一种方式获取,如果我们想要指定时间,怎么做呢?用set方法。
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 2023); //设置年份
c.set(Calendar.MONTH, 8); //设置月份,注意8表示9月....
c.set(Calendar.DATE, 2); //设置天数
//设置时分秒
c.set(Calendar.HOUR_OF_DAY, 21);
c.set(Calendar.MINUTE, 30);
c.set(Calendar.SECOND, 45);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(c.getTime())); //2023-09-02 21:30:45
//一步到位:
c.set(2022,11,22,7,48,00);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
其他注意事项:
- 设置小时,如果使用HOUR_OF_DAY设置的是24小时制,使用HOUR表示的是12小时制。
- 也可以一次性设置完所有字段:
c.set(2019, 10, 20, 8, 15, 0);
- 设置毫秒时,注意毫秒的范围是0-999 (1秒 = 1000毫秒)。如果超过999会怎么样呢?就会自动转为秒。。。。 例如
c.set(Calendar.MILLISECOND, 2000);
,就会增加2秒。set其他字段同理
# Calendar.getTime()
直接打印Calendar对象,其输出并不是时间,而是其内部的一些属性,例如:
java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai"...............
所以我们可以通过Calendar的getTime方法返回一个Date对象,并打印。
# 重置Calendar
如果想要重置Calendar呢?可以用clear方法,这样Calendar是从1970-1-1 00:00:00开始
Calendar c = Calendar.getInstance();
c.clear();
System.out.println(c.getTime());//Thu Jan 01 00:00:00 CST 1970
2
3
# 对日期进行加减
Calendar可以对日期进行简单的加减
Calendar c = Calendar.getInstance();
c.set(2022,11,22,7,48,00);
System.out.println(sdf.format(c.getTime())); //2022-12-22 07:48:00
c.add(Calendar.DAY_OF_MONTH, 5); //加5天
c.add(Calendar.SECOND, -1); //减一秒
System.out.println(sdf.format(c.getTime())); //2022-12-27 07:47:59
2
3
4
5
6
7
# 比较Calendar对象
可以用compareTo方法比较两个Calendar对象。小于返回-1,相等(年月日时分秒和毫秒都相等)则返回0,大于则返回1
Calendar c = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
System.out.println(c.compareTo(c2)); //-1
2
3
由于代码运行需要一定的时间,所以如果是不同的创建语句,创建出来的对象基本上毫秒数是不同的。
# TimeZone类
和Calendar
和Date
相比,TimeZone提供了时区转换的功能。
# TimeZone
基本概念
TimeZone tzDefault = TimeZone.getDefault(); //当前时区
TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00"); //GMT + 9:00 时区
TimeZone tzNewYork = TimeZone.getTimeZone("America/New_York"); //纽约时区
System.out.println(tzDefault.getID()); //Asia/Shanghai
System.out.println(tzGMT9.getID()); //GMT+09:00
System.out.println(tzNewYork.getID()); //America/New_York
2
3
4
5
6
7
时区的唯一标识是以字符串表示的ID,我们获取指定TimeZone
对象也是以这个ID为参数获取,GMT+09:00
、Asia/Shanghai
都是有效的时区ID。
要列出系统支持的所有ID,可以用TimeZone.getAvailableIDs()
,
String str[] = TimeZone.getAvailableIDs();
System.out.println(str.length); //2022年12月22日运行的时候,是628个
for (int i = 0; i < str.length; i++) {
System.out.println(str[i]);
}
2
3
4
5
# 根据时区转换时间
举个例子,将北京时间转为纽约时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar c = Calendar.getInstance();
c.clear();
c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
c.set(2022, 11,22,7,57,00);
System.out.println("北京时间: " + sdf.format(c.getTime()));
//北京时间: 2022-12-22 07:57:00
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
System.out.println("纽约时间: " + sdf.format(c.getTime()));
//纽约时间: 2022-12-21 18:57:00
2
3
4
5
6
7
8
9
10
11
12
说明下时区转换的步骤:
- 获取Calendar实例,清除所有字段并设置时间
- 创建
SimpleDateFormat
并设定目标时区 - 通过
SimpleDateFormat
显示转换后的时间
# SimpleDateFormat
我们可以用SimpleDateFormat来控制Date类型的打印格式:
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date)); //format返回的是一个String类型
2
3
这里的yyyy-MM-dd HH:mm:ss 分别指年份、月份、日期、小时、分钟和秒数。编译和运行:
2022-12-21 21:23:17
SimpleDateFormat的参数的更多说明,可以看文档:SimpleDateFormat (Java SE 12 & JDK 12 ) (opens new window)
也可以使用SimpleDateFormat来转换字符串为Date对象:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = "2022-05-21 10:59:27";
Date date = sdf.parse(dateStr);
2
3
需要注意的是,SimpleDateFormat
是线程不安全的。
# 小结
Date类常用方法:
构造方法
Date();
Date(int year, int month, int date);
Date(int year, int month, int date, int hrs, int min)
Date(int year, int month, int date, int hrs, int min, int sec)
Date(long date) //毫秒数,可以通过Date.parse()来获取毫秒
Date(String s) //s - 日期的字符串表示形式,和Date.parse(String s)中s的格式相同常用方法
date.getYear() 返回年份,注意得加上1900
date.getMonth() 返回月份,注意0表示1月,1表示2月……
date.getDate() 返回天数,范围是0~31
date.toString() 返回时间的字符串,默认是西方的格式
date.toGMTString() 返回GMT时区的时间
date.toLocaleString() 返回当地时间
Date.parse(String s) 获取时间的毫秒数
Calendar类常用方法:
Calendar c = Calendar.getInstance(); //唯一创建Calendar实例的方法
常用方法
c.getTime() 返回Date对象
c.get(Calendar.YEAR); //获取年份
c.get(Calendar.MONTH) + 1; //获取月份,注意0表示1月……
c.get(Calendar.DAY_OF_MONTH); //获取天数
c.get(Calendar.DAY_OF_WEEK); //获取星期几,注意1表示星期日,2表示星期一...
c.get(Calendar.HOUR_OF_DAY); //获取小时
c.get(Calendar.MINUTE); //获取分钟
c.get(Calendar.SECOND); //秒数
c.get(Calendar.MILLISECOND); //毫秒数设置Calendar
c.set(2022,11,22,7,48,00);
c.set(Calendar.YEAR, 2023);
c.set(Calendar.MONTH, 8);
c.set(Calendar.DATE, 2);
c.set(Calendar.HOUR_OF_DAY, 21);
c.set(Calendar.MINUTE, 30);
c.set(Calendar.SECOND, 45);
c.clear(); 重置为1970-1-1 0点0分,可用于初始化加减
c.add(Calendar.DAY_OF_MONTH, 5); //加5天
c.add(Calendar.SECOND, -1); //减一秒比较:c.compareTo(c2)
TimeZone :可用于转换时区,根据SimpleDateFormat格式化输出
构造方法:
TimeZone.getDefault(); //当前时区
TimeZone.getTimeZone(时区ID) 例如"GMT+09:00" "America/New_York"
TimeZone.getAvailableIDs() 获取所有时区ID
SimpleDateFormat
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(String s) ;返回一个Date对象
sdf.setTimeZone(TimeZone对象) 设置时区