# Java8 之日期处理,工作必用
# 1、简介
Java 处理日期、日历和时间的方式一直为社区所诟病,将 java.util.Date 设定为可变类型,以及 SimpleDateFormat 的非线程安全使其应用非常受限。
伴随 lambda、streams 以及一系列的小优化,Java8 推出了全新的日期时间 API,不同于老版本,新 API 基于 ISO 标准日历系统,java.time 包下的所有类都是不可变类型且是线程安全的。
# 2、关键类
类名 | 描述 |
---|---|
Instant (opens new window) | 在时间线上的瞬间点 |
Duration (opens new window) | 基于时间的时间量(持续时间、时间差),如:34.5 秒 |
LocalDate (opens new window) | 日期的描述,如:2007-12-03 |
LocalTime (opens new window) | 时间的描述,如:10:15:30 |
LocalDateTime (opens new window) | 日期和时间的描述,如:2007-12-03T10:15:30 |
Period (opens new window) | ISO-8601日历系统中的日期时间,例如:2年3个月4天 |
ZoneOffset (opens new window) | 与格林威治/ UTC的时区偏移量,如:+02:00 |
ZonedDateTime (opens new window) | 具有时区的日期时间的不可变表示,如:2007-12-03T10:15:30+01:00 Europe/Paris |
Clock (opens new window) | 使用时区提供对当前即时,日期和时间的访问的时钟 |
java.time.format.DateTimeFormatter (opens new window) | 格式化器用于打印和解析日期时间对象 |
# 3、实战
# 3.1 获取今天的日期
public void getCurrentDate() {
LocalDate today = LocalDate.now();
System.out.println("Today's Local date: " + today);
}
Today's Local date: 2021-07-04
# 3.2 获取年、月、日信息
LocalDate 提供了获取年、月、日的快捷方法,其实例还包含很多其他的日期属性。通过调用这些方法就可以很方便的得到需要的日期信息,不用像以前一样需要依赖 java.util.Calendar 类了。
public void getDetailDate() {
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", year, month, day);
}
Year: 2021 Month: 7 day: 4
# 3.3 处理特定日期
我们通过静态工厂方法 now()
非常容易地创建了当天日期。我们还可以调用另一个有用的工厂方法 LocalDate.of()
创建任意日期, 该方法需要传入年、月、日做参数,返回对应的 LocalDate 实例。这个方法的好处是没再犯老 API 的设计错误,比如年度起始于 1900,月份是从 0 开始等等。日期所见即所得,就像下面这个例子表示了6月21日,直接明了。
public void handleSpecificDate() {
LocalDate dateOfBirth = LocalDate.of(2021, 6, 21);
System.out.println("The specific date is: " + dateOfBirth);
}
The specific date is: 2021-06-21
# 3.4 判断两个日期是否相等
LocalDate 重载了 equals 方法
public void compareDate() {
LocalDate today = LocalDate.now();
LocalDate date1 = LocalDate.of(2021, 7, 4);
if (date1.equals(today)) {
System.out.printf("TODAY %s and DATE1 %s are same date%n", today, date1);
}
}
TODAY 2021-07-04 and DATE1 2021-07-04 are same date
# 3.5 检查像生日这种周期性事件
Java 中另一个日期时间的处理就是检查类似生日、纪念日、法定假日(如:国庆、春节)或者每个月固定时间发送邮件给客户这种周期性事件。
Java 中如何检查这些节日或其它周期性事件呢?答案就是 MonthDay
类。这个类组合了月份和日,去掉了年,这意味着你可以用它判断每年都会发生的事件。和这个类相似的还有一个 YearMonth
类。
public void cycleDate() {
LocalDate today = LocalDate.now();
LocalDate dateOfBirth = LocalDate.of(2021, 6, 21);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(today);
if (currentMonthDay.equals(birthday)) {
System.out.println("Many Many happy returns of the day!");
} else {
System.out.println("Sorry, today is not your birthday.");
}
}
Sorry, today is not your birthday.
# 3.6 获取当前时间
public void getCurrentTime() {
LocalTime time = LocalTime.now();
System.out.println("local time now: " + time);
}
local time now: 10:34:47.668
# 3.7 在现有的时间上增加小时
Java 8 提供了更好的 plusHours()
方法替换 add()
,并且是兼容的。注意,这些方法返回一个全新的 LocalTime 实例,由于其不可变性,返回后一定要用变量赋值。
public void plusHours() {
LocalTime time = LocalTime.now();
LocalTime newTime = time.plusHours(2); // 增加两小时
System.out.println("Time after 2 hours: " + newTime);
}
Time after 2 hours: 12:39:11.459
# 3.8 计算一个星期之后的日期
LocalDate 日期不包含时间信息,它的 plus()
方法用来增加天、周、月,ChronoUnit 类声明了这些时间单位。
public void nextWeek() {
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: 2021-07-04
Date after 1 week: 2021-07-11
# 3.9 计算一年前或一年后的日期
public void minusDate() {
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: 2020-07-04
Date after 1 year: 2022-07-04
# 3.10 使用 Java 8 的 Clock 时钟类
Java 8 增加了一个 Clock 时钟类用于获取当时的时间戳,或当前时区下的日期时间信息。以前用到 System.currentTimeInMillis()
和 TimeZone.getDefault()
的地方都可用 Clock 替换。
public void clock() {
// 根据系统时间返回当前时间并设置为 UTC
Clock clock = Clock.systemUTC();
System.out.println("Clock: " + clock);
// 根据系统时钟区域返回时间
Clock defaultClock = Clock.systemDefaultZone();
System.out.println("Clock: " + clock);
}
Clock: SystemClock[Z]
Clock: SystemClock[Z]
# 3.11 判断日期是早于还是晚于另一个日期
LocalDate 类有两类方法 isBefore()
和 isAfter()
用于比较日期。调用 isBefore()
方法时,如果给定日期小于当前日期则返回 true.
public void isBeforeOrIsAfter() {
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.of(2021, 7, 5);
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
# 3.12 处理时区
Java 8 不仅分离了日期和时间,还把时区也分离出来了。现在有一系列单独的类如 ZoneId 来处理特定时区,ZoneDateTime 类来表示某时区下的时间。
public void getZoneTime() {
ZoneId america = ZoneId.of("America/New_York");
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localDateTime, america);
System.out.println(dateAndTimeInNewYork);
}
2021-07-04T11:03:47.703-04:00[America/New_York]
# 3.13 如何体现出固定日期
例如:表示信用卡到期这类固定日期。与 MonthDay 检查重复事件的例子相似,YearMonth 是另一个组合类,用于表示信用卡到期日、FD到期日、期货期权到期日等。
还可以用这个类得到当月共有多少天,YearMonth 实例的 lengthOfMonth()
方法可以返回当月的天数,在判断2 月有 28 天还是 29 天时非常有用。
public void checkCardExpiry() {
YearMonth currentYearMonth = YearMonth.now();
System.out.printf(
"Days in month year: %s, length of month: %d%n",
currentYearMonth,
currentYearMonth.lengthOfMonth()
);
YearMonth creditCardExpiry = YearMonth.of(2028, Month.FEBRUARY);
System.out.printf("Your credit card expires on %s%n", creditCardExpiry);
}
Days in month year: 2021-07, length of month: 31
Your credit card expires on 2028-02
# 3.14 检查闰年
public void isLeapYear() {
LocalDate today = LocalDate.now();
if (today.isLeapYear()) {
System.out.println("This year is Leap year.");
} else {
System.out.println(today.getYear() + " is not a Leap year.");
}
}
2021 is not a Leap year.
# 3.15 计算两个日期之间的天数和月数
有一个常见日期操作是计算两个日期之间的天数、周数或月数。在 Java 8 中可以用 java.time.Period 类来做计算。
public void calcDateDays() {
LocalDate today = LocalDate.now();
LocalDate java8Release = LocalDate.of(2021, Month.SEPTEMBER, 14);
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: 2
# 3.16 包含时差信息的日期和时间
ZoneOffset 类用来表示时区,举例来说印度与 GMT 或 UTC 标准时区相差 +05:30,可以通过 ZoneOffset.of() 静态方法来获取对应的时区。一旦得到了时差就可以通过传入 LocalDateTime 和 ZoneOffset 来创建一个 OffSetDateTime 对象。
public void zoneOffset() {
LocalDateTime datetime = LocalDateTime.of(2021, Month.JULY, 4, 19, 30);
ZoneOffset offset = ZoneOffset.of("+05:30");
OffsetDateTime date = OffsetDateTime.of(datetime, offset);
System.out.println("Date and Time with timezone offset in Java: " + date);
}
Date and Time with timezone offset in Java: 2021-07-04T19:30+05:30
# 3.17 获取当前的时间戳
Instant 类有一个静态工厂方法 now()
会返回当前的时间戳。
public void getTimestamp() {
Instant timestamp = Instant.now();
System.out.println("What is value of this instant " + timestamp);
}
What is value of this instant 2021-07-04T03:27:03.213Z
# 3.18 使用预定义的格式化工具去解析或格式化日期
Java 8 引入了全新的日期时间格式工具,线程安全而且使用方便。它自带了一些常用的内置格式化工具。
下面这个例子使用了 BASIC_ISO_DATE 格式化工具将 2021 年 7 月 4 日格式化成 20210704.
public void formatDate() {
String dateStr = "20210704";
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE);
System.out.printf("Date generated from String %s is %s%n", dateStr, date);
}
Date generated from String 20210704 is 2021-07-04
# 3.19 获取本月的第一天和最后一天
LocalDate firstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
System.out.println("First day of the month: " + firstDay);
System.out.println("Last day of the month: " + lastDay);
First day of the month: 2021-11-01
Last day of the month: 2021-11-30
# 3.20 获取上个月的第一天和最后一天
LocalDate firstDay = LocalDate.now().minusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDay = LocalDate.now().minusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
System.out.println("First day of last month: " + firstDay);
System.out.println("Last day of last month: " + lastDay);
First day of last month: 2021-10-01
Last day of last month: 2021-10-31
# 3.21 获取本周的周一、周日日期
LocalDate monday = LocalDate.now().with(DayOfWeek.MONDAY);
LocalDate sunday = LocalDate.now().with(DayOfWeek.SUNDAY);
System.out.println("This Monday is: " + monday);
System.out.println("This Sunday is: " + sunday);
This Monday is: 2021-11-29
This Sunday is: 2021-12-05
# 3.22 判断今天是不是星期五
boolean todayIsFriday = DayOfWeek.FRIDAY.equals(LocalDate.now().getDayOfWeek());
System.out.println("Today is Friday:" + todayIsFriday);
Today is Friday:false
# 4、总结
Java 8 日期时间 API 的重点:
- 提供了 javax.time.ZoneId 获取时区。
- 提供了 LocalDate 和 LocalTime 类。
- Java 8 的所有日期和时间 API 都是不可变类并且线程安全,而现有的 Date 和 Calendar API 中的 java.util.Date 和 SimpleDateFormat 是非线程安全的。
- 主包是 java.time,包含了表示日期、时间、时间间隔的一些类。里面有两个子包 java.time.format 用于格式化, java.time.temporal 用于更底层的操作。
- 时区代表了地球上某个区域内普遍使用的标准时间。每个时区都有一个代号,格式通常由区域/城市构成(Asia/Tokyo),再加上与格林威治或 UTC 的时差。例如:东京的时差是 +09:00。
参考文章及来源
文章转自:https://segmentfault.com/a/1190000012922933 (opens new window)