欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java8中操作日期时间相关的API

程序员文章站 2024-01-22 20:05:04
...

从Java8开始,原生的Java API中已经能提供高质量的日期和时间支持,java.time包中整合了很多Joda-Time的特性。
1、LocalDate和LocalTime
首先LocalDate类,该类的实例是一个不可变对象,它只提供了简单的日期,并不包含当天的时间信息,也不附带任何与时区相关的信息。

一般通过静态工厂方法of创建一个LocalDate实例。LocalDate实例提供了多种方法来读取常用的值,比如年份、月份、星期几等,如下所示:

import java.time.LocalDate;

public class DateTimeTest {

    public static void main(String[] a) {
        //获取当前日期
        LocalDate now = LocalDate.now();
        
        //获取指定年月日的localdate
        LocalDate date = LocalDate.of(2017, 5, 29);
        
        System.out.println("年:" + date.getYear());
        
        System.out.println("月:" + date.getMonthValue());
        
        System.out.println("几号:" + date.getDayOfMonth());
        
        System.out.println("星期几:" + date.getDayOfWeek().getValue());
        
        System.out.println("当年中的第几天:" + date.getDayOfYear());
        
        System.out.println("当月的天数:"+date.lengthOfMonth());
        
        System.out.println("当年的天数:"+date.lengthOfYear());
        
    }
}

类似地,一天中的时间,比如13:45:20,可以使用LocalTime类表示。你可以使用of重载的两个工厂方法创建LocalTime的实例。第一个重载函数接收小时和分钟,第二个重载函数同时还接收秒。同LocalDate一样,LocalTime类也提供了一些getter方法访问这些变量的值,如下所示。

//表示11:30分
LocalTime time=LocalTime.of(11,30);
//11:30:50
LocalTime time2=LocalTime.of(11,30,50);
System.out.println("时:"+time2.getHour());
System.out.println("分:"+time2.getMinute());
System.out.println("秒:"+time2.getSecond());

LocalDate和LocalTime都可以通过解析代表它们的字符串创建。使用静态方法parse,你可以实现这一目的:

LocalDate date = LocalDate.parse("2014-03-18");
LocalTime time = LocalTime.parse("13:45:20");

你可以向parse方法传递一个DateTimeFormatter。该类的实例定义了如何格式化一个日期或者时间对象。

合并日期和时间
这个复合类名叫LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造。

//2017-05-29 11:30:50
LocalDateTime dateTime=LocalDateTime.of(2017,5,29,11,30,50);
LocalDateTime dateTime2=LocalDateTime.of(date,time2);

可以通过它们各自的atTime或者atDate方法,向LocalDate传递一个时间对象,或者向LocalTime传递一个日期对象的方式,你可以创建一个LocalDateTime对象。你也可以使用toLocalDate或者toLocalTime方法,从LocalDateTime中提取LocalDate或LocalTime对象。

LocalDateTime ldt=time2.atDate(date);
LocalDateTime dateTime3=date.atTime(time2);
LocalDate localDate = dateTime3.toLocalDate();
LocalTime localTime=dateTime3.toLocalTime();

Instant
作为人,我们习惯于以星期几、几号、几点、几分这样的方式理解日期和时间。毫无疑问, 这种方式对于计算机而言并不容易理解。新的java.time.Instant类是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数来表示时间。
你可以通过向静态工厂方法ofEpochSecond传递一个代表秒数的值创建一个该类的实例。静态工厂方法ofEpochSecond还有一个增强的重载版本,它接收第二个以纳秒为单位的参数值,对传入作为秒数的参数进行调整。一旦得到Instant对象,你就可以像之前的LocalDate和LocalTime一样获取该时间的各个信息.

//获取当前时间的instant
Instant now_instant=Instant.now();
//传入秒得到instant
Instant instant=Instant.ofEpochSecond(100);
//传入毫秒
Instant instant2=Instant.ofEpochMilli(100000);
now.getDayOfMonth();
now.getDayOfYear();
now.getMonthValue();
now.getYear();
now.lengthOfYear();

