Java日期时间
Date和Calendar
在计算机中,以整数存储的时间戳(从1970年1月1日零点到现在所经历的秒数)表示某一个时刻,它打印出的各种各样的时间表示,则是数据的展示。展示有多种形式,本质上就是一个转换方法。
1 | String toDisplay(int n){...} |
Epoch Time
又称为时间戳,在不同的编程语言中,会有几种存储方式:
- 以秒为单位的整数:1574208900,缺点是精度只能到秒;
- 以毫秒为单位的整数:1574208900123,最后3位表示毫秒数;
- 以秒为单位的浮点数:1574208900.123,小数点后面表示零点几秒
Java标准库API
提供了两套处理日期和时间的API:
java.util
包,主要包括Data
,Calendar
,TimeZone
- Java8新API定义在
java.time
包,主要包括LocalDateTime
,ZonedDateTime
,ZoneId
Date
java.util.Date
用于表示一个日期和时间的对象,实际上存储了一个long类型以毫秒表示的时间戳。
1 | public class Date implements Serializable, Cloneable, Comparable<Date>{ |
基本方法
1 | Date date = new Date(); |
自定义格式输出
1 | Date date = new Date(); |
M
:输出9
MM
:输出09
MMM
:输出Sep
MMMM
:输出September
缺点:无法转化其他时区,很难对日期和时间进行计算
Calendar
与Date比多了一个可以做简单的日期和时间计算的功能。
1 | Calendar c = Calendar.getInstance(); |
利用Calendar.getTime()
可以将一个Calendar
对象转化为Date
对象,然后用SimpleDateFormat
进行格式化了。
计算:
1 | Calendar c = Calendar.getInstance(); |
TimeZone
与Date
和Calendar
相比,提供了时间转换的功能。时区用TimeZone
对象表示:
1 | TimeZone tzDefault = TimeZone.getDefault(); //当前时区 |
转化时区
1 | Calendar c = Calendar.getInstance(); |
LocalDateTime
从java8开始,java.time
提供了新的日期和时间API,主要涉及的类型有:
- 本地日期和时间:
LocalDateTime
,LocalDate
,LocalTime
- 带时区的日期和时间:
ZoneDateTime
- 时刻:
Instant
- 时区:
ZoneID
,ZoneOffset
- 时间间隔:
Duration
- 格式类型:
DateTimeFormatter
另外修正了:
- Month的范围有1-12表示
- Week范围从1-7表示
LocalDateTime
1 | LocalDate d = LocalDate.now(); |
创建日期:
1 | LocalDateTime dt = LocalDateTime.of(2019, 11, 30, 15, 16, 17); |
注意ISO 8601规定的日期和时间分隔符是T
。标准格式如下:
- 日期:yyyy-MM-dd
- 时间:HH:mm:ss
- 带毫秒的时间:HH:mm:ss.SSS
- 日期和时间:yyyy-MM-dd’T’HH:mm:ss
- 带毫秒的日期和时间:yyyy-MM-dd’T’HH:mm:ss.SS
自定义输出的格式DateTimeFormatter
1 | //自定义格式化 |
和SimpleDateFormat
不同的是,DateTimeFormatter
不但是不变对象,它还是线程安全的。因为SimpleDateFormat
不是线程安全的,使用的时候,只能在方法内部创建新的局部变量。而DateTimeFormatter
可以只创建一个实例,到处引用。
LocalDateTime加减
1 | LocalDataTime dt = dt.plusDays(5).minusHours(3); //加5天,减3小时 |
加减月份会自动调整
对日期和时间调整则使用withXXX()
方法
1 | //调整年:withYear() |
更复杂的运算:
1 | LocalDateTime fDay = LocalDate.now().withDayOfMonth(1).atStartOfDay(); //从本月第一天00:00时刻 |
要判断两个LocalDateTime
先后,可以使用isBefore()
, isAfter()
注:LocalDateTime
无法与时间戳进行转换,因为LocalDateTime
没有时区
Duration和Period
Duration
表示两个时刻之前的时间间隔,Period
表示两个日期之间的天数
1 | LocalDateTime start = LocalDateTime.of(2019, 11, 11, 1, 2, 0); |
Duration
和Period
的表示方法也符合ISO 8601的格式,它以P...T...
的形式表示,P...T
之间表示日期间隔,T
后面表示时间间隔。如果是PT...
的格式表示仅有时间间隔。利用ofXxx()
或者parse()
方法也可以直接创建Duration
:
1 | Duration d1 = Duration.ofHours(10); //10hours |
ZonedDateTime
要表示一个带时区的日期和时间ZonedDateTime
,可以理解成LocalDateTime
加ZoneId
。
ZoneId
是java.time
引入新的时区类,创建ZonedDateTime
对象:
ZonedDateTime zbj = ZonedDateTime.now();
//默认时区ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York"));
- 通过
LocalDateTime
附加一个ZoneId
1 | LocalDateTime ldt = LocalDateTime.of(2020, 1, 1, 1, 0, 0); |
时区转换
要转换时区,使用ZonedDateTime
对象的withZoneSameInstant()
1 | ZonedDataTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); |
转化为本地时间:
LocalDateTime ldt = zdt.toLocalDateTime();
转化后丢失时区信息。
Instant
计算机存储的当前时间,本质上只是一个不断递增的整数。Java提供的System.currentTimeMillis()
返回的就是以毫秒表示的当前时间戳。
这个时间戳在java.time
以Instant类型表示,和Instant.now()
获取时间戳效果类似。
1 | System.out.println(Instant.now()); //2020-02-16T15:59:28.433728Z |
实际上,Instant
内部只有两个核心字段:
1 | public final class Instant implemet ...{ |
既然Instant
是时间戳,加上一个时区,就可以创建出ZonedDateTime
1 | Instant ins =Instant.ofEpochSecond(1581868861); |
所以,LocalDateTime
,ZoneId
,Instant
,ZonedDateTime
和long
都可以互相转换。
需注意:long
类型以毫秒还是秒为单位
版本日期转化
旧API转新API
Date
或Calendar
,通过toInstant
转为Instantd
对象,再继续转为ZonedDateTime
旧的TimeZone
提供了一个toZoneId()
,可以把自己变成新的ZoneId
。
新API转为旧API
只能借助long
转
1 | ZonedDateTime zdt = ZonedDateTime.now(); |
新的ZoneId
转换为旧的TimeZone
,需要借助ZoneId.getId()
返回的String
完成
数据库中存储日期和时间
java.sql.Date
,继承自java.util.Date
,但会自动忽略所有时间相关信息(也就是不会有时区区分)
在数据库,存在几种时间和日期类型:
实际上,在数据库中,我们需要存储的最常用的是时刻(Instant
),因为有了时刻信息,就可以根据用户自己选择的时区,显示出正确的本地时间。
因此在数据库中存储时间戳时,尽量使用long
型时间戳,它具有省空间,效率高,不依赖数据库的优点。