本文主要讲解Java 8的时间处理方式和Java8之前版本的时间处理方式的区别。笔者将Java8之前的jdk版本统称为旧版本。
在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类。它在易用性上许多问题,下面就谈谈这个类的缺点。
缺点一:易用性较差。
Date类年份的起始选择是1900年,月份的起始从0 开始。这意味着,如果你想要用Date表示Java 8的发布日期,即2014年3月18日,需要创建下面 这样的Date实例:
Date date = new Date(114, 2, 18);
它的打印输出效果为:
Tue Mar 18 00:00:00 CET 2014
Date类的toString方法返回的字符串也容易误导 人。以我们的例子而言,它的返回值中甚至还包含了JVM的默认时区CET,即中欧时间(Central Europe Time)。但这并不表示Date类在任何方面支持时区。
缺点二:Date类是可变的,能把2014年3月18日修改成4月18日意味着什么呢?这种设计会将你拖入维护的噩梦。
java.util.Calendar
类是为了替代Date类而出现的。很不幸的是,Calendar类中也有许多缺点,许多设计缺陷问题并未彻底解决。缺点如下:
LocalDate
类的实例是一个不 可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。
LocalTime
用来表示一天中的时间,比如13:45:20。
//1.1 通过of重载的工厂方法创建
LocalDate ofDate = LocalDate.of(2014, 3, 18);//2014-03-18
LocalTime ofTime = LocalTime.of(13, 45, 20); // 13:45:20
//1.2 使用静态方法parse来创建
LocalDate parseDate = LocalDate.parse("2014-03-18");//2014-03-18
LocalTime parseTime = LocalTime.parse("13:45:20");// 13:45:20
//2. 读取LocalDate和LocalTime常用值的两种方式
//2.1 LocalDate 和 LocalTime 类提供了多种方法来 读取常用的值,比如年份、月份、星期几等
int hour = ofTime.getHour(); // 13
int minute = ofTime.getMinute(); // 45
int second = ofTime.getSecond(); // 20
System.out.println(ofTime);
int year = ofDate.getYear(); // 2014
Month month = ofDate.getMonth(); // MARCH
int day = ofDate.getDayOfMonth(); // 18
DayOfWeek dow = ofDate.getDayOfWeek(); // TUESDAY
int len = ofDate.lengthOfMonth(); // 31 (days in March)
boolean leap = ofDate.isLeapYear(); // false (判断是否为为闰年)
System.out.println(ofDate);
//2.1 通过传递一个TemporalField参数给get方法拿到同样的信息。
int y = ofDate.get(ChronoField.YEAR);//2014
int m = ofDate.get(ChronoField.MONTH_OF_YEAR);//3
int d = ofDate.get(ChronoField.DAY_OF_MONTH);//18
int dow2 = ofDate.get(ChronoField.DAY_OF_WEEK);//2
int hour2 = ofTime.get(ChronoField.HOUR_OF_DAY);//13
int minute2 = ofTime.get(ChronoField.MINUTE_OF_HOUR);//45
int second2 = ofTime.get(ChronoField.SECOND_OF_MINUTE);//20
LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息。
//3. 创建LocalDateTime的两种方式
//3.1 通过重载的of工厂方法创建
LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(ofDate, ofTime);
//3.2 通过合并日期和时间的方式创建
LocalDateTime dt3 = ofDate.atTime(13, 45, 20);
LocalDateTime dt4 = ofDate.atTime(ofTime);
LocalDateTime dt5 = ofTime.atDate(ofDate);
//4. 从LocalDateTime中提取LocalDate或者LocalTime 组件
LocalDate date1 = dt1.toLocalDate();//2014-03-18
LocalTime time1 = dt1.toLocalTime();//13:45:20
作为人,我们习惯于以星期几、几号、几点、几分这样的方式理解日期和时间。毫无疑问, 这种方式对于计算机而言并不容易理解。从计算机的角度来看,建模时间最自然的格式是表示一 个持续时间段上某个点的单一大整型数。
java.time.Instant
类对时间建模的方式,基本上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的 秒数进行计算。
//1. 通过向静态工厂方法ofEpochSecond传递一个代表秒数的值创建一个该类的实例。
Instant instant1 = Instant.ofEpochSecond(3);
//2. ofEpochSecond的重载增强版本,它接收第二个以纳秒为单位的参数值,对传入作为秒数的参数进行调整。
// 2秒之后再加上 100万纳秒(1秒)
Instant instant3 = Instant.ofEpochSecond(2, 1_000_000_000);
//4秒之前的100万纳秒(1秒)
Instant instant4 = Instant.ofEpochSecond(4, -1_000_000_000);
Duration类主要用于以秒和纳秒衡量时间的长短。
Period类以年、月或者日的方式对多个时间单位建模。
private static void userDurationAndPeriod(){
LocalTime time1 = LocalTime.of(13, 45, 20); // 13:45:20
LocalTime time2 = LocalTime.of(20, 45, 20); // 13:45:20
LocalDateTime dateTime1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); // 2014-03-18T13:45
LocalDateTime dateTime2 = LocalDateTime.of(2015, Month.MARCH, 18, 13, 45, 20); // 2015-03-18T13:45
Instant instant1 = Instant.ofEpochSecond(3);
Instant instant2 = Instant.ofEpochSecond(4);
LocalDate localDate1 = LocalDate.of(2014, 3, 8);
LocalDate localDate2 = LocalDate.of(2014, 3, 18);
//Duration的创建方式
//1. 通过两个LocalTimes对象、两个LocalDateTimes对象、或者两个Instant对象创建duration
Duration d1 = Duration.between(time1,time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);
//2. 通过工厂类,直接创建对应的实例;
Duration threeMinutes1 = Duration.ofMinutes(3);
Duration threeMinutes2 = Duration.of(3, ChronoUnit.MINUTES);
//Duration的创建方式
//1. 通过两个LocalDate对象创建duration
Period tenDays = Period.between(localDate1,localDate2);
//2. 通过工厂类,直接创建对应的实例;
Period tenDays2 = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
System.out.println(d1.getSeconds());
System.out.println(d2.getSeconds());
}
注意:由于Duration类主要用于以秒和纳 秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象做参数。同样,Period以年、月或者日的方式对多个时间单位建模,所以只能传递LocalDate对象作为参数。
新的 java.time.format 包就是格式化以及解析日期、时间对象的。这个包中最重要的是DateTimeFormatter。
private static void useDateFormatter() {
LocalDate date = LocalDate.of(2014, 3, 18);
//1. 从时间生成字符串
//1.1 使用特定不同的格式器生成字符串
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);//20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);//2014-03-18
//1.2 DateTimeFormatter类还支持静态工厂方法,它可以按 照某个特定的模式创建格式器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter chinaFormatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String s3 = date.format(formatter);//18/03/2014
String s5 = date.format(chinaFormatter2);//2014年3月18日 星期二
//2. 从字符串生成时间
//2.1 通过解析代表日期或时间的字符串重新创建该日期对象。
LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate date3 = LocalDate.parse("18/03/2014", formatter);
LocalDate date5 = LocalDate.parse("2014年3月18日 星期二", chinaFormatter2);
//3. 自定义DateTimeFormatter
DateTimeFormatter complexFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
}
文档地址:DateTimeFormatter
之前你看到的Java8中的日期和时间的种类都不包含时区信息。时区的处理是新版日期和时间API新增 加的重要功能,使用新版日期和时间API时区的处理被极大地简化了。跟其他日期和时间类一 样,ZoneId类也是无法修改的。
时区是按照一定的规则将区域划分成的标准时间相同的区间。每个特定 的ZoneId对象都由一个地区ID标识,比如:
ZoneId romeZone = ZoneId.of("Europe/Rome");
地区ID都为“{区域}/{城市}”的格式,这些地区集合的设定都由英特网编号分配机构(IANA) 的时区数据库提供。java 8中支持的所有地区集合可以通过以下语句打印出来:
Set<String> zoneIds= ZoneId.getAvailableZoneIds();
for (String zone : zoneIds) {
//共计599个
System.out.println(zone);
}
一旦得到一个ZoneId对象,你就可以将它与LocalDate、LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime实例,它代表了相对于指定时区的时间点。代码如下所示:
private static void useZoneId(){
ZoneId romeZone = ZoneId.of("Europe/Rome");
LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zdt2 = dateTime.atZone(romeZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(romeZone);
System.out.println(zdt1);
System.out.println(zdt2);
System.out.println(zdt3);
}
下图对ZonedDateTime的组成部分进行了说明,相信能够帮助你理解LocaleDate、 LocalTime、LocalDateTime以及ZoneId之间的差异。
通过ZoneId,你还可以将LocalDateTime转换为Instant:
LocalDateTime dateTime2 = LocalDateTime.of(2018,7,21,18,46,0);
ZoneId romeZone2 = ZoneId.systemDefault();
Instant instantFromDateTime = dateTime2.atZone(romeZone2).toInstant();
System.out.println(instantFromDateTime.getEpochSecond());