文章目录
1.介绍
如果曾经使用过java8之前处理日期和时间的api,就知道java.util.Date
以及java1.1中引入的java.util.Calendar
类有些许难用。比如Date的年份起始是1900
年,月份的起始是从0
开始的,一周开始是周日
,周日用1
表示。如想要表示2020年1月25日,我们的代码可能写成这样:
Date date = new Date(120,0,25);
//打印结果
Sat Jan 25 00:00:00 CST 2020
这个打印结果可读性极差,你可能想到将日期和时间解析并格式化,那么需要用到一个抽象类DateFormat
,常见到的是它的子类SimpleDateFormat
。SimpleDateFormat
是线程不安全的,如果两个线程同时进行操作,结果可能不符合预期。此外Date和Calendar类都是可变的,也就说我们想要在date加上7天返回日期副本,但此操作会将原先的日期也进行了修改。
java8借鉴了Joda-Time
框架,使我们处理日期和时间更加简单。通过下面一张表看下java8中比较重要的几个类。
类名 | 解释 |
---|---|
LocalDate,LocalTime,LocalDateTime | 本地日期和时间 |
Duration,Period | 日期-时间间隔 |
DateTimeFormatter | 格式化日期和时间 |
Instant | 时刻(时间戳) |
ZoneId,ZoneOffSet | 时区有关变量 |
ZonedDateTime | 带有时区的日期和时间 |
可以看出java8中对日期、时间、带有时区的日期时间、时刻等有了严格的区分。
2.LocalDate、LocalTime、LocalDateTime、Instant、Duration以及Period
2.1 LocalDate、LocalTime和LocalDateTime
LocalDate
提供了简单的日期,不包含时间信息,可以操作年份,月份,日,以及星期几这些日期信息。LocalTime
提供的是一天中的时间,比如13:14:20,可以操作时,分,秒这些时间信息。LocalDateTime
是LocalDate
和LocalTime
的合体,它同时表示了日期和时间,但是不带有时区信息。翻看三个类的api,可以发现它们的大部分方法具有相似性。
————创建对象方式
比较常见的方式有以下几种:
1.通过of()
方法来创建。
LocalDate,LocalTime,LocalDateTime都分别有几种类型的of()
重载方法,比如可以通过LocalDate.of()
方法指定年份,月份,日来返回一个LocalDate,其中月份可以通过数字或者Month
枚举类型来指定。而LocalDateTime由于是日期和时间复合,因此可以通过LocalDateTime.of()
方法指定年份,月份,日,小时,分钟,秒,纳秒来返回LocalDateTime,还可以传入LocalDate和LocalTime来返回一个LocalDateTime。
//指定年份,月份,日
LocalDate localDate = LocalDate.of(2020, 5, 20);
//指定小时,分钟,秒
LocalTime localTime = LocalTime.of(13, 14, 20);
//指定年份,月份,日,小时,分钟,秒
LocalDateTime ldf = LocalDateTime.of(2020, 5, 20, 13, 14, 20);
//参数传入LocalDate,LocalTime
LocalDateTime ldf2 = LocalDateTime.of(localDate ,localTime );
2.通过now()
重载方法创建当前日期-时间。
now()
方法返回当前默认时区日期和时间信息,它还有另外两种重载方法,now(ZoneId zone)
和now(Clock clock)
。实际上now()方法底层会调用now(Clock clock)
方法,参数传入系统默认Clock
。
//获取当前日期
LocalDate ld = LocalDate.now();
//获取当前时间
LocalTime lt = LocalTime.now();
//获取当前日期-时间
LocalDateTime ldt = LocalDateTime.now();
//通过ZoneId来指定时区
LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
//通过指定一个clock实例
LocalDateTime ldt2 = LocalDateTime.now(Clock.systemDefaultZone());
3.通过parse()
重载方法解析字符串
LocalDate,LocalTime,LocalDateTime默认会按照ISO 8601
日历系统的日期和时间格式解析字符串和打印。日期和时间分隔符是T
,默认带有毫秒的日期时间标准格式为yyyy-MM-dd'T'HH:mm:ss.SSS
。另外parse()
方法也提供了按自定义格式解析的方法parse(CharSequence text, DateTimeFormatter formatter)
。
//按照ISO-8601日期格式解析,如2020-05-20
LocalDate parseDate = LocalDate.parse("2020-05-20");
//按照ISO-8601时间格式解析,如13:14,13:14:20
LocalTime parseTime = LocalTime.parse("13:14:20");
//按照ISO-8601日期时间格式解析,如2020-05-20T13:14:20
LocalDateTime parseDateTime = LocalDateTime.parse("2020-05-20T13:14:20");
//指定日期时间解析格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = LocalDateTime.parse("2020-10-20 13:14:15", dateTimeFormatter);
————获取日期和时间字段值
1、getXXXX()
方法读取字段值
以LocalDateTime为例,LocalDateTime
实例提供了getXXXX()
方法读取日期-时间字段值,如年份,月份,日,周几等。LocalDate
和LocalTime也是类似的。LocalDate
有相应获取日期字段的getXXXX()
方法(如年,月,日等),而LocalTime
则提供了获取时间字段getXXXX()
方法(如时,分,秒等)。
LocalDateTime dateTime = of(2020, 2, 21, 13, 14, 20);
System.out.println(dateTime.getYear());//年份
System.out.println(dateTime.getMonth());//月份
System.out.println(dateTime.getDayOfMonth());//月中第几天
System.out.println(dateTime.getDayOfWeek());//星期几
System.out.println(dateTime.getDayOfYear());//年中第几天
System.out.println(dateTime.getHour());//小时
System.out.println(dateTime.getMinute());//分钟
System.out.println(dateTime.getSecond());//秒
2、通用方法get(TemporalField field)
读取字段值
上面通过getXXXX()
方法获取字段值,可以通过通用方法get(TemporalField field)
来获取同样的字段值,其中TemporalField
是一个接口,它定义了如何访问temporal对象某个字段的值。 ChronoField
枚举实现了这一接口,可以使用 get()
方法得到枚举元素的值。
LocalDateTime localDateTime = of(2020, 2, 21, 13, 14, 20);
System.out.println(dateTime.get(ChronoField.YEAR));
System.out.println(dateTime.get(ChronoField.MONTH_OF_YEAR));
System.out.println(dateTime.get(ChronoField.DAY_OF_MONTH));
LocalTime localTime = LocalTime.of(13,14,20);
System.out.println(localTime.get(ChronoField.HOUR_OF_DAY));
System.out.println(localTime.get(ChronoField.MINUTE_OF_HOUR));
System.out.println(localTime.get(ChronoField.SECOND_OF_MINUTE));
这里需要注意一点的是LocalDate
因为表示的是日期,包含了年,月,日等字段,因此应该向它的get()
方法传入比如ChronoField.HOUR_OF_DAY
日期字段枚举值。同样LocalTime
表示的是时间,可以向它的get()
传入小时,分钟,秒等枚举值,如果传入ChronoField.YEAR
这样的日期枚举类型,将会抛出UnsupportedTemporalTypeException
异常。
————操作日期和时间
1、日期和时间字段修改操作
通过withXXXX()
方法来修改指定字段的值,withXXXX()
方法会创建一个原先对象副本,并按要求修改对应字段值,方法不会改变原来的日期-时间对象。
LocalDateTime dateTime = of(2020, 2, 21, 13, 14, 20);
LocalDateTime changeYear = dateTime.withYear(2008);//2008-02-21T13:14:20
LocalDateTime changeDayOfMonth = dateTime.withDayOfMonth(29);//2020-02-29T13:14:20
LocalTime localTime = LocalTime.of(13,14,20);
LocalTime changeMinute = localTime.withMinute(50);//13:50:20
LocalTime changeSecond = localTime.withSecond(45);//13:14:45
与get()方法比较类似,withXXXX()
也提供了一个比较通用的方法with(TemporalField field, long newValue)
,可以将对应日期-时间字段修改为一个newValue
。
LocalDateTime dateTime = of(2020, 2, 21, 13, 14, 20);
LocalDateTime changeYear = dateTime.with(ChronoField.YEAR,2008);//2008-02-21T13:14:20
LocalDateTime changeDayOfMonth = dateTime.with(ChronoField.DAY_OF_MONTH,29);//2020-02-29T13:14:20
LocalTime localTime = LocalTime.of(13,14,20);
LocalTime changeMinute = localTime.with(ChronoField.MINUTE_OF_HOUR,50);//13:50:20
LocalTime changeSecond = localTime.with(ChronoField.SECOND_OF_MINUTE,45);//13:14:45
需要注意的是修改的属性值不能超过属性本身的取值范围,比如修改分钟withMinute()
,传入参数取值范围是0-59
,修改小时方法withHour()
,传入参数取值范围是0-23
。如果超过属性对应取值范围,将会报DateTimeException
异常。
2、日期-时间字段加减操作
LocalDateTime,LocalDate,LocalTime对于日期和时间加减方法可以归三类:
minus(TemporalAmount amount)/ plus(TemporalAmount amount)
plus(long amountToAdd, TemporalUnit unit)/minus(long amountToSubtract, TemporalUnit unit)
minusXXXX()/plusXXXX()类方法
【第一种类型】minus(TemporalAmount amount)/ plus(TemporalAmount amount)
TemporalAmount
是一个时间量的基本接口,它有两个比较常用实现类,一个是基于时间间隔的Duration和基于日期间隔的Period。因此可以对LocalDateTime或者LocalTime进行加减间隔Duration操作,对LocalDate加减间隔Period操作。
LocalDateTime dateTime = LocalDateTime.of(2020, 2, 21, 13, 14, 20);
System.out.println(dateTime.minus(Period.ofYears(10)));//-10年--->2010-02-21T13:14:20
System.out.println(dateTime.plus(Period.ofDays(30)));//+30天--->2020-03-22T13:14:20
LocalTime localTime = LocalTime.parse("13:14:20");
System.out.println(localTime.minus(Duration.ofSeconds(30)));//-30s-->13:13:50
System.out.println(localTime.plus(Duration.ofHours(1)));//+1h-->14:14:20
需要注意的是不能混用Duration和Period,例如对LocalTime间隔一个Period,将会抛出UnsupportedTemporalTypeException
异常。
【第二种类型】plus(long amountToAdd, TemporalUnit unit)/minus(long amountToSubtract, TemporalUnit unit)
第一个参数表示的是加减量值,第二参数TemmporalUnit
是一个接口,它的实现类ChronoUnit
是一个不可变,线程安全的枚举类型。ChronoUnit
提供了日期,时间和日期-时间的字段枚举类型。
LocalDateTime dateTime = LocalDateTime.of(2020, 2, 21, 13, 14, 20);
System.out.println(dateTime.minus(10,ChronoUnit.YEARS));//-10年--->2010-02-21T13:14:20
System.out.println(dateTime.plus(30,ChronoUnit.DAYS));//+30天--->2020-03-22T13:14:20
LocalTime localTime = LocalTime.parse("13:14:20");
System.out.println(localTime.minus(30,ChronoUnit.SECONDS));//-30s-->13:13:50
System.out.println(localTime.plus(1,ChronoUnit.HOURS));//+1h-->14:14:20
此处也需要注意的是对LocalDate不能操作时间的枚举类型,对LocalTime不能操作日期类型枚举类型。否则将抛出UnsupportedTemporalTypeException
异常。
【第三种类型】minusXXXX()/plusXXXX()
类方法。
LocalDateTime,LocalDate和LocalTime分别提供日期-时间,日期,时间字段直接加减指定值的方法。
LocalDateTime dateTime = LocalDateTime.of(2020, 2, 21, 13, 14, 20);
System.out.println(dateTime.minusYears(10));//-10年--->2010-02-21T13:14:20
System.out.println(dateTime.plusDays(30));//+30天--->2020-03-22T13:14:20
LocalTime localTime = LocalTime.parse("13:14:20");
System.out.println(localTime.minusSeconds(30));//-30s-->13:13:50
System.out.println(localTime.plusHours(1));//+1h-->14:14:20
3、使用TemporalAdjuster
对字段复杂操作
上面对于日期,时间的加减的方法相对比较直接,某些场景下,我们可能需要更加复杂的操作,比如将日期调整到下个周日,下个工作日或者本月的最后一天等。此时可以使用重载版本的with()方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象
【方式一】使用TemporalAdjuster预定义方法
通过TemporalAdjusters的静态工厂可以返回一个TemporalAdjuster对象。
LocalDate localDate = LocalDate.of(2020, 2, 21);//周五
System.out.println(localDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)));//2020-02-21
System.out.println(localDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)));//2020-02-28
System.out.println(localDate.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)));//2020-02-14
TemporalAdjusters中包含的静态方法列表如下
方法名 | 描述 |
---|---|
dayOfWeekInMonth | 创建一个新的日期,它的值为同一个月中每一周的第几天 |
firstDayOfMonth | 创建一个新的日期,它的值为当月的第一天 |
firstDayOfNextMonth | 创建一个新的日期,它的值为下月的第一天 |
firstDayOfNextYear | 创建一个新的日期,它的值为明年的第一天 |
firstDayOfYear | 创建一个新的日期,它的值为当年的第一天 |
firstInMonth | 创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值 |
lastDayOfMonth | 创建一个新的日期,它的值为当月的最后一天 |
lastDayOfNextMonth | 创建一个新的日期,它的值为下月的最后一天 |
lastDayOfNextYear | 创建一个新的日期,它的值为明年的最后一天 |
lastDayOfYear | 创建一个新的日期,它的值为今年的最后一天 |
lastInMonth | 创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值 |
next/previous | 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期 |
nextOrSame/previousOrSame | 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,如果该日期已经符合要求,直接返回该对象 |
从上表可以看出,TemporalAdjuster可以对日期进行复杂操作,见名思意,方法不难使用。
【方式二】自定义一个TemporalAdjuster
如果在TemporalAdjusters
中没有找到符合的静态方法,可以进行自定义一个TemporalAdjuster
。从它的源码可以看到这是一个函数式接口。
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
从接口方法可以看出,adjustInto方法实际上是将一个Temporal转换成另外一个Temporal对象。
问题:定制一个TemporalAdjuster,类名为NextWorkingDay,功能是获取下一个工作日。
实现思路:如果日期属于周日(包含)到周四(包含)之间,可以直接加一天;如果是周五,下一个工作是周一因此需要加三天;如果是周六,需要加两天。
代码实现:为了方便,直接通过lambda表达式。
LocalDate localDate = LocalDate.of(2020, 2, 21);//周五
LocalDate nextWorkingDay = localDate.with(temporal->{
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));//获取当前为周几
int dayToAdd = 1;//默认加1天
if(dayOfWeek ==DayOfWeek.FRIDAY){//周五加3天
dayToAdd = 3;
}
if(dayOfWeek==DayOfWeek.SATURDAY){//周六加2天
dayToAdd = 2;
}
return temporal.plus(dayToAdd,ChronoUnit.DAYS);
});
System.out.println(nextWorkingDay);//2020-02-24
如果这个定制计算方式要在多个地方使用,使用Lambda表达式定义 TemporalAdjuster
对象,推荐使用 TemporalAdjusters
类的静态工厂方法 ofDateAdjuster
,它接受一个 UnaryOperator<LocalDate>
类型的参数,如下:
LocalDate localDate = LocalDate.of(2020, 2, 21);//周五
TemporalAdjuster temporalAdjuster = TemporalAdjusters.ofDateAdjuster(temporal -> {
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));//获取当前为周几
int dayToAdd = 1;//默认加1天
if (dayOfWeek == DayOfWeek.FRIDAY) {//周五加3天
dayToAdd = 3;
}
if (dayOfWeek == DayOfWeek.SATURDAY) {//周六加2天
dayToAdd = 2;
}
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
LocalDate nextWorkingDay = localDate.with(temporalAdjuster);
System.out.println(nextWorkingDay);//2020-02-24
————日期和时间比较
我们经常可能会判断时间先后的问题,在LocalDate,LocalTime,LocalDateTime中提供了isAfter(),isBefore()
方法,另外在LocalDate和LocalDateTime有判断日期-时间是否相等方法 isEqual()
。
LocalDateTime nowDateTime = LocalDateTime.now();//2020-02-21T13:10:21
LocalDateTime dateTime = of(2020, 1, 20, 13, 14, 20);
System.out.println(nowDateTime.isAfter(dateTime));
System.out.println(LocalDate.now().isEqual(LocalDate.parse("2020-02-21")));
System.out.println(LocalTime.of(13,14,20).isBefore(LocalTime.parse("15:20:50")));
2.2 Instant
人们通常用年,月,日,星期几,时,分,秒等来表示日期-时间,但对于处理二进制的计算机是很难理解。为了计算机存储,产生了一个时间戳的概念,可理解为时间线上的瞬时点,表示为格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。计算机存储的当前时间,就是一个不断递增整数。在java中可以通过System.currentTimeMillis()
获取以毫秒单位的时间戳,在java8中可以使用java.time.Instant
类来表示时间戳。
Instant
有两个字段,seconds
表示以秒为单位的时间戳,nanos
字段表示的是秒字段时间线上纳秒分片,其取值范围是0-999999999
之间。
public final class Instant
implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable{
private final long seconds;//秒
private final int nanos;//纳秒
}
可以通过Instant中提供的静态工厂方返回一个Instant对象。
//获取当前时刻的时间戳
Instant nowInstant = Instant.now();
//指定秒数
Instant sInstant = Instant.ofEpochSecond(1582434751);
//第一个参数指定秒数,第二个参数用于调整纳秒数
Instant snInstant = Instant.ofEpochSecond(1582434751,449000000);
//指定毫秒
Instant miInstant = Instant.ofEpochMilli(1582434751449L);
//解析日期-时间字符串
Instant parseInstant = Instant.parse("2020-2-21T13:14:20T");
另外Instant
对象也提供一些时间戳的操作方法,如获取秒,毫秒为单位的时间戳以及对时间戳进行加减操作等,如下代码。
Instant instant = Instant.now();//2020-02-23T13:39:54
//时间戳加上时区,获取时区日期-时间
System.out.println(instant.atZone(ZoneId.systemDefault()));//2020-02-23T13:39:54.607+08:00[Asia/Shanghai]
//获取时间戳支持字段值
System.out.println(instant.get(ChronoField.NANO_OF_SECOND));
//获取毫秒时间戳
System.out.println(instant.toEpochMilli());
//获取秒基础上的纳秒分片
System.out.println(instant.getNano());
//秒为单位时间戳基础上,将秒减100000
System.out.println(instant.minusSeconds(1000).atZone(ZoneId.systemDefault()));
//秒为单位时间戳基础上,将纳秒分片加100000
System.out.println(instant.plus(Duration.ofNanos(100000)).atZone(ZoneId.systemDefault()));
//秒为单位时间戳
System.out.println(instant.getEpochSecond());
在使用Instant实例的方法时,需要注意西,Instant初衷设计是为了便于机器使用,它包含了秒及纳秒构成的数字,所以它无法处理日期-时间单位,例如int day = Instant.now().get(ChronoField.DAY_OF_MONTH)
,将会抛出UnsupportedTemporalTypeException
异常。
2.3 Duration和Period
前面提到Duration
表示两个时间或者时刻的间隔,可以创建两个LocalTime
对象,LocalDateTime
对象,或者两个Instant
对象之间的Duration
。类似的Period
表示的是两个日期间隔,可以创建LocalDate
之间的Period
。
————创建Duration和Period对象
创建一个Duration和Period的方法有相似地方,可以分为以下几种类型:
方法名称 | 方法描述 |
---|---|
Duration.between() | 创建两个时间点之间interval |
Duration.from() | 由一个临时时间点创建interval |
Duration.parse() | 由字符串创建 interval 的实例 |
Duration.of() | 由它的组成部分创建 interval的实例 |
Duration.ofXXXX() | 由它的组成部分创建 interval的实例 |
可以通过Duration的静态工厂方法来获取Duration实例。
Duration parseDuration = Duration.parse("P2DT3H4M");//PT51H4M
Duration seconds = Duration.ofSeconds(20);//PT20S
Duration ofSeconds = Duration.of(20, ChronoUnit.SECONDS);//PT20S
Duration days = Duration.from(Duration.ofDays(10));//PT240H
//两个LocalTime的时间间隔
LocalTime start = LocalTime.of(13,14,20);
LocalTime end = LocalTime.of(15,14,20);
Duration tduration = Duration.between(start,end);//PT2H
//两个Instant之间的时间间隔
Instant startInstant = Instant.ofEpochSecond(9999);
Instant endInstant = Instant.ofEpochSecond(8888);
Duration bduration = Duration.between(startInstant, endInstant);//PT-18M-31S
ISO-8601对于Duration格式解析定义为PnDTnHnMn.nS
,字符串开头可以有正负号。如果为负号,对整个表达式为取负。紧跟着"P
“不区分大小写,它后面由四部分组成,即”nD
","nH
","nM
","nS
"。每个部分由数字和一个不区分大小写的前缀组成,其中"D
“代表天数,”H
“代表小时数,”M
“代表小时数,”S
“代表秒数。这四个部分至少得有一部分存在,并且必须按顺序出现。如果存在”nH"
,"nM
","nS
“三个部分,必须跟在”T
“后面。
需要注意的是每个部分中数字由一个或多个ASCII数字组成,并且能被解析为long类型,数字前面可以存在正负号。"nS
"部分前面符号可以为英文的点或者逗号。例如”P2DT3H4M
“表示的是2天3小时4分钟,”-PT-6H+3M
",实际完整表达式为"-(PT-6H+3M)
",即PT+6H-3M
,表示的+6小时-3分钟,所以最终结果为5个小时57分钟。
LocalTime time = LocalTime.of(13, 14, 20);
Duration duration = Duration.parse("P2DT3H4M");//27小时4分钟
LocalTime plusTime = time.plus(duration);
System.out.println(plusTime);//16:18:20
duration = Duration.parse("-PT-6H3M");//+6小时-3分钟
plusTime = time.plus(duration);
System.out.println("plusTime---"+plusTime);//19:11:20
由Period的静态工厂方法来获取Period代码如下,注释表示的是打印结果。
Period parsePeriod = Period.parse("P1Y2M3W4D");//P1Y2M25D
Period dayss = Period.ofDays(10);//P10D
Period ofPeriods = Period.of(1, 2, 3);//P1Y2M3D
Period weeks = Period.from(Period.ofWeeks(1));//P7D
//两个LocalDate之间的日期间隔
LocalDate startDate = LocalDate.of(2020,1,21);
LocalDate endDate = LocalDate.of(2020,1,22);
Period dperiod = Period.between(startDate,endDate);//P1D
ISO-8601标准的Period解析格式为PnYnMnWnD
,字符串开头可以有正负号,如果是负号,表示对整个表达式取负。紧跟着"P
“不区分大小写,它后面有四个部分,即”nY
","nM
","nW
","nD
"。每一个部分都由数字和一个不区分大小的前缀组成,其中"Y
“表示的是多少年,”M
“表示的是几个月,”W
“表示的是多少周,”D
“表示的是多少天。
每个部分的数字由ASCII数字构成,而且能被解析为int类型,数字前面可以出现正负号。这四个部分至少要一个部分出现。比如”P1Y2M3D
“表示的是1年2个月3天,”-P1Y2M
",由于前面是负号,因此可以理解为"-(P1Y2M)
",表示的是-1年-2个月。由于Period是日期间隔,因此负数就是将日期减去指定日期间隔。
LocalDate of = LocalDate.of(2020, 3, 21);
Period parse = Period.parse("-P1Y2M");
//将2020,3,21减去1年2个月,即为2019-01-21
System.out.println(of.plus(parse));
————操作Duration和Period对象
Duration和Period共享了一些比较相似的方法,以Duration为例,获取Duration指定单位时间量的方法,如getSeconds()
,通过通用的get(ChronoUnit.SECONDS)
方法也可以获取相同的结果。加减指定时间量的方法,如minusSeconds()
,以及通用的方法minus(long amountToSubtract, TemporalUnit unit)
获取相同的结果。Duration和Period的通用方法见下表。
方法名称 | 方法描述 |
---|---|
addTo | 创建该 interval 的副本,并将其叠加到某个指定的 temporal 对象 |
get | 读取该 interval 的状态 |
isNegative | 检查该 interval 是否为负值,不包含零 |
isZero | 检查该 interval 的时长是否为零 |
minus | 通过减去一定的时间创建该 interval 的副本 |
multipliedBy | 将 interval 的值乘以某个标量创建该 interval 的副本 |
negated | 以忽略某个时长的方式创建该 interval 的副本 |
plus | 以增加某个指定的时长的方式创建该 interval 的副本 |
subtractFrom | 从指定的 temporal 对象中减去该 interval |
3.格式化日期-时间对象和解析
java.time.format
包下面提供了格式化日期-时间对象和解析的类,其中比较重要的一个类是DateTimeFormatter
,它里面提供了一些静态工厂方法和常量来创建格式化器。DateTimeFormatter
可以将日期-时间打印成一个代表特定日期-时间的字符串,也可以通过解析代表日期-时间的字符串重新创建该日期-事件对象。与jdk8之前常使用的格式化器SimpleDateFormat
相比,DateTimeFormatter
是不可变对象,并且是线程安全的。
1、通过静态工厂方法ofPattern(String pattern)
获取DateTimeFormatter
,也可以指定Locale
,即ofPattern(String pattern, Locale locale)
。
//ofPattern()
LocalDate localDate = LocalDate.of(2020, 1, 25);
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String dateFormat = dateFormatter.format(localDate);//2020-01-25
//通过日期字符串重建一个LocalDate对象
LocalDate parseDate = LocalDate.parse("2020-01-25", dateFormatter);
//ofPattern(),指定Locale
DateTimeFormatter localeFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy",Locale.ENGLISH);
//通过日期字符串重建一个LocalDate对象
String localeFormat = localeFormatter.format(localDate);//25. January 2020
LocalDate parseDate1 = LocalDate.parse("25. January 2020", localeFormatter);//2020-01-25
2、DateTimeFormatter
中预定义了一些常量,比如BASIC_ISO_DATE
,ISO_LOCAL_DATE
等。
DateTimeFormatter basicDateFormatter = DateTimeFormatter.BASIC_ISO_DATE;
DateTimeFormatter localDateFormatter = DateTimeFormatter.ISO_LOCAL_DATE;
LocalDate localDate = LocalDate.of(2020, 1, 25);
String dateString1 = basicDateFormatter.format(localDate);//20200125
String dateString2 = localDateFormatter.format(localDate);//2020-01-25
//通过日期-时间字符串重建日期-时间对象
LocalDate parseLocalDate1 = LocalDate.parse("20200125", basicDateFormatter );
LocalDate parseLocalDate2 = LocalDate.parse("2020-01-25", localDateFormatter);
3、DateTimeFormatter
中提供ofXXXX()
类型的方法,可以通过传入FormatStyle
枚举类型。
DateTimeFormatter dateTimeFormatter =
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.MEDIUM);
LocalDateTime dateTime = LocalDateTime.of(2020, 1, 24,13, 14, 20);
String format = dateTimeFormatter.format(dateTime);
System.out.println(format);
4、使用DateTimeFormatterBuilder
类构造一个复杂的格式器,它提供了非常强大的解析功能,比如区分大小写的解析,填充方法等。
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendText(ChronoField.YEAR)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.DAY_OF_MONTH)
.parseCaseInsensitive()
.toFormatter(Locale.ENGLISH);
LocalDate localDate = LocalDate.of(2020, 1, 24);
System.out.println(localDate.format(formatter));//2020. January 24
通过以上代码可以看出,可以通过日期-时间对象中的format方法,传入参数DateTimeFormatter对象,或者DateTimeFormatter中方法format方法,可以将一个日期-时间对象打印为指定格式字符串。也可以通过日期-时间对象中的parse方法,传入一个代表日期-时间的字符串和DateTimeFormatter来重建日期-时间对象。
4.时区概念ZoneId、ZonedDateTime
目前为止看到的日期-时间对象都不含有时区的概念,新版本的java.time.ZoneId
类是老板java.util.TimeZone
的替代品,与其他的日期-时间类一样,ZoneId
是无法修改的。通过ZoneId
中的getAvailableZoneIds()
方法可以获取一个代表时区ID的集合,时区ID的格式为{区域}/{城市}
,
可以通过向ZoneId.of()
方法传入一个时区ID返回一个ZoneId
,java8的新方法toZoneId()
将一个老的时区对象转换为ZoneId
。
ZoneId romeZone = ZoneId.of("Europe/Rome");
//将老时区对象转换为ZoneId
ZoneId zoneId = TimeZone.getDefault().toZoneId();
得到ZoneId对象,就可以将它与LocalDate,LocalDateTime或者Instant对象整合起来,构造一个ZonedDateTime对象,它代表了指定时区的时间点。它们关系可以总结为下图。
因此可以在LocalDate,LocalDateTime,Instant加上时区转换成ZonedDateTime。
//LocalDate---->ZonedDateTime
LocalDate localDate = LocalDate.of(2020, 2, 27);
ZonedDateTime zonedDate = localDate.atStartOfDay(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDate);//2020-02-27T00:00+08:00[Asia/Shanghai]
//LocalDateTime---->ZonedDateTime
LocalDateTime localDateTime = LocalDateTime.parse("2020-02-27T09:54:58");
ZonedDateTime zoneDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zoneDateTime);//2020-02-27T09:54:58+08:00[Asia/Shanghai]
//Instant---->ZonedDateTime
Instant instant = Instant.now();
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime);//2020-02-27T10:11:18.554+08:00[Asia/Shanghai]
当然也可以通过ZonedDateTime来反向获取LocalTime,LocalDate,LocalDateTime,Instant等。
//2020-02-27T10:11:18.554+08:00[Asia/Shanghai]
ZonedDateTime zonedDateTime = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
Instant toInstant = zoneDateTime.toInstant();
System.out.println(toInstant);//2020-02-27T01:54:58Z
LocalTime toLocalTime = zoneDateTime.toLocalTime();
System.out.println(toLocalTime);//09:54:58
LocalDateTime toLocalDateTime = zoneDateTime.toLocalDateTime();
System.out.println(toLocalDateTime);//2020-02-27T09:54:58
LocalDate toLocalDate = zoneDateTime.toLocalDate();
System.out.println(toLocalDate);//2020-02-27
ZonedDateTime
是一个包含了时区概念的有完整日期-时间的类,它除了提供了获取日期-时间对象之外,还提供一些获取日期-时间字段,加减日期-时间字段以及跟时区有关的操作,它可以通过format(DateTimeFormatter formatter)
方法格式化日期-时间。这些方法与日期-时间对象如LocalDateTime
中方法相差无几,因此此处不再详细介绍。
5.应用实践
1、LocalDate
和LocalTime
与LocalDateTime
之间的转换。
它们之间转换实际很好理解,LocalDateTime由于包含了日期和时间,因此可以直接得到日期和时间。LocalTime是时间,因此加上日期,就可以得到LocalDateTime。同样LocalDate加上时间,就可以转换成LocalDateTime。
LocalDateTime localDateTime = LocalDateTime.parse("2020-02-27T09:54:58");
//LocalDateTime--->LocalTime
LocalTime localTime = localDateTime.toLocalTime();
//LocalDateTime--->LocalDate
LocalDate localDate = localDateTime.toLocalDate();
//localTime--->LocalDateTime
LocalDateTime localDateTime1 = localTime.atDate(localDate);
//localDate--->LocalDateTime
LocalDateTime localDateTime2 = localDate.atStartOfDay();
LocalDateTime localDateTime3 = localDate.atTime(localTime);
2、旧日期-时间对象Date
和Calendar
与Instant
之间的转换。
Date<------>Instant
Date nowDate = Date.from(Instant.now());
Instant instant = new Date().toInstant();
Calendar<----->Instant
Instant instant1 = Calendar.getInstance().toInstant();
3、ZonedDateTime
与日期-时间对象,Instant
转换。加上时区概念,实现Date
与LocalDateTime
,LocalDate
间接互转。
在时区概念一节已经提到LocalDateTime和LocalDate,Instant加上时区概念可以获取一个ZonedDateTime,同样从ZonedDateTime可获取LocalDateTime,LocalDate,Instant以及LocalTime。而Date和Calendar与Instant可以互转,因此在有时区概念情况下,可以实现Date与LocalDateTime,LocalDate间接转换。
//LocalDateTime,LocalDate,Instant<---->ZonedDateTime
//这部分参见时区概念一节,此处省略
//加上时区概念,LocalDateTime---->Date
Date date = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
//加上时区概念,LocalDate---->Date
Date date1 = Date.from(LocalDate.parse("2020-01-25").atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
//加上时区概念,Date---->LocalTime
LocalTime toLocalTime = ZonedDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault()).toLocalTime();
//加上时区概念,Date---->LocalDateTime
LocalDateTime toLocalDateTime =ZonedDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault()).toLocalDateTime();
//加上时区概念,Date---->LocalDate
LocalDate toLocalDate =ZonedDateTime.ofInstant(new Date().toInstant(),ZoneId.systemDefault()).toLocalDate();
6.参考
《Java8 实战》
http://joda-time.sourceforge.net/cal_iso.html
https://en.wikipedia.org/wiki/ISO_8601
https://www.liaoxuefeng.com/wiki/1252599548343744/1303985694703650