Duration
Duration类主要用于以秒和纳秒衡量时长。Duration类的静态工厂方法between用于创建两个时间之间的时长。

你可以创建两个LocalTime对象、两个LocalDateTime或者两个Instant对象之间的duration。

//创建两个localTime之间的时长对象
Duration duration = Duration.between(LocalTime.of(19, 20), LocalTime.of(19, 30));
//获取相差多少秒      
System.out.println(duration.getSeconds());
//创建两个LocalDateTime之间的时长对象
Duration duration2 = Duration.between(LocalDateTime.of(2017, 5, 29, 19, 20), LocalDateTime.of(2017, 5, 29, 19, 30));
//获取相差多少秒
System.out.println(duration2.getSeconds());
//获取相差多少秒的绝对值
System.out.println(duration2.abs().getSeconds());
//相差多少天
System.out.println(duration2.toDays());
//相差多少时
System.out.println(duration2.toHours());
//相差多少分钟
System.out.println(duration2.toMinutes());
//相差多少毫秒
System.out.println(duration2.toMillis());

由于LocalDateTime和Instant是为不同的目的而设计的,一个是为了便于人阅读使用, 另一个是为了便于机器处理,所以你不能将二者混用。如果你试图在这两类对象之间创建 duration,会触发一个DateTimeException异常。此外,由于Duration类主要用于以秒和纳秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象做参数。
如果你需要以年、月或者日的方式对多个时间单位建模,可以使用Period类。使用该类的 工厂方法between,你可以使用得到两个LocalDate之间的时长,如下所示:

Period tenDays = Period.between(LocalDate.of(2017, 3, 8),   

Duration和Period类都提供了很多非常方便的工厂类,用于直接创建对应的实例。

//1天的时长转成小时
System.out.println(Duration.ofDays(1).toHours());
//1小时的时长转成分钟
System.out.println(Duration.ofHours(1).toMinutes());
//10天的时长
System.out.println(Period.ofDays(10).getDays());

操纵和格式化日期
上述这些日期和时间对象都是不可修改的,这是为了更好地支持函数式编程,确保线程安全,保持领域模式一致性而做出的重大设计决定。当然,新的日期和时间API也 提供了一些便利的方法来创建这些对象的可变版本。
如果你已经有一个LocalDate对象,想要创建它的一个修改版,最直接也最简单的方法是使 用withXXX方法。withXXX方法会创建对象的一个副本,并按照需要修改它的属性。注意,下面的这段代码中所有的方法都返回一个修改了属性的新对象。它们都不会修改原来的对象!

//2017-5-18
LocalDate date1 = LocalDate.of(2017, 5, 18);
//2018-5-18
LocalDate date2 = date1.withYear(2018);
//2018-5-25
LocalDate date3 = date2.withDayOfMonth(25);

也可以以声明的方式操纵LocalDate对象。比如,你可以加上或者减去一段时间。

//加3天
System.out.println(date1.plusDays(5));
//减2天减2小时减1周
System.out.println(date1.minusDays(2).minusMonths(2).plusWeeks(1));

使用TemporalAdjuster
有的时候,你需要进行一些更加复杂的日期操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的withXXX方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象, 更加灵活地处理日期。对于最常见的用例,日期和时间API已经提供了大量预定义的 TemporalAdjuster。你可以通过TemporalAdjuster类的静态工厂方法访问它们。

import static java.time.temporal.TemporalAdjusters.*;
LocalDate date1 = LocalDate.of(2019, 4, 18);
//2019-04-21
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); 
//2019-04-30
LocalDate date3 = date2.with(lastDayOfMonth());

