从Java8开始,原生的Java API中已经能提供高质量的日期和时间支持,java.time包中整合了很多Joda-Time的特性。
开始使用新的日期和时间API时,你最先碰到的可能是LocalDate类。该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。
你可以通过静态工厂方法of创建一个LocalDate实例。LocalDate实例提供了多种方法来 读取常用的值,比如年份、月份、星期几等,如下所示:
package java8;
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();
作为人,我们习惯于以星期几、几号、几点、几分这样的方式理解日期和时间。毫无疑问, 这种方式对于计算机而言并不容易理解。新的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类的静态工厂方法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), LocalDate.of(2017, 3, 18));
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));
有的时候,你需要进行一些更加复杂的日期操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的withXXX方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象, 更加灵活地处理日期。对于最常见的用例,日期和时间API已经提供了大量预定义的 TemporalAdjuster。你可以通过TemporalAdjuster类的静态工厂方法访问它们。
import static java.time.temporal.TemporalAdjusters.*;
LocalDate date1 = LocalDate.of(2014, 3, 18);
//2014-3-24
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
//2014-3-
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实例都能用于以一定的格式创建代表特定日期或时间的字符串。
LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate date1 = LocalDate.parse("20140318",
DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18",
DateTimeFormatter.ISO_LOCAL_DATE);
你也可以通过解析代表日期或时间的字符串重新创建该日期对象。所有的日期和时间API 都提供了表示时间点或者时间段的工厂方法,你可以使用工厂方法parse达到重创该日期对象 的目的:
LocalDate date1 = LocalDate.parse("20140318",
DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-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")));