在实际开发项目中,我们经常会与各种时间类型打交道,在Java8之前我们是用Calendar,Date, Time和SimpleDateFormat等来处理各种时间格式间的转换,但由于SimpleDateFormat不是线程安全的,因为当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。为了解决线程安全问题,需要将SimpleDateFormat设成局部变量或者使用ThreadLocal对象将每个线程都拥有自己的SimpleDateFormat对象副本来解决。这些Java8之前时间库的诟病在Java8中都得以解决。在Java8的java时间库中,定义了主要的日期时间概念,而且分类比较细,,比如LocalDateTime, LocalDate,LocalTime,ZonedDateTime等。此外还定义了许多帮助开发者使用的库,例如 instants, duration, dates, times, timezones, and periods,YearMonth,MonthDay等。本文通过具体的20几个简单例子来演示这些功能,包括时间转换,时间计算等。
实例一,获取今天的日期
Java8中有一个叫做 LocalDate 的类,它可以用来表示今天的日期,这个类不包含时区信息。 这个类与 java.util.Date有稍许不同,因为它只包含日期,没有时间部分。 因此,如果您只是表示没有时间的日期,请使用LocalDate。实例代码:
LocalDate today = LocalDate.now();
System.out.println("Today's Local date : " + today);
//Output Today's Local date : 2021-06-01
可以看到它创建了今天的日期,没有任何时间和时区信息,而且它还以格式良好的方式打印日期,不像以前的 Date 类打印的是非格式化的数据。
实例二,获取当前年月日
LocalDate类有一些方便的方法可以从 LocalDate 类的实例中提取年、月、日和其他几个日期属性。 通过使用这些方法,您可以获得您想要的任何日期属性,无需使用像 java.util.Calendar这个类。
LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
System.out.println("Today's Local date : " + today);
System.out.printf("Year : %d Month : %d day : %d \t %n", year, month, day);
//Output Today's Local date : 2014-01-14
//Year : 2021 Month : 6 day : 1
相比Java8之前获取年月日,这些API简单易用多了。
实例三,获取指定日期
在实例一中,我们使用now非常简单的获取当前日期,我们也可以使用另一个名为 LocalDate.of()的工厂方法从任意日期创建日期,参数是年、月和日,返回对应的 LocalDate 实例。这种方法的好处是它没有重复以前的 API 中犯过的错误,例如 年份从 1900 年开始,月份从零开始,等等这些让人很费解的使用方式。这里的日期是非常直接的方式表示,例如 在以下示例中,它将代表 6 月 1 日,没有任何隐藏。
LocalDate dateOfBirth = LocalDate.of(2021, 6, 1);
System.out.println("Your Date of birth is : " + dateOfBirth);
Output : Your Date of birth is : 2021-06-01
实例四,怎样检测日期是否相等
现实世界的日期时间任务,其中之一是检查两个日期是否相同。 很多时候您想检查今天是否是那个特殊的日子、您的生日、周年纪念或交易假期。 有时,你会得到一个任意的日期,你需要检查某个日期,例如 假期以确认给定日期是否为假期。
这个例子将帮助你在 Java 8 中完成这些任务。就像你想象的那样,LocalDate 已经覆盖了 equal 方法来提供日期相等,如下面的例子所示:
LocalDate today = LocalDate.now();
LocalDate date1 = LocalDate.of(2021, 6, 1);
if(date1.equals(today)){
System.out.printf("Today %s and date1 %s are same date %n", today, date1);
}
//Output today 2021-06-01 and date1 2021-06-01 are same date
相比java8之前的比较,这种方式简单多了,之前需要先转成Date,然后使用compareTo方法来进行比较。
实例五,如何检测重复事件
Java 中与日期和时间相关的另一个实际任务是检查重复事件,例如月账单、结婚纪念日或年度保险费日期。如果您在电子商务网站工作,您肯定会有一个模块可以在每个重大节日向您的客户发送生日祝福和季节问候,例如圣诞节、感恩节或印度的屠妖节。如何在 Java 中检查假期或任何其他重复事件?通过使用 MonthDay 类。这个类是月份和日期的组合,这意味着您可以将它用于每年发生的事件。其他组合也存在类似的类,例如年月。与新日期和时间 API 中的其他类一样,这也是不可变的和线程安全的,并且它也是一个值类。现在让我们看一个如何使用 MonthDay 类检查重复日期时间事件的示例:
LocalDate dateOfBirth = LocalDate.of(2021, 6, 1);
LocalDate today = LocalDate.now();
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");
}
//Output: Many Many happy returns of the day !!
实例六,获取当前时间
这与我们在 Java 8 中获取当前日期的第一个示例非常相似。这一次,我们将使用一个名为 LocalTime 的类,它是没有日期的时间并且是 LocalDate 类的近亲。 在这里,您也可以使用静态工厂方法 now() 来获取当前时间。 默认格式为 hh:mm:ss:nnn,其中 nnn 是纳秒。
LocalTime time = LocalTime.now();
System.out.println("local time now : " + time);
//Output local time now : 16:33:33.369
你可以看到当前时间仅仅包含时间,没有日期信息。
实例七,怎样增加时间
在很多情况下,我们希望添加小时、分钟或秒来计算未来的时间。 Java 8 不仅帮助了不可变和线程安全的类,而且还提供了更好的方法,例如 plusHours() 而不是 add()。需要注意这些方法返回对新 LocalTime 实例的引用,因为 LocalTime 是不可变的,所以不要忘记将它们存储回来。
LocalTime time = LocalTime.now();
LocalTime newTime = time.plusHours(2); // adding two hours
System.out.println("Time after 2 hours : " + newTime);
//Output : Time after 2 hours : 18:33:33.369
实例八,计算一周后的日期
这和前面的例子类似,上个例子我们演示了如何在 2 小时后找到时间,这个例子演示如何在 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);
//Output: Today is : 2021-06-01 Date after 1 week : 2021-06-08
实例九,计算一年前或一年后的日期
这是上一个例子的延续。 在上一个示例中,我们学习了如何使用 LocalDate 的 plus() 方法在日期中添加天、周或月,现在我们将学习如何使用 minus() 方法查找 1 年前的一天。
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);
//Output: Date before 1 year : 2020-06-01 Date after 1 year : 2022-06-01
实例十,如何比较两个日期
这是实际项目中另一个非常常见的任务。 如何确定给定日期是在当前日期之前还是之后,或者只是另一个日期? 在 Java 8 中,LocalDate 类具有 isBefore() 和 isAfter() 等方法,可用于比较 Java 中的两个日期。 如果给定日期早于调用此方法的日期,则 isBefore() 方法返回 true。
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.of(2021, 6, 2);
if(tommorow.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");
}
//Output: Tomorrow comes after today Yesterday is day before today
实例十一,如何处理时区
Java 8 不仅分离了日期和时间,还分离了时区。 现在有一组与时区相关的单独类,例如 ZonId 表示特定时区,ZonedDateTime 类表示带时区的日期时间。 它相当于 Java 8 之前的世界中的 GregorianCalendar 类。 通过使用此类,您可以将本地时间转换为另一个时区的等效时间,如下例所示:
// Date and time with timezone in Java 8
ZoneId america = ZoneId.of("America/New_York");
LocalDateTime localtDateAndTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localtDateAndTime, america ); System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);
//Output : Current date and time in a particular timezone : 2021-06-01T16:33:33.373-05:00[America/New_York]
注意ZonedDateTime.of方法不会进行任何时区的转换,它只是把localdatetime后面加上指定的时区信息。
实例十二,如何使用YearMonth
就像我们用于检查重复事件的 MonthDay 示例一样,YearMonth 是另一个组合类,用于表示信用卡到期日等。还可以使用这个类来查找当月有多少天, lengthOfMonth() 返回当前 YearMonth 实例中的天数,用于检查二月是 28 天还是 29 天。实例如下:
YearMonth currentYearMonth = YearMonth.now();
System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth creditCardExpiry = YearMonth.of(2022, Month.FEBRUARY);
System.out.printf("Your credit card expires on %s %n", creditCardExpiry);
//Output: Days in month year 2021-06: 30
//Your credit card expires on 2022-02
实例十三,检测闰年
这里没什么特别的,LocalDate 类有 isLeapYear() 方法,如果该 LocalDate 表示的年份是闰年,则该方法返回 true。
LocalDate today = LocalDate.now();
if(today.isLeapYear()){
System.out.println("This year is Leap year"); }else {
System.out.println("2014 is not a Leap year");
}
//Output: 2021 is not a Leap year
实例十四,计算两个日期之间的月份有多少天
一项常见任务是计算两个给定日期之间的天数、周数或月数。 可以使用 java.time.Period 类来计算 Java 中两个日期之间的天数、月数或年数。 在以下示例中,我们计算了当前日期和未来日期之间的月数。
LocalDate java8Release = LocalDate.of(2014, Month.MARCH, 14);
Period periodToNextJavaRelease = Period.between(today, java8Release); System.out.println("Months left between today and Java 8 release : " + periodToNextJavaRelease.getMonths() );
//Output: Months left between today and Java 8 release : 87
实例十五,带有时区偏移的日期和时间
在 Java 8 中,您可以使用 ZoneOffset 类来表示时区,例如,印度是 GMT 或 UTC +05:30,您可以使用静态方法 ZoneOffset.of() 方法来获取相应的时区。 获得偏移量后,您可以通过将 LocalDateTime 和偏移量传递给它来创建一个 OffSetDateTime。
LocalDateTime datetime = LocalDateTime.of(2021, 6, 1, 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);
//Output : Date and Time with timezone offset in Java : 2021-06-01T19:30+05:30
实例十六,获取当前时间戳
Instant 类有一个静态工厂方法 now() ,它返回当前时间戳,如下所示:
Instant timestamp = Instant.now();
System.out.println("What is value of this instant " + timestamp);
//Output : What is value of this instant 2014-01-14T08:33:33.379Z
您可以看到当前时间戳具有日期和时间组件,很像 java.util.Date,事实上,Instant 是 Java 8 之前日期的等效类,您可以使用两者中添加的相应转换方法在 Date 和 Instant 之间进行转换 这些类中,例如 Date.from(Instant) 用于将 Instant 转换为 Java 中的 java.util.Date 并且 Date.toInstant() 返回该 Date 类的 Instant 等价物。此外,Instant也有很多的方法toEpochMilli,minus,plus等方法。
实例十七,如何在 Java 8 中使用预定义格式解析/格式化日期
在 Java 8 之前的世界中,日期和时间格式化非常棘手,我们唯一的朋友 SimpleDateFormat 不是线程安全的,而且非常庞大,无法用作格式化和解析大量日期实例的局部变量。 值得庆幸的是,线程局部变量使它可以在多线程环境中使用,但 Java 已经走了很长一段路。它引入了一个全新的日期和时间格式化程序,它是线程安全且易于使用的。 它现在带有一些用于常见日期模式的预定义格式化程序。 例如,在此示例代码中,我们使用预定义的 BASIC_ISO_DATE 格式化程序,它使用 2021 年 6 月 1 日的格式 20210601。
String dayAfterTommorrow = "20210602";
LocalDate formatted = LocalDate.parse(dayAfterTommorrow, DateTimeFormatter.BASIC_ISO_DATE); System.out.printf("Date generated from String %s is %s %n", dayAfterTommorrow, formatted); //Output : Date generated from String 20210602 is 2021-06-02
907 / 5000
Translation results
实例十八,如何使用自定义格式解析 Java 中的日期
在最后一个示例中,我们使用了内置的日期和时间格式化程序来解析 Java 中的日期字符串。 当然,预定义的格式化程序很棒,但有时您想使用自己的自定义日期模式,在这种情况下,您必须创建自己的自定义日期时间格式化程序实例,如本例所示。 以下示例的日期格式为“MMM dd yyyy”。
您可以使用 ofPattern() 静态方法创建具有任意模式的 DateTimeFormatter,它遵循与以前相同的文字来表示模式,例如 M 仍然是一个月,m 仍然是一分钟。 无效模式将抛出 DateTimeParseException,但不会捕获使用 m 而不是 M 的逻辑上不正确的模式。
String goodFriday = "Apr 18 2014";
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy");
LocalDate holiday = LocalDate.parse(goodFriday, formatter);
System.out.printf("Successfully parsed String %s, date is %s%n", goodFriday, holiday);
} catch (DateTimeParseException ex) {
System.out.printf("%s is not parsable!%n", goodFriday); ex.printStackTrace();
}
//Output : Successfully parsed String Apr 18 2014, date is 2014-04-18
实例十九,如何在 Java 8 中将日期转换为字符串,格式化日期
在最后两个示例中,虽然我们一直在使用 DateTimeFormatter 类,但我们主要解析格式化的日期字符串。 在这个例子中,我们将做完全相反的事情。 这里我们有一个日期,LocalDateTime 类的实例,我们将转换为格式化的日期字符串。 这是迄今为止在 Java 中将 Date 转换为 String 的最简单和最简单的方法。
下面的示例将返回格式化的字符串来代替日期。 与前面的示例类似,我们仍然需要使用给定的模式创建一个 DateTimeFormatter 实例,但现在我们将调用 format() 方法而不是调用 LocalDate 类的 parse() 方法。
此方法返回一个字符串,该字符串表示以绕过 DateTimeFormatter 实例的模式表示的日期。
LocalDateTime arrivalDate = LocalDateTime.now();
try {
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a");
String landing = arrivalDate.format(format);
System.out.printf("Arriving at : %s %n", landing);
} catch (DateTimeException ex) {
System.out.printf("%s can't be formatted!%n", arrivalDate);
ex.printStackTrace();
}
//Output : Arriving at : Jan 14 2014 04:33 PM
实例二十,Date和LocalDate转换
由于 java.time.Instant 中新日期和时间 API 中 java.util.Date 的等效类,我们首先将 Date 转换为 Instant,然后使用 System 的默认时区从该 Instant 创建 LocalDateTime 实例。JDK 8 在 java.util.Date 上添加了方便的 toInstant() 方法,以将旧的日期和时间 API 与新的 API 接口。
这里要注意的关键细节是 java.util.Date 不完全是日期,而是时间的实例(从 Epoch 传递过来的毫秒数),这就是为什么它等同于 java.time.Instant 对象的实例。
要记住的另一件重要事情是 Instant 也不包含任何有关时区的信息。 因此,要从 Instant 创建 DateTime 对象,必须指定时区。
对于 LocalDateTime,这应该是 ZoneId.systemDefault() 方法提供的默认区域。如果您的应用程序控制时区,那么您也可以在此处使用它。 LocalDateTime 类有一个方便的工厂方法。 LocalDateTime.ofInstant(),它接受Instant和时区参数。实例如下:
Date转LocalDateTime
// converting java.util.Date to java.time.LocalDateTime
Date now = new Date();
Instant current = now.toInstant();
LocalDateTime ldt = LocalDateTime.ofInstant(current, ZoneId.systemDefault());
System.out.println("value of current: " + current);
System.out.println("value of Date: " + now);
System.out.println("value of LocalDateTime: " + ldt);
LocalDateTime转Date
// converting java 8 LocalDateTime to java.util.Date
ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));
Date output = Date.from(zdt.toInstant());
System.out.println("value of zdt:" + zdt);
System.out.println("value of LocalDateTime: " + ldt);
System.out.println("value of Date: " + output);
数据输出如下:
value of current: 2021-06-01T07:18:29.296Z
value of Date: Tue Jun 01 15:18:29 CST 2021
value of LocalDateTime: 2021-06-01T15:18:29.296
value of zdt:2021-06-01T15:18:29.296-04:00[America/New_York]
value of LocalDateTime: 2021-06-01T15:18:29.296
value of Date: Wed Jun 02 03:18:29 CST 2021
总结
看完所有这些例子后,我相信你已经学到了很多关于 Java 8 日期和时间 API 的新东西。 现在让我们退后一步,回顾一下关于这个漂亮的 API 的一些要点。
- 提供 javax.time.ZoneId 来处理时区。
- 提供 LocalDate 和 LocalTime 类
- Java 8 的新日期和时间 API 的所有类都是不可变和线程安全的,与现有的日期和日历 API 不同,其中关键类例如java.util.Date 或 SimpleDateFormat 不是线程安全的。
- 新日期和时间 API 的关键在于它定义了主要的日期时间概念,例如instants, duration, dates, times, timezones, and periods,YearMonth,MonthDay等。它们也基于 ISO 日历系统。
- 每个 Java 开发人员至少应该知道以下来自新日期和时间 API 的五个类:
Instant - 它代表一个时间戳,例如2014-01-14T02:20:13.592Z 并且可以从 java.time.Clock 类获取,如下所示:
Instant current = Clock.system(ZoneId.of("Asia/Tokyo")).instant();
LocalDate - 表示没有时间的日期,例如2014-01-14。可用于存储生日、周年纪念、加入日期等。
LocalTime - 表示没有日期的时间
LocalDateTime - 用于组合日期和时间,但仍然没有任何偏移量或时区
ZonedDateTime - 带有时区的完整日期时间和已解决的 UTC/格林威治偏移量 - 主包是java.time,它包含表示Date、Time、Instant和Period时间的类。
- 时区是地球上使用相同标准时间的区域。每个时区都由一个标识符描述,通常具有区域/城市(亚洲/东京)格式和格林威治/UTC 时间的偏移量。例如,东京的偏移量为 +09:00。
- OffsetDateTime 类实际上结合了 LocalDateTime 类和 ZoneOffset 类。它用于表示完整的日期(年、月、日)和时间(小时、分钟、秒、纳秒)与格林威治/UTC 时间的偏移量(+/- 小时:分钟,例如 +06:00 或 - 08:00)。
- DateTimeFormatter 类用于在 Java 中格式化和解析日期。与 SimpleDateFormat 不同,这既是不可变的又是线程安全的,并且可以(并且应该)在适当的情况下将其分配给静态常量。 DateFormatter 类提供了许多预定义的格式化程序,还允许您定义自己的格式化程序。
遵循约定,Java 中也有 parse() 将 String 转换为 Date 并抛出 DateTimeParseException ,如果转换过程中出现任何问题。同样,DateFormatter 有一个 format() 方法来格式化 Java 中的日期,如果出现问题,它会抛出 DateTimeException 错误。 - 补充一点,日期格式“MMM d yyyy”和“MMM dd yyyy”之间存在细微差别,因为前者会同时识别“Jan 2, 2014”和“Jan 14, 2014”,但稍后会抛出异常你通过“Jan 2, 2014”,因为它总是期望每个月的哪一天有两个字符。要解决此问题,您必须始终通过在开头填充零来将个位数天作为两位数传递,例如“Jan 2, 2014”应改为“Jan 02 2014”。
参考文章
1)https://javarevisited.blogspot.com/2015/03/20-examples-of-date-and-time-api-from-Java8.html#axzz6wPyf7OpT
2)https://www.java67.com/2016/03/how-to-convert-date-to-localdatetime-in-java8-example.html