TemporalAdjuster类中的常用工厂方法
dayOfWeekInMonth 创建一个新的日期,它的值为同一个月中每一周的第几天
firstDayOfMonth 创建一个新的日期,它的值为当月的第一天
firstDayOfNextMonth 创建一个新的日期,它的值为下月的第一天
firstDayOfNextYear 创建一个新的日期,它的值为明年的第一天
firstDayOfYear 创建一个新的日期,它的值为当年的第一天
firstInMonth 创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值
lastDayOfMonth 创建一个新的日期,它的值为当月的最后一天
lastDayOfNextMonth 创建一个新的日期,它的值为下月的最后一天
lastDayOfNextYear 创建一个新的日期,它的值为明年的最后一天
lastDayOfYear 创建一个新的日期,它的值为今年的最后一天
lastInMonth 创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值
next/previous 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星 期几要求的日期
nextOrSame/previousOrSame 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星 期几要求的日期,如果该日期已经符合要求,直接返回该对象

日期格式化
处理日期和时间对象时,格式化以及解析日期时间对象是另一个非常重要的功能。新的 java.time.format包就是特别为这个目的而设计的。这个包中,最重要的类是DateTime- Formatter。创建格式器最简单的方法是通过它的静态工厂方法以及常量。像BASIC_ISO_DATE 和ISO_LOCAL_DATE这 样 的 常 量 是DateTimeFormatter类 的 预 定 义 实 例 。 所 有 的 DateTimeFormatter实例都能用于以一定的格式创建代表特定日期或时间的字符串。

//2019-03-18
LocalDate date = LocalDate.of(2019, 3, 18);
//20190318
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
//2019-03-18
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
//2019-03-18
LocalDate date1 = LocalDate.parse("20190318",
			DateTimeFormatter.BASIC_ISO_DATE);
//2019-03-18
LocalDate date2 = LocalDate.parse("2019-03-18",
			DateTimeFormatter.ISO_LOCAL_DATE);

你也可以通过解析代表日期或时间的字符串重新创建该日期对象。所有的日期和时间API 都提供了表示时间点或者时间段的工厂方法,你可以使用工厂方法parse达到重创该日期对象 的目的:

LocalDate date1 = LocalDate.parse("20190318",
                                 DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2019-03-18",
                                 DateTimeFormatter.ISO_LOCAL_DATE);

和老的java.util.DateFormat相比较,所有的DateTimeFormatter实例都是线程安全的。所以,你能够以单例模式创建格式器实例,就像DateTimeFormatter所定义的那些常量,并能在多个线程间共享这些实例。DateTimeFormatter类还支持一个静态工厂方法,它可以按照某个特定的模式创建格式器.

DateTimeFormatter formatter = 
DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);

处理不同的时区
上述的日期和时间的种类都不包含时区信息。时区的处理是新版日期和时间API新增加的重要功能,使用新版日期和时间API时区的处理被极大地简化了。新的java.time.ZoneId 类是老版java.util.TimeZone的替代品。它的设计目标就是要让你无需为时区处理的复杂和繁琐而操心,比如处理日光时这种问题。跟其他日期和时间类一样,ZoneId类也是无法修改的。
时区是按照一定的规则将区域划分成的标准时间相同的区间。在ZoneRules这个类中包含了40个这样的实例。你可以简单地通过调用ZoneId的getRules()得到指定时区的规则。每个特定的ZoneId对象都由一个地区ID标识,比如:

ZoneId romeZone = ZoneId.of("Europe/Rome");

地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA) 的时区数据库提供。

也可以利用当前时区和UTC/格林尼治的固定偏差创建时区。

//东九区
ZoneId zoneId= ZoneId.of("+09:00");

你可以通过Java 8的新方法toZoneId将一个老的时区对象转换为ZoneId:

ZoneId zoneId = TimeZone.getDefault().toZoneId();

一旦得到一个ZoneId对象整合起来,构造为一个ZonedDateTime实例,它代表了相对于指定时区的时间点.

//不包含任何时区的时间
LocalDateTime localDateTime=LocalDateTime.now();
//东九区
ZoneId zoneId= ZoneId.of("+09:00");
//东八区
ZoneId bj=ZoneId.of("+08:00");
//将东八区的当期时间转化成东九区的时间
System.out.println(LocalDateTime.ofInstant(localDateTime.atZone(bj)
								.toInstant(),zoneId)
								.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));