Java8之前的日期与时间的处理,最常用的是Data、Calendar这两个类,还有格式化的SimpleDateFormat类。这三个类是经常使用的。则三个类的功能其实是非常薄弱的。我们的痛点列举如下:
1、进行一些日期计算的时候,比如当前日期+60天之后的日期。或30天之前的日期,或5周之后的日期,3周之前的日期,3个月、2年之后的日期等等;
Data类是一个可变的类,是很容易出错的。SimpleDateFormat不是线程安全的。
针对以上痛点,在Java8出来之前,社区里已经出现了一个专门的项目针对日期和时间处理的。这个项目就叫JodaTime。它提供了非常多的易用而且见名知意的非常非常多的一些方法,这些方法都非常实用,可以解决日常开发中很多时间处理的问题。在Java8出来之前时间处理还有一个非常头疼的问题就是时区的概念,Java8出来之前就没有提供时区处理的方式。
时区概念:中国是处在东八区,像日本就是东九区,比如北京上上午十点,东京就是上午十一点,同一个时刻,在不同的国家时间是不一样的。这一点在之前的Java8日期API中根本就没有提供解决方案。
JodaTime可以和Java即有的类例如Data进行互相转化。我用JoadTime处理完一些时间之后还可以转换回到Data上。
从Java8开始,在Java里就引入了一套全新的日期与时间的API。如果你之前在项目中大量使用了JodaTime,那么你学习Java8提供的这套日期与时间API将会非常轻松。Java8的这套日期与时间API,包括设计思想,都是极大的得到了JodaTime的启发,很多地方都是沿用了JoadTime里的一些设计方式。新的API里解决了日期线程安全的问题。
下面大概过一些JodaTime这个项目里面的一些子项目:
Joda-Time:提供与日期和时间处理的基础类
Joda_money:提供与货币、钱相关的一些基础处理,金融项目里面经常会用到的
Joda-Beans:基础的管理Java Bean的方法
Joda-Convert:将字符串转化成对象,也可以将对象转换程字符串。
Joda-Collect:对于集合的数据库结构提供额外的支撑
Joda-Primitives:提供针对原生数据类型的集合,避免装箱拆箱的操作
Java8里新提供的日期和时间的基础设施是在rt.jar里面的java.time包以及这个包下面的子包里,从Java8开始,建议使用Java8里面的内容替换掉项目里面使用的JodaTime。
关于日期与时间:
1、世界标准时间:格林威治标准时间,全球时间的基准,其他地方的时间都是在这个基准时间的基础上加或者减时间。
2、UTC时间,不带时区的一个标准时间。例如格式是2018-12-1T11:22:33.567Z【mongodb时间底层存储的就是这种格式的】
3、ISO8601,本身是一个标准,就是针对日期与时间标准的处理形式。JodaTime默认使用的时间格式。
下面举些示例:
public boolean isAfterPayDay(DateTime datetime) {
if (datetime.getMonthOfYear() == 2) { // February is month 2!!
return datetime.getDayOfMonth() > 26;
}
return datetime.getDayOfMonth() > 28;
}
public Days daysToNewYear(LocalDate fromDate) {
LocalDate newYear = fromDate.plusYears(1).withDayOfYear(1);
return Days.daysBetween(fromDate, newYear);
}
public boolean isRentalOverdue(DateTime datetimeRented) {
Period rentalPeriod = new Period().withDays(2).withHours(12);
return datetimeRented.plus(rentalPeriod).isBeforeNow();
}
public String getBirthMonthText(LocalDate dateOfBirth) {
return dateOfBirth.monthOfYear().getAsText(Locale.ENGLISH);
}
下面开始学习JodaTime了:
package com.shensiyuan.java8;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
public class JodaTest2 {
public static void main(String[] args) {
// 今天
DateTime today = new DateTime();
// 明天
DateTime tomorry = today.plusDays(1);
System.out.println(today.toString("yyyy-MM-dd"));
System.out.println(tomorry.toString("yyyy-MM-dd"));
System.out.println("=======================");
// 返回一个datetime的副本同时更新了天这个字段,就是把天这个字段设置为1
DateTime d1 = today.withDayOfMonth(1);
System.out.println(d1.toString("yyyy-MM-dd"));
System.out.println("=====================");
// 表示当前的时区
LocalDate localDate = new LocalDate();
System.out.println(localDate);
System.out.println("=====================");
// 计算距离当前这一天后面三个月的最后一天的日期
// dayOfMonth表示获取到了那个月份的那一天的这个属性。withMaximumValue就是把它设置成当前这个月份里面最小可以设置的那个天数(就是1)
// withMaximumValue()表示把它设置成当前这个月份里最大可以设置的那一天,也就是取一个月最后一天(不需要自己考虑那一天到底是有28、30还是31天)。
localDate = localDate.plusMonths(3).dayOfMonth().withMinimumValue();
System.out.println(localDate);
System.out.println("=====================");
// 计算两年前的第三个月的最后一天的日期
DateTime dateTime = new DateTime();
// monthOfYear()获取到月份,setCopy(3)将这个月份设置为3,dayOfMonth()获取到月份的天
DateTime dateTime2 = dateTime.minusYears(2).monthOfYear().setCopy(3).dayOfMonth().withMinimumValue();
System.out.println(dateTime2.toString("yyyy-MM-dd"));
}
}
更有价值的示例:
package com.shensiyuan.java8;
import java.util.Date;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
public class JodaTest3 {
public static void main(String[] args) {
// 传入的是一个utc标准时间,而北京是位于东八区的,所以打印出来的时间会在这个时间的基础上加八个小时
System.out.println(JodaTest3.convertUTC2Date("2014-11-04T09:22:54.876Z"));
// 这个是东八区的时间转换成标准的utc时间,要减去8小时
System.out.println(JodaTest3.convertDate2UTC(new Date()));
System.out.println(JodaTest3.convertDate2LocalByDateFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
// 说明一点:做app的时候,前后端交互,后端给前端返回的时间格式最好是utc的,因为这个是不带时区的,返回到前端之后,前端app肯定知道它所处的时区是哪个,它自己把这个转换成对应时区的时间即可
// 给定一个UTC标准时间,把它转换成一个日期类型
// 标准UTC时间:2014-11-04T09:22:54.876Z
// 常用的由客户端传过来的时间转换成一个服务器存储的时间
public static Date convertUTC2Date(String utcDate) {
try {
DateTime dateTime = DateTime.parse(utcDate, DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
return dateTime.toDate();
} catch (Exception e) {
return null;
}
}
// 给定一个日期类型,返回utc字符串类型
// 常用的由服务器返回的时间转换成一个utc的标准时间
public static String convertDate2UTC(Date javaDate) {
DateTime dateTime = new DateTime(javaDate, DateTimeZone.UTC);
return dateTime.toString();
}
// 格式化给定日期
public static String convertDate2LocalByDateFormat(Date javaDate, String dateFormat) {
DateTime dateTime = new DateTime(javaDate);
return dateTime.toString(dateFormat);
}
}
强调一点:
无论是jodaTime还是java8里面的日期时间的api,他们都是不可变的对象,目的是为了确保线程安全,每一次创建的时候都是返回一个全新的对象去使用。这个里面返回的月份都是从1开始的,不是从0开始,可读性更强。
Java8之前的Date、Calendar、SimpleDateFormat都是线程不安全的,都是可变的对象。这个里面返回的月是从0开始的,比如3月,返回的是2,可读性不强。
下面看Java8里面提供的日期和时间api的使用方式(java8提供的日期时间类基本都位于java.time这个包下):
package com.shensiyuan.java8;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.Period;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Set;
public class Java8TimeTest {
public static void main(String[] args) {
// 当前时区里的当前时间
// LocalDate关注的是年月日
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
System.out.println(localDate.getYear() + "," + localDate.getMonthValue() + "," + localDate.getDayOfMonth());
System.out.println("===================");
LocalDate localDate2 = LocalDate.of(2017, 3, 3);
System.out.println(localDate2);
System.out.println("===================");
LocalDate localDate3 = LocalDate.of(2010, 3, 25);
// MonthDay表示有月份有天,没有年的概念。如果只关注月和日,不关注年则可以使用这个类
MonthDay monthDay = MonthDay.of(localDate3.getMonth(), localDate3.getDayOfMonth());
MonthDay monthDay2 = MonthDay.from(LocalDate.of(2011, 3, 25));
if (monthDay.equals(monthDay2)) {
System.out.println("equals");
} else {
System.out.println("not equals");
}
System.out.println("===================");
// LocalTime关注的是时分秒
LocalTime time = LocalTime.now();
System.out.println(time);
// 增加三小时,增加20分钟
LocalTime time2 = time.plusHours(3).plusMinutes(20);
System.out.println(time2);
System.out.println("===================");
// 找到当前这一天的下两周的时间
LocalDate localDate1 = localDate.plus(2, ChronoUnit.WEEKS);
System.out.println(localDate1);
System.out.println("===================");
// 当前这一天的两个月之前的日期
LocalDate localDate4 = localDate.minus(2, ChronoUnit.MONTHS);
System.out.println(localDate4);
System.out.println("===================");
// Clock表示时钟的意思表示当前的时刻
// systemDefaultZone表示当前默认的时区
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.millis());
System.out.println("===================");
LocalDate localDate5 = LocalDate.now();
LocalDate localDate6 = LocalDate.of(2017, 3, 19);
System.out.println(localDate5.isAfter(localDate6));
System.out.println(localDate5.isBefore(localDate6));
System.out.println(localDate5.equals(localDate6));
System.out.println("===================");
// ZoneId表示时区,getAvailableZoneIds()返回的是所有的时区信息
Set<String> set = ZoneId.getAvailableZoneIds();
set.stream().forEach(System.out::println);
System.out.println("===================");
// 构造一个时区,后面是时区的名字
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
// 本地的带有日期和时间两个维度的这样的一个对象
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
// 构造一个带有时区的时间
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);
System.out.println(zonedDateTime);
System.out.println("===================");
// 带有年也月的时间
YearMonth yearMonth = YearMonth.now();
System.out.println(yearMonth);
// 月份的天数
System.out.println(yearMonth.lengthOfMonth());
// 是不是闰年
System.out.println(yearMonth.isLeapYear());
System.out.println("===================");
YearMonth yearMonth1 = YearMonth.of(2016, 2);
System.out.println(yearMonth1);
// 这个月的长度
System.out.println(yearMonth1.lengthOfMonth());
// 这年的长度
System.out.println(yearMonth1.lengthOfYear());
System.out.println(yearMonth1.isLeapYear());
System.out.println("===================");
LocalDate localDate7 = LocalDate.now();
LocalDate localDate8 = LocalDate.of(2017, 8, 25);
// Period表示周期的意思。between表示包含前边的,不包含后边的一个时间范围
Period period = Period.between(localDate7, localDate8);
// 得到间隔的年数
System.out.println(period.getYears());
// 得到间隔的月数
System.out.println(period.getMonths());
// 得到间隔的天数
System.out.println(period.getDays());
System.out.println("===================");
// Instant表示时间线上的一个时间点。输出的是不带时区的标准的UTC的格式
System.out.println(Instant.now());
}
}
下面举更多的例子:
Period的between方法含义: