Date、Calendar 和 TimeZone
# 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 对象)
设置时区