【Java常用类】1-8 Date, SimpleDateFormat 和 Calendar
§1-8 Date
, SimpleDateFormat
和 Calendar
1-8.1 JDK 7 前的时间相关类
在 JDK 7 以前,Java API 中所提供的时间相关类如下所示
类 | 简要描述 |
---|---|
Date |
时间 |
SimpleDateFormat |
格式化时间 |
Calendar |
日历 |
其中,Date
和 Calendar
都位于 java.util
包下,而 SimpleDateFormat
继承自抽象类 DateFormat
,后者也继承自抽象类 Format
,全部位于 java.text
包下。
有关时间的小知识:
- 全世界的时间,拥有一个统一的计算标准;
- 1884 年,将格林尼治时间(GMT, Greenwich Mean Time)认为是世界标准时间;
- GMT 的计算核心:将一天划分为 24 小时,太阳直射时为正午 12 时;
- 位于本初子午线东侧的地区为东部地区,时间基于 GMT 相加;位于子午线西侧的地区为西部地区,时间基于 GMT 相减;
- 共有 24 个时区,其中包含 23 个整时区和 2 个半时区,中国位于东八区,即 GMT+8;
- 由于地球自转速度不均,当前时刻与实际时刻存在误差,后来诞生了协调世界时(UTC, Coordinated Universal Time),基于铯原子的振动频率计算得出,成为目前的世界标准时间;
- 常用的时间单位换算关系:\(1 \text{s} = 10^3 \text{ms}, 1 \text{ms} = 10^3 \mathrm{\mu s}, 1 \mathrm{\mu s} = 10^3 \text{ns}\) ;
1-8.2 Date
类
Date
类用于表示某个具体的时刻,精确到毫秒。
构造方法:
构造方法 | 描述 |
---|---|
Date() |
无参构造,返回实例化对象时刻的 Date 对象 |
Date(long date) |
基于所给时间创建一个 Date 对象 |
注意:
Date(long date)
所接受的参数是指自 1970.1.1 00:00:00 GMT 起所经历的毫秒数;- 传入年、月、日(时、分、秒)的构造方法自 JDK 1.1 起被弃用,相关方法已由
Calendar
类中的静态方法set(year + 1900, month, date, hrs, min, sec)
,或GregorianCalendar(year + 1900, month, date, hrs, min, sec)
取代; - 传入日期字符串的构造方法自 JDK 1.1 起被弃用,代之以
DateFormat.parse(String s)
; - 所有与年月日时分秒参数相关的方法都已被弃用,代之以
Calendar
中的方法,详见官方文档; - 所有与格式化有关的方法(包含解析方法和日期字符串构造时间对象的方法)都已被弃用,代之以
DateFormat
中的方法,详见官方文档;
常用方法:
方法 | 描述 |
---|---|
boolean after(Date when) |
测试该日期是否晚于指定日期 |
boolean before(Date when) |
测试该日期是否早于指定日期 |
int compareTo(Date anotherDate) |
比较两个日期的时间顺序 |
void setTime(long time) |
设置该对象中的时间 |
long getTime() |
返回时间 |
注意:
- 上表所述的 ”时间“,指的都是自 1970.1.1 00:00:00 GMT 起所经历的毫秒数;
- 该类重写了
toString()
方法,打印出来的日期字符串格式为dow mon dd hh:mm:ss zzz yyyy
,即星期 月份 日期 时:分:秒 时区 年份
;
案例演示:
要求:
- 打印自时间原点开始一年后的时间;
- 定义两个任意
Date
对象,比较二者孰前孰后;
演示:
import java.util.Date;
import java.util.Random;
public class DateTest {
public static void main(String[] args) {
//打印自时间原点一年后的时间
Date d1 = new Date(31_536_000_000L);
System.out.println("自时间原点一年后的时间:" + d1);
//比较两个任意时间的先后顺序
Random rd = new Random(System.currentTimeMillis());
d1.setTime(Math.abs(rd.nextInt()));
Date d2 = new Date(Math.abs(rd.nextInt()));
System.out.println("新的时间 1:" + d1);
System.out.println("新的时间 2:" + d2);
System.out.print("二者先后:");
if (d1.equals(d2)) {
System.out.println("二者相同。");
} else if (d1.before(d2)) {
System.out.println("d2 在后,d1 在前。");
} else {
System.out.println("d1 在后,d2 在前。");
}
}
}
运行得到
自时间原点一年后的时间:Fri Jan 01 08:00:00 CST 1971
新的时间 1:Fri Jan 16 10:01:34 CST 1970
新的时间 2:Sun Jan 25 07:45:18 CST 1970
二者先后:d2 在后,d1 在前。
1-8.3 SimpleDateFormat
类
SimpleDateFormat
类是一个具体类,用于针对不同地区格式化和解析日期。格式化,即从日期到文字;解析,即从文字到日期。
因此,SimpleDateFormat
主要工作就是格式化(把时间变为所需格式)和解析(把文字所述时间变为 Date
对象)。
构造方法:
构造方法 | 描述 |
---|---|
SimpleDateFormat() |
使用默认的模式和日期格式标识,为默认地区创建对象 |
SimpleDateFormat(String pattern) |
使用指定的模式,默认的日期格式标识,为默认地区创建对象 |
SimpleDateFormat(String pattern, Locale locale) |
使用指定的模式,默认的日期格式标识,为指定地区创建对象 |
常用方法:
方法 | 描述 |
---|---|
Date parse(String text, ParsePosition pos) Date parse(String text) |
从字符串中的指定索引位置解析文本,产生一个新的 Date 对象 |
StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) StringBuffer format(Date date) |
格式化指定的日期/时间,将结果追加到指定的 StringBuffer 对象中 |
void applyPattern(String pattern) |
应用指定的新模式字符串,格式化日期 |
注意:
- 若成功解析文本,
pos
将更新到最后一个使用的字符后(解析并不一定使用至末尾的所有字符),并返回解析的对象,更新后的pos
可用于指示下次调用该方法时的起始位置; - 若解析文本失败,
pos
索引不变,且将位于发生错误的字符索引处,并返回null
,抛出ParseException
(声明于超类); - 解析文本时,所使用的模式必须与所给字符串模式相同;
- 所有的日期/时间模式将在下文列出,其中常用的为
y, M, d, H, m, s
;
日期/时间模式:
字母 | 日期或时间组成成分 | 表示 | 示例 |
---|---|---|---|
G |
纪元 | 文本 | AD |
y |
年份 | 年份 | 1996; 96 |
Y |
Week year | 年份 | 2009; 09 |
M |
年中月份(上下文敏感) | 月份 | July; Jul; 07 |
L |
年中月份(独立形式) | 月份 | July; JUl; 07 |
w |
年中周 | 数字 | 27 |
W |
月中周 | 数字 | 2 |
D |
年中日 | 数字 | 189 |
d |
月中日 | 数字 | 10 |
F |
月中星期 | 数字 | 2 |
E |
星期名字 | 文本 | Tuesday; Tue |
u |
星期数字编号 | 数字 | 1 |
a |
Am/pm 标志 | 文本 | PM |
H |
一天中小时(0~23) | 数字 | 0 |
k |
一天中小时(1-24) | 数字 | 24 |
K |
am/pm 小时(0~11) | 数字 | 0 |
h |
am/pm 小时(1-12) | 数字 | 12 |
m |
小时中的分钟 | 数字 | 30 |
s |
分钟中的秒数 | 数字 | 55 |
S |
毫秒 | 数字 | 978 |
z |
时区 | 一般时区 | Pacific Standard Time; PST; GMT-08:00 |
Z |
时区 | RFC 822 时区 | -0800 |
X |
时区 | ISO 8601 时区 | -08; -0800; -08:00 |
注意:
- 在字符串中,无引号括起来的字母
A-Z
和a-z
会解释为字符串中表示不同日期和时间的组成成分; - 使用单引号可避免被解析,
"''"
表示一个单引号,其余所有字符都不会被解析,而是会被复制到输出字符串中; - 模式字母通常会重复使用,它们的个数决定了具体的表示;
案例演示:
要求:
- 给定一个年月日
2000-11-11
,用字符串表示该数据,转换为2000年11月11日
; - 秒杀活动自 2023.11.11 00:00:00 至 2023.11.11 00:10:00 开始,小明下单付款时间为 2023.11.11 00:01:00,小红下单付款时间为 2023.11.11 00:11:00。在代码层面判断二人是否成功参加活动。
演示:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Practices {
public static void main(String[] args) throws ParseException {
//案例:
//1. 转换 2000-11-11 :xxxx年-xx月xx日
String str1 = "2000-11-11";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date parse1 = sdf1.parse(str1);
sdf1.applyPattern("yyyy年MM月dd日");
String str2 = sdf1.format(parse1);
System.out.println(str2);
}
}
运行,得到
2000年11月11日
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Practices {
public static void main(String[] args) throws ParseException {
//2. 秒杀活动
//秒杀时间为 2023.11.11 00:00:00 ~ 00:10:00
//小明下单付款时间 2023.11.11 00:01:00
//小红下单付款时间 2023.11.11 00:11:00
//用代码说明二位是否成功参加
SimpleDateFormat sfd = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
String startStr = "2023.11.11 00:00:00";
String endStr = "2023.11.11 00:10:00";
String xiaoming = "2023.11.11 00:01:00";
String xiaohong = "2023.11.11 00:11:00";
//一种没有可读性的写法:偷懒
long startTime = sfd.parse(startStr).getTime();
long period = sfd.parse(endStr).getTime() - startTime;
long xiaomingTime = sfd.parse(xiaoming).getTime() - startTime;
long xiaohongTime = sfd.parse(xiaohong).getTime() - startTime;
System.out.println(((xiaomingTime <= period) ? "小明成功参加" : "小明未能参加"));
System.out.println(((xiaohongTime <= period) ? "小红成功参加" : "小红未能参加"));
}
}
运行得到
小明成功参加
小红未能参加
1-8.4 Calendar
类
Calendar
是一个抽象类,其内部提供一些具体时间与日历字段(年、月、日等)相互转换、操作日历字段的方法。
Calendar
代表了系统当前时间的日历对象,可以单独修改、获取时间中的年、月、日等字段。
该类的构造方法不可以被直接调用,要想获取实例,只能使用其静态方法。
静态方法:
静态方法 | 描述 |
---|---|
Calendar getInstance() |
使用默认时区和地区获取一个日历实例 |
Locale[] getAvailableLocales() |
返回地区数组,包含 getInstance() 方法所能支持的所有本地化实例 |
常用方法:
方法 | 描述 |
---|---|
final Date getTime() |
返回一个 Date 对象,表示该 Calendar 的时间值(与时间原点的偏移量) |
final void setTime(Date date) |
使用指定 Date 日期设置时间 |
long getTimeInMillis() |
返回该日历的毫秒时间值 |
void setTimeInMillis(long millis) |
设置该日历的时间 |
int get(int field) |
返回指定日历字段的值 |
void set(int field, int value) |
使用指定值设置日历中字段的值 |
void add(int field, int amount) |
基于日历规则,在指定字段上加或减指定数量 |
final Instant toInstant() |
将对象转换为 Instant 对象 |
注意:
-
getInstance()
方法会根据系统的不同时区创建不同的日历,一般而言,创建的是格里高利日历(Gregorian Calendar); -
Calendar
会把时间当中的纪元、年、月、日、时、分、秒、星期等信息放在一个数组中存储;- 可以通过向控制台打印该实例查看该实例中所存储的信息;
- 月份字段的范围为 \([0,11]\),以此表示一月到十二月;
- 一般而言,一周的第一天为周日;星期字段的表示范围为 \([1,7]\),分别从一周的第一天开始至最后一天;可用
int getFirstDayOfWeek()
查看,在美国,一周第一天为周日,在法国为周一;
-
使用
get()
和set()
方法时,field
字段对应的实际上是数组的索引,若越界,则会抛出异常ArrayIndexOutOfBoundsException
,索引所对应内容为:索引 对应公有静态常量 字段 0
ERA
纪元 1
YEAR
年 2
MONH
月 3
WEEK_OF_YEAR
一年中的周 4
WEEK_OF_MONTH
一月中的周 5
DAY_OF_MONTH
,DATE
一月中的日 6
DAY_OF_YEAR
一年中的日 7
DAY_OF_WEEK
一周第几天 8
DAY_OF_WEEK_IN_MONTH
本月第几个周几 9
AM_PM
AM / PM 10
HOUR
时(12小时制) 11
HOUR_OF_DAY
时(24小时制) 12
MINUTE
分 13
SECOND
秒 14
MILLISECOND
毫秒 15
ZONE_OFFSET
与 GMT 的毫秒偏移量 16
DST_OFFSET
夏令时 -
set()
和add()
方法传入新值时,若数据发生溢出(例如 13 月、2 月 30 日、星期循环等),则会自动往后(前)顺延;