Java8 日期和时间

本文介绍了Java8中处理日期和时间的改进,包括LocalDate、LocalTime、LocalDateTime、Instant、Duration和Period的使用,以及如何进行日期时间的格式化、时区转换等操作。文章详细讲解了各个类的创建、操作方法,如日期时间的加减、字段修改,以及日期时间的比较和转换,提供了丰富的示例和实践应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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,常见到的是它的子类SimpleDateFormatSimpleDateFormat是线程不安全的,如果两个线程同时进行操作,结果可能不符合预期。此外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,可以操作时,分,秒这些时间信息。LocalDateTimeLocalDateLocalTime的合体,它同时表示了日期和时间,但是不带有时区信息。翻看三个类的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_DATEISO_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、LocalDateLocalTimeLocalDateTime之间的转换。
它们之间转换实际很好理解,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、旧日期-时间对象DateCalendarInstant之间的转换。

Date<------>Instant
Date nowDate = Date.from(Instant.now());
Instant instant = new Date().toInstant();
Calendar<----->Instant
Instant instant1 = Calendar.getInstance().toInstant();

3、ZonedDateTime与日期-时间对象,Instant转换。加上时区概念,实现DateLocalDateTimeLocalDate间接互转。
在时区概念一节已经提到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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值