一、为什么引入新的日期时间库
Java对日期,日历及时间的处理一直以来都饱受诟病,尤其是它决定将java.util.Date
定义为可修改的以及将SimpleDateFormat
实现成非线程安全的。
关于这个新的时间日期库的最大的优点就在于它定义清楚了时间日期相关的一些概念,比方说,瞬时时间(Instant),持续时间(duration),日期(date),时间(time),时区(time-zone)以及时间段(Period)。同时它也借鉴了Joda
库的一些优点,比如将人和机器对时间日期的理解区分开的。Java 8仍然延用了ISO的日历体系,并且与它的前辈们不同,java.time
包中的类是不可变且线程安全的。
二、如何使用Java8的新日期和时间
首先认识下Java8新日期和时间的一些关键类:
类名 | 说明 |
---|---|
Instant | 代表时间戳 |
LocalDate | 不包含具体时间的日期 |
LocalTime | 不包含日期的时间 |
LocalDateTime | 包含了日期及时间,没有时区信息 |
ZonedDateTime | 包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的 |
DateTimeFormatter | 日期解析和格式化工具类 |
接下来从几个示例中认识Java8新日期和时间的特别之处,很强大
在Java8中获取当前日期
Java 8中有一个叫LocalDate的类,它能用来表示今天的日期。这个类与java.util.Date略有不同,因为它只包含日期,没有时间。因此,如果你只需要表示日期而不包含时间,就可以使用它。
LocalDate today = LocalDate.now();
System.out.println("Today is : " + today);
输出结果:
Today is : 2020-12-13
从输出结果中可以看到,日期是格式化完了后再输出来的,不像之前的Date类那样,打印出来的数据都是未经格式化的,不便于阅读。
在Java8中获取当前的年月日
LocalDate类中提供了一些很方便的方法可以用于提取出年月日以及其它的日期属性。使用这些方法,你可以获取到任何你所需要的日期属性,而不再需要使用java.util.Calendar这样的类
LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
System.out.printf("Year : %d , Month : %d , day : %d \t %n", year, month, day);
输出结果:
Year : 2020 , Month : 12 , day : 13
在Java8中获取某个特定的日期
使用工厂方法LocalDate.of(),则可以创建出任意一个日期,它接受年月日的参数,然后返回一个等价的LocalDate实例。关于这个方法还有一个好消息就是它没有再犯之前API中的错,比方说,年只能从1900年开始,月必须从0开始,等等。这里的日期你写什么就是什么,比如说,下面这个例子中它代表的就是1月14日,没有什么隐藏逻辑
LocalDate today = LocalDate.of(2020, 12, 13);
System.out.println("Today is : " + today);
输出结果:
Today is : 2020-12-13
在Java8中检查两个日期是否相等
LocalDate重写了equals方法来进行日期的比较
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2014, 01, 14);
if(date.equals(today)){
System.out.printf("Today %s and date %s are same date %n", today, date);
}
输出结果:
Today 2020-12-13 and date 2020-12-13 are same date
在Java8中检查重复事件
使用MonthDay类。这个类由月日组合,不包含年信息,也就是说你可以用它来代表每年重复出现的一些日子。当然也有一些别的组合,比如说YearMonth类。它和新的时间日期库中的其它类一样也都是不可变且线程安全的,并且它还是一个值类(value class)
LocalDate today = LocalDate.now();
LocalDate dateOfBirth = LocalDate.of(2020, 12, 13);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(today);
if (currentMonthDay.equals(birthday)) {
System.out.println("Oh, today is your birthday");
} else {
System.out.println("Sorry, today is not your birthday");
}
输出结果:
Oh, today is your birthday
在Java8中获取当前时间
使用LocalTime的类,它是没有日期的时间,与LocalDate是近亲。这里你也可以用静态工厂方法now()来获取当前时间。默认的格式是hh:mm:ss:nnn,这里的nnn是纳秒
LocalTime time = LocalTime.now();
System.out.println("local time now : " + time);
输出结果:
local time now : 13:44:48.255
在Java8中增加小时数
Java 8不仅提供了不可变且线程安全的类,它还提供了一些更方便的方法譬如plusHours()来替换原来的add()方法。顺便说一下,这些方法返回的是一个新的LocalTime实例的引用,因为LocalTime是不可变的,可别忘了存储好这个新的引用。
LocalTime time = LocalTime.now();
LocalTime newTime = time.plusHours(2);
System.out.println("Time after 2 hours : " + newTime);
输出结果:
Time after 2 hours : 15:47:00.787
在Java8中获取1周后的日期
LocalDate是用来表示无时间的日期的,它有一个plus()方法可以用来增加日,星期,或者月,ChronoUnit则用来表示这个时间单位。由于LocalDate也是不可变的,因此任何修改操作都会返回一个新的实例,因此别忘了保存起来。
LocalDate today = LocalDate.now();
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
System.out.println("Today is : " + today);
System.out.println("Date after 1 week : " + nextWeek);
输出结果:
Today is : 2020-12-13
Date after 1 week : 2020-12-20
在Java8中获取一年前后的日期
使用LocalDate的plus()方法来给日期增加日,周或者月,现在我们来学习下如何用minus()方法来找出一年前的那天。
LocalDate today = LocalDate.now();
LocalDate previousYear = today.minus(1, ChronoUnit.YEARS);
System.out.println("Date before 1 year : " + previousYear);
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
System.out.println("Date after 1 year : " + nextYear);
输出结果:
Date before 1 year : 2019-12-13
Date after 1 year : 2021-12-13
在Java8中使用时钟
Java 8中自带了一个Clock类,你可以用它来获取某个时区下当前的瞬时时间,日期或者时间。可以用Clock来替代System.currentTimeInMillis()与 TimeZone.getDefault()方法,如果你需要对不同时区的日期进行处理的话这是相当方便的。
// Returns the current time based on your system clock and set to UTC.
Clock clock1 = Clock.systemUTC();
System.out.println("Clock : " + LocalDate.now(clock1));
// Returns time based on system clock zone Clock defaultClock =
Clock clock2 = Clock.systemDefaultZone();
System.out.println("Clock : " + LocalDate.now(clock2));
输出结果:
Clock : 2020-12-13
Clock : 2020-12-13
在Java8中判断一个日期在某个日期的前后
在Java 8中,LocalDate类有一个isBefore()和isAfter()方法可以用来比较两个日期。如果调用方法的那个日期比给定的日期要早的话,isBefore()方法会返回true。
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.of(2020, 12, 14);
if (tomorrow.isAfter(today)) {
System.out.println("Tomorrow comes after today");
}
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
if (yesterday.isBefore(today)) {
System.out.println("Yesterday is day before today");
}
输出结果:
Tomorrow comes after today
Yesterday is day before today
在Java8中处理不同的时区
Java 8不仅将日期和时间进行了分离,同时还有时区。现在已经有好几组与时区相关的类了,比如ZonId代表的是某个特定的时区,而ZonedDateTime代表的是带时区的时间。
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localDateTime, ZoneId.of("America/New_York"));
System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);
输出结果:
Current date and time in a particular timezone : 2020-12-13T14:40:44.664-05:00[America/New_York]
在Java8中表示固定的日期
YearMonth又是另一个组合,它代表的是像信用卡还款日,定期存款到期日,options到期日这类的日期。你可以用这个类来找出那个月有多少天,lengthOfMonth()这个方法返回的是这个YearMonth实例有多少天,这对于检查2月到底是28天还是29天可是非常有用的。
YearMonth currentYearMonth = YearMonth.now();
System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth creditCardExpiry = YearMonth.of(2020, Month.FEBRUARY);
System.out.printf("Your credit card expires on %s %n", creditCardExpiry);
输出结果:
Days in month year 2020-12: 31
Your credit card expires on 2020-02
在Java8中检查闰年
LocalDate类有一个isLeapYear()的方法能够返回当前LocalDate对应的那年是否是闰年。
LocalDate today = LocalDate.now();
if (today.isLeapYear()) {
System.out.println("This year is Leap year");
} else {
System.out.println("2020 is not a Leap year");
}
输出结果:
This year is Leap year
在Java8中判断两个日期之间包含多少天/月
一个常见的任务就是计算两个给定的日期之间包含多少天,多少周或者多少年。你可以用java.time.Period类来完成这个功能。
LocalDate today = LocalDate.now();
LocalDate java8Release = LocalDate.of(2021, Month.JANUARY, 13);
Period periodToNextJavaRelease = Period.between(today, java8Release);
System.out.println("Months left between today and Java 8 release : " + periodToNextJavaRelease.getMonths());
输出结果:
Months left between today and Java 8 release : 1
在Java8中获取当前时间戳
Instant类有一个静态的工厂方法now()可以返回当前时间戳
Instant timestamp = Instant.now();
System.out.println("instant : " + timestamp);
输出结果:
instant : 2020-12-13T07:30:55.877Z
在Java8中使用预定义的格式器来对日期进行解析/格式化
在Java 8之前,时间日期的格式化可是个技术活,我们的好伙伴SimpleDateFormat并不是线程安全的,而如果用作本地变量来格式化的话又显得有些笨重。多亏了线程本地变量,这使得它在多线程环境下也算有了用武之地。这次它引入了一个全新的线程安全的日期与时间格式器。它还自带了一些预定义好的格式器,包含了常用的日期格式。
String date = "20201213";
LocalDate formatted = LocalDate.parse(date, DateTimeFormatter.BASIC_ISO_DATE);
System.out.printf("Date generated from String %s is %s %n", date, formatted);
输出结果:
Date generated from String 20201213 is 2020-12-13
在Java8中使用自定义的格式器来解析日期
在DateTimeFormatter的ofPattern静态方法()传入任何的模式,它会返回一个实例,这个模式的字面量与前例中是相同的。比如说M还是代表月,而m仍是分。无效的模式会抛出DateTimeParseException异常
String goodFriday = "12 13 2020";
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd yyyy");
LocalDate holiday = LocalDate.parse(goodFriday, formatter);
System.out.printf("Successfully parsed String %s, date is %s%n", goodFriday, holiday);
} catch (DateTimeParseException ex) {
ex.printStackTrace();
}
输出结果:
Successfully parsed String 12 13 2020, date is 2020-12-13
在Java8中对日期进行格式化,转换成字符串
使用DateTimeFormatter类的实例,调用它的format()方法。这个方法会返回一个代表当前日期的字符串,对应的模式就是传入的DateTimeFormatter实例中所定义好的。
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a");
String landing = localDateTime.format(format);
System.out.printf("Arriving at : %s %n", landing);
输出结果:
Arriving at : 十二月 13 2020 04:13 下午
三、总结
Java 8中新的时间与日期API中的所有类都是不可变且线程安全的,这与之前的 Date
与Calendar API
中的恰好相反,那里面像java.util.Date
以及SimpleDateFormat
这些关键的类都不是线程安全的。新的时间与日期API中很重要的一点是它定义清楚了基本的时间与日期的概念,比方说,瞬时时间,持续时间,日期,时间,时区以及时间段。它们都是基于ISO日历体系的。 Java8新的API能胜任任何与时间日期相关的任务。