时间(二)之核心类
上一篇时间(一)文章,我们已经了解学习了时间相关的一些概念和时间相关的其他知识,那么本篇文章开始我们深入在计算机领域中关于时间的相关核心类的知识,先来让我们看看 JSR-310(JDK8+) 和之前(JDK7 之前)的比较
JDK7
Date
Date 是 Java 最早提供用来封装日期时间的类,由于不易于国际化且参数计算不符合日常认知或不正确,很多获取年、月、日、小时等数据的方法都已废弃(@Deprecated),被 Calendar 类的方法替代
Date 类有两个关键的成员变量
1 | // 记录当前时间戳 |
Date 类提供了两个构造函数
1 | // 无参构造方法,创建当前时间的 Date 类 |
静态方法
System.currentTimeMillis()
返回 UTC 时间从 1970-01-01 00:00:00 到现在的总毫秒数,返回类型为 long,也是我们最常见获取当前时间的方法
java.sql.Date
,java.sql.Time
,java.sql.Timestamp
类都继承自 java.util.Date
类,是专门用于数据库连接的,由于是继承关系,从数据结构来看它们和父类的区别不大。
主要区别在于 Timestamp
类可以表示至纳秒级,其 fastTime
字段从秒之后被截断,毫秒至纳秒精度保持在特有的 nanos
字段中,需要注意 Timestamp
类的纳秒进度可能是 假的
1 | package java.sql.Timestamp; |
可以看出,在 fastTime
字段强行截断后,进行毫秒值直接乘以 1000000 的操作后赋给了 nanos 字段,成为了 “只能表示到毫秒的纳秒级精度” 。当然,还可以通过 setNanos(int n)
方法给纳秒数赋精确值。
虽然从数据结构上看没有什么特别,但如果涉及到 Timestamp 类的父子类型转换或时间比较,就需要注意这里的“坑”
-
equals() 方法的不对称性
java.sql.Timestamp
类和其父类java.util.Date
的 equals() 方法是不符合对称性1
2
3
4
5
6
7
8
9
10public static void main(String[] args){
Data date = new Date();
Timestamp timestamp = new Timestamp(date.getTime());
System.out.println(date.equals(timestamp));
System.out.println(timestamp.equals(date));
}
// 输出结果
true
false -
时间比较类方法的 “异常” 现象如下,两个有毫秒之差的时间点,
after()
方法返回不符合客观事实1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args){
Long current = System.currentTimeMillis();
Date t1 = new Timestamp(current);
Date t2 = new Timestamp(current + 1);
System.out.println(t2.after(t1));
System.out.println(t2.compareTo(t1) > 0);
}
// 输出结果
false
true
如果在不确定类型的情况下进行时间的比较,尽量使用 compareTo() 方法,可以保证正确性
Calendar
Calendar 类是一个日历抽象类,提供了一组对年月日时分秒星期等日期信息操作的函数,并针对不同国家和地区的日历提供了相应的子类,即本地化(比如:公历【GregorianCalendar】,佛历【BuddhistCalendar】,日本历【JapaneseImperialCalendar】等)
从 JDK1.1 版本开始,在处理日期和时间时系统推荐使用 Calendar 类进行实现。在设计上,Calendar 类的功能要比 Date 类强大太多,而且在实现方式上也比 Date 类要复杂些
Calendar 类可以通过静态工厂方法或 new 子类的方式来获得实例
-
getInstance() 方法,有四个重载方法,参数是时区和地区,如果不传会取服务器默认的时区和地区(地区的出现是专门为了区分泰国和日本)
- getInstance()
- getInstance(TimeZone zone)
- getInstance(Locale aLocale)
- getInstance(TimeZone zone, Locale aLocale)
-
新建子类对象
1
Calendar calendar = new GregorianCalendar();
Calendar 类可以实现带时区的年月日时分秒星期等对 Unix 时间戳的转换,内部通过子类复杂的 computeTime() 方法进行计算。
-
使用 getTime() 方法返回 java.util.Date 类型的时间
-
使用 getTimeInMillis() 方法返回当前 Unix 时间戳
-
通过 get(int field) 方法获取其他年月日等单独信息
常量 含义 Calendar.YEAR 年份 Calendar.MONTH 月份 Calendar.DATE 日期 Calendar.DAY_OF_MONTH 日期,和上面字段意义完全相同 Calendar.HOUR 12 小时制的小时 Calendar.HOUR_OR_DAY 24 小时制的小时 Calendar.MINUTE 分钟 Calendar.SECOND 秒 Calendar.DAY_OF_WEEK 星期几 Calendar.DAY_OF_YEAR 今年的第几天
也可以通过多个 set 重载方法设定各种值,同时,add() 方法支持对单个值的加减,从而实现时间推移的计算,传入负数即为减
1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args){
Calendar calendar = Calendar.getInstance();
System.out.println("当前日期: " + calendar.getTime());
calendar.add(Calendar.DATE, 10);
System.out.println("日期+10: " + calendar.getTime());
calendar.add(Calendar.DATE, -5);
System.out.println("日期-5: " + calendar.getTime());
}
// 输出结果
当前日期: Fri Apr 10 15:37:46 CST 2020
日期+10: Mon Apr 20 15:37:46 CST 2020
日期-5: Wed Apr 15 15:37:46 CST 2020 -
GregorianCalendar
对象可以直接使用 isLeapYear(int year)
接口判断是否闰年。需要注意两点
- Calendar 中 MONTH 的范围: 0-11(Calendar.JANUARY - Calendar.DECEMBER),因此为避免使用错,Calendar.JANUARY 来表示一月
- Calendar 中 DAY_OF_WEEK 星期日是 1,并不是我们习惯的星期一是 1
SimpleDateFormat
SimpleDateFormat
是一个以语言环境敏感的方式来格式化和分析日期的类,SimpleDateFormat允许选择任何用户自定义的日期时间格式来格式化
1 | public static void main(String[] args){ |
字符含义
日期和时间格式由日期和时间模式字符串中的日期和时间模式字符串指定,从 A
到 Z
和从 a
到 z
的无引号字母被解释为表示日期或时间字符串组件的模式字母。可以使用单引号'
引用文本以避免解释。"''
"表示单引号。所有其他字符都不会被解释;它们只是在格式化期间复制到输出字符串中,或者在解析期间与输入字符串匹配。
定义了以下模式字母(所有其他字符A
— Z
和 a
- z
保留),详细说明见 SimpleDateFormat
类 Java Doc 中注释明
字母 | 日期或时间组成 | 说明 | 示例 |
---|---|---|---|
G | Era designator【是一个代号】 | Text | AD |
y(小写) | Year【年】 | Year | 1996; 96 |
Y | Week year【周年】 | Year | 2009; 09 |
M | Month in year (context sensitive)【一年中的月份(上下文相关)】 | Month | July; Jul; 07 |
L | Month in year (standalone form)【一年中的月份(独立形式)】 | Month | July; Jul; 07 |
w(小写) | Week in year【一年中的第几周】 | Number | 27 |
W | Week in month【每月的周】 | Number | 2 |
D | Day in year【一年中的一天】 | Number | 189 |
d(小写) | Day in month【每月的一天】 | Number | 10 |
F | Day of week in month【每月的星期几】 | Number | 2 |
E | Day name in week【星期几】 | Text | Tuesday; Tue |
u(小写) | Day number of week (1 = Monday, …, 7 = Sunday) 【星期几(1 =星期一,…,7 =星期日)】 | Number | 1 |
a(小写) | Am/pm marker【上午/下午标记】 | Text | PM |
H | Hour in day (0-23)【一天中的小时,0-23表示】 | Number | 0 |
k(小写) | Hour in day (1-24)【一天中的小时, 1-24表示】 | Number | 24 |
K | Hour in am/pm (0-11)【上午/下午 0-11表示】 | Number | 0 |
h(小写) | Hour in am/pm (1-12)【上午/下午 1-12表示】 | Number | 12 |
m(小写) | Minute in hour【一小时内】 | Number | 30 |
s(小写) | Second in minute【分钟】 | Number | 55 |
S | Millisecond【毫秒】 | Number | 978 |
z(小写) | Time zone【时区】 | General time zone | Pacific Standard Time; PST; GMT-08:00 |
Z | Time zone【时区】 | RFC 822 time zone | -0800 |
X | Time zone【时区】 | ISO 8601 time zone | -08; -0800; -08:00 |
格式化示例
给定太平洋时间,2001-07-04 12:08:56,以下示例展示按照自定的格式显示时间
日期和时间模式 | 结果 |
---|---|
“yyyy.MM.dd G ‘at’ HH:mm:ss z” | 2001.07.04 AD at 12:08:56 PDT |
“EEE, MMM d, ''yy” | Wed, Jul 4, '01 |
“h:mm a” | 12:08 PM |
“hh ‘o’‘clock’ a, zzzz” | 12 o’clock PM, Pacific Daylight Time |
“K:mm a, z” | 0:08 PM, PDT |
“yyyyy.MMMMM.dd GGG hh:mm aaa” | 02001.July.04 AD 12:08 PM |
“EEE, d MMM yyyy HH:mm:ss Z” | Wed, 4 Jul 2001 12:08:56 -0700 |
“yyMMddHHmmssZ” | 010704120856-0700 |
“yyyy-MM-dd’T’HH:mm:ss.SSSZ” | 2001-07-04T12:08:56.235-0700 |
“yyyy-MM-dd’T’HH:mm:ss.SSSXXX” | 2001-07-04T12:08:56.235-07:00 |
“YYYY-'W’ww-u” | 2001-W27-3 |
TimeZone
JDK8+
Clock
Instant
LocalDate
LocalTime
LocalDateTime
OffsetTime
OffsetDateTime
ZonedDateTime
ZoneId
Year
Month
DayOfWeek
MonthDay
YearMonth
总结
附录
- Java SE 8 日期和时间
- 循序渐进解读计算机中的时间—应用篇(上)
- 循序渐进解读计算机中的时间—应用篇(下)
- JSR310新日期API(二)-日期时间API
- Java中日期时间相关核心类
- 《Java 8 实战》● 第 12 章