常见方法

在新的时间API中,Instant表示一个精确的时间点,DurationPeriod表示两个时间点之间的时间量。
LocalDate表示日期,即xx年xx月xx日,即不包括时间也不带时区。LocalTimeLocalDate类似,
但只包含时间。LocalDateTime则包含日期和时间。ZoneDateTime表示一个带时区的时间。
DateTimeFormatter提供格式化和解析功能。

  1. now() 静态方法,根据当前时间创建对象
  2. of() 静态方法,根据指定日期/时间创建对象
  3. plusDays(),plusWeeks(),plusMonths(),plusYears() 向当前 LocalDate 对象添加几天、几周、几个月、几年
  4. minusDays(),minusWeeks(),minusMonths(),minusYears() 从当前 LocalDate 对象减去几天、几周、几个月、几年
  5. plus(), minus() 添加或减少一个 Duration Period
  6. withDayOfMonth(),withDayOfYear(),withMonth(),withYear() 将月份天数、年份天数、月份、年份修改为指定 的值 并返回新的LocalDate 对象
  7. getDayOfMonth() 获得月份天数(1-31)
  8. getDayOfYear() 获得年份天数(1-366)
  9. getDayOfWeek() 获得星期几(返回一个 DayOfWeek枚举值)
  10. getMonth() 获得月份, 返回一个 Month 枚举值
  11. getMonthValue() 获得月份(1-12)
  12. getYear() 获得年份
  13. until() 获得两个日期之间的 Period 对象,或者指定 ChronoUnits 的数字
  14. isBefore(), isAfter() 比较两个 LocalDate
  15. isLeapYear() 判断是否是闰年

常用操作

// 1.将日期时间字符串转为LocalDateTime做完处理后再转回日期时间字符串
String str = "1986-04-08 12:30:23";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);
System.out.println(dateTime);  // 1986-04-08T12:30:23

String formatDate = dateTime.format(formatter);
System.out.println(formatDate); // 1986-04-08 12:30:23


// 2.获取当前日期时间并以自定义格式插入数据库
String dateTimeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));


注:
1)DateTimeFormatter 在这里是双向可使用的,可作为字符串转LocalDateTime使用;也可以作为LocalDateTime转字符串使用
2)三个类都是以Local开头表示:使用系统所在时区,由当前的 Unix 时间构建

...这里的常用操作会持续更新...

示例1 如何 在Java 8中获取当天的日期

Java 8中有一个叫LocalDate的类,它能用来表示今天的日期。这个类与java.util.Date略有不同,因为它只包含日期,没有时间。因此,如果你只需要表示日期而不包含时间,就可以使用它。

LocalDate today = LocalDate.now(); 
System.out.println("Today's Local date : " + today); 

输出
Today's Local date : 2018-06-01

你可以看到它创建了今天的日期却不包含时间信息。它还将日期格式化完了再输出出来,不像之前的Date类那样,打印出来的数据都是未经格式化的。

示例2 如何在Java 8中获取当前的年月日

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);

输出 
Today's Local date : 2018-06-01
Year : 2018 Month : 6 day : 1

可以看到,在Java 8中获取年月信息非常简单,只需使用对应的getter方法就好了,无需记忆,非常直观。你可以拿它和Java中老的获取当前年月日的写法进行一下比较。

示例3 在Java 8中如何获取某个特定的日期

在第一个例子中,我们看到通过静态方法now()来生成当天日期是非常简单的,不过通过另一个十分有用的工厂方法LocalDate.of(),则可以创建出任意一个日期,它接受年月日的参数,然后返回一个等价的LocalDate实例。关于这个方法还有一个好消息就是它没有再犯之前API中的错,比方说,年只能从1900年开始,月必须从0开始,等等。这里的日期你写什么就是什么,比如说,下面这个例子中它代表的就是1月14日,没有什么隐藏逻辑。

LocalDate dateOfBirth = LocalDate.of(2010, 01, 14); 
System.out.println("Your Date of birth is : " + dateOfBirth); 

输出 : Your Date of birth is : 2010-01-14

可以看出,创建出来的日期就是我们所写的那样,2014年1月14日。

示例4 在Java 8中如何检查两个日期是否相等

如果说起现实中实际的处理时间及日期的任务,有一个常见的就是要检查两个日期是否相等。你可能经常会碰到要判断今天是不是某个特殊的日子,比如生日啊,周年纪念日啊,或者假期之类。有的时候,会给你一个日期,让你检查它是不是某个日子比方说假日。下面这个例子将会帮助你在Java 8中完成这类任务。正如你所想的那样,LocalDate重写了equals方法来进行日期的比较,如下所示:

LocalDate today = LocalDate.now();
LocalDate date1 = LocalDate.of(2018, 06, 01);
if(date1.equals(today)){
    System.out.printf("Today %s and date1 %s are same date %n", today, date1);
}

输出 
Today 2018-06-01 and date1 2018-06-01 are same date

在本例中我们比较的两个日期是相等的。同时,如果在代码中你拿到了一个格式化好的日期串,你得先将它解析成日期然后才能比较。你可以将这个例子与Java之前比较日期的方式进行下比较,你会发现它真是爽多了。

示例5 在Java 8中如何检查重复事件,比如说生日

在Java中还有一个与时间日期相关的实际任务就是检查重复事件,比如说每月的帐单日,结婚纪念日,每月还款日或者是每年交保险费的日子。如果你在一家电商公司工作的话,那么肯定会有这么一个模块,会去给用户发送生日祝福并且在每一个重要的假日给他们捎去问候,比如说圣诞节,感恩节,在印度则可能是万灯节(Deepawali)。如何在Java中判断是否是某个节日或者重复事件?使用MonthDay类。这个类由月日组合,不包含年信息,也就是说你可以用它来代表每年重复出现的一些日子。当然也有一些别的组合,比如说YearMonth类。它和新的时间日期库中的其它类一样也都是不可变且线程安全的,并且它还是一个值类(value class)。我们通过一个例子来看下如何使用MonthDay来检查某个重复的日期:

LocalDate today = LocalDate.now();
LocalDate dateOfBirth = LocalDate.of(2018, 06, 01);
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");
}

输出: Many Many happy returns of the day !!

虽然年不同,但今天就是生日的那天,所以在输出那里你会看到一条生日祝福。你可以调整下系统的时间再运行下这个程序看看它是否能提醒你下一个生日是什么时候,你还可以试着用你的下一个生日来编写一个JUnit单元测试看看代码能否正确运行。

示例6 如何在Java 8中获取当前时间

这与第一个例子中获取当前日期非常相似。这次我们用的是一个叫LocalTime的类,它是没有日期的时间,与LocalDate是近亲。这里你也可以用静态工厂方法now()来获取当前时间。默认的格式是hh:mm:ss:nnn,这里的nnn是纳秒。可以和Java 8以前如何获取当前时间做一下比较。

LocalTime time = LocalTime.now(); 
System.out.println("local time now : " + time);

输出 
local time now : 11:41:21.988 // in hour, minutes, seconds, nano seconds

可以看到,当前时间是不包含日期的,因为LocalTime只有时间,没有日期。

示例7 如何增加时间里面的小时数

很多时候我们需要增加小时,分或者秒来计算出将来的时间。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); 

输出 : 
Time after 2 hours : 13:43:49.420

可以看到当前时间2小时后是13:43:49.420。现在你可以将它和Java中增加或者减少小时的老的方式进行下比较。一看便知哪种方式更好。

示例8 如何获取1周后的日期

这与前一个获取2小时后的时间的例子类似,这里我们将学会如何获取到1周后的日期。LocalDate是用来表示无时间的日期的,它有一个plus()方法可以用来增加日,星期,或者月,ChronoUnit则用来表示这个时间单位。由于LocalDate也是不可变的,因此任何修改操作都会返回一个新的实例,因此别忘了保存起来。

LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); 
System.out.println("Today is : " + today); 
System.out.println("Date after 1 week : " + nextWeek); 

输出: 
Today is : 2018-06-01
Date after 1 week : 2018-06-08

可以看到7天也就是一周后的日期是什么。你可以用这个方法来增加一个月,一年,一小时,一分钟,甚至是十年,查看下Java API中的ChronoUnit类来获取更多选项。

示例9 一年前后的日期

这是上个例子的续集。上例中,我们学习了如何使用LocalDate的plus()方法来给日期增加日,周或者月,现在我们来学习下如何用minus()方法来找出一年前的那天。

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 : 2017-06-01
Date after 1 year : 2019-06-01

可以看到现在一共有两年,一个是2017年,一个是2019年,分别是2018的前后那年。

示例10 在Java中如何判断某个日期是在另一个日期的前面还是后面

这也是实际项目中常见的一个任务。你怎么判断某个日期是在另一个日期的前面还是后面,或者正好相等呢?在Java 8中,LocalDate类有一个isBefore()和isAfter()方法可以用来比较两个日期。如果调用方法的那个日期比给定的日期要早的话,isBefore()方法会返回true。

LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.of(2018, 6, 2);
if(tomorrow.isAfter(today)){
    System.out.println("Tomorrow comes after today");
}
LocalDate yesterday = today.minus(1, DAYS);
if(yesterday.isBefore(today)){
    System.out.println("Yesterday is day before today");
}

输出: 
Tomorrow comes after today 
Yesterday is day before today

可以看到在Java 8中进行日期比较非常简单。不需要再用像Calendar这样的另一个类来完成类似的任务了。

示例11 如何表示固定的日期,比如信用卡过期时间

正如MonthDay表示的是某个重复出现的日子的,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(2018, Month.FEBRUARY); 
System.out.printf("Your credit card expires on %s %n", creditCardExpiry); 

输出: 
Days in month year 2018-06: 30
Your credit card expires on 2018-02

示例12 如何在Java 8中检查闰年

这并没什么复杂的,LocalDate类有一个isLeapYear()的方法能够返回当前LocalDate对应的那年是否是闰年。

LocalDate today = LocalDate.now();
if(today.isLeapYear()){
    System.out.println("This year is Leap year");
}else {
    System.out.println("2018 is not a Leap year");
}

输出: 2018 is not a Leap year

你可以多检查几年看看结果是否正确,最好写一个单元测试来对正常年份和闰年进行下测试。

示例13 两个日期之间包含多少天,多少个月

还有一个常见的任务就是计算两个给定的日期之间包含多少天,多少周或者多少年。你可以用java.time.Period类来完成这个功能。在下面这个例子中,我们将计算当前日期与将来的一个日期之前一共隔着几个月。

/**
 * Duration: 计算两个"时间"间隔
 */
Instant ins1 = Instant.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
}
Instant ins2 = Instant.now();
System.out.println("时间间隔(s): " + Duration.between(ins1, ins2).getSeconds());

/**
 * Period: 计算两个"日期"间隔
 */
LocalDate local1 = LocalDate.now();
LocalDate local2 = LocalDate.of(2018, 5, 3);
System.out.println("时间间隔(d): " + Period.between(local2, local1).getDays());

输出: 
时间间隔(s): 1
时间间隔(d): 29

示例14 在Java 8中如何获取当前时间戳

如果你还记得在Java 8前是如何获取当前时间戳的,那现在这简直就是小菜一碟了。Instant类有一个静态的工厂方法now()可以返回当前时间戳,如下:

Instant timestamp = Instant.now(); // Instant.now().toEpochMilli() 获取毫秒值
System.out.println("What is value of this instant " + timestamp); 

输出 : 
What is value of this instant 2014-01-14T08:33:33.379Z

可以看出,当前时间戳是包含日期与时间的,与java.util.Date很类似,事实上Instant就是Java 8前的Date,你可以使用这两个类中的方法来在这两个类型之间进行转换,比如Date.from(Instant)是用来将Instant转换成java.util.Date的,而Date.toInstant()是将Date转换成Instant的。

示例15 如何在Java 8中使用预定义的格式器来对日期进行解析/格式化

在Java 8之前,时间日期的格式化可是个技术活,我们的好伙伴SimpleDateFormat并不是线程安全的,而如果用作本地变量来格式化的话又显得有些笨重。多亏了线程本地变量,这使得它在多线程环境下也算有了用武之地,但Java维持这一状态也有很长一段时间了。这次它引入了一个全新的线程安全的日期与时间格式器。它还自带了一些预定义好的格式器,包含了常用的日期格式。比如说,本例 中我们就用了预定义的BASIC_ISO_DATE格式,它会将2014年2月14日格式化成20140114。

String dayAfterTommorrow = "20180601"; 
LocalDate formatted = LocalDate.parse(dayAfterTommorrow, 
DateTimeFormatter.BASIC_ISO_DATE); 
System.out.printf("Date generated from String %s is %s %n", dayAfterTommorrow, formatted); 

输出 : 
Date generated from String 20180601 is 2018-06-01

你可以看到生成的日期与指定字符串的值是匹配的,就是日期格式上略有不同。

示例16 如何在Java 8中对日期进行格式化,转换成字符串

在上两个例子中,尽管我们用到了DateTimeFormatter类但我们主要是进行日期字符串的解析。在这个例子中我们要做的事情正好相反。这里我们有一个LocalDateTime类的实例,我们要将它转换成一个格式化好的日期串。这是目前为止Java中将日期转换成字符串最简单便捷的方式了。下面这个例子将会返回一个格式化好的字符串。与前例相同的是,我们仍需使用指定的模式串去创建一个DateTimeFormatter类的实例,但调用的并不是LocalDate类的parse方法,而是它的format()方法。这个方法会返回一个代表当前日期的字符串,对应的模式就是传入的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(); 
} 

输出 : Arriving at : 六月 01 2018 01:46 下午

可以看到,当前时间是用给定的”MMM dd yyyy hh:mm a”模式来表示的,它包含了三个字母表示的月份以及用AM及PM来表示的时间。

示例16 Instant 详解

Instant表示一个精确的时间,时间数轴就是由无数个时间点组成,数轴的原点就是上面提
到的1970-1-1 00:00:00Instant由两部分组成,一是从原点开始到指定时间点的秒数s,
二是距离该秒数s的纳秒数。
Java8-日期操作 - 图1

使用静态方法Instant.now()可以获取当前的时间点,该方法默认使用的是UTC(协调世界时——由原子钟提供)时间,可以使用equealcompareTo来比较两个时间点的值。
计算某段代码执行时间可以使用下面的方式:

Instant start = Instant.now();

doSomething();

Instant end = Instant.now();

Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
System.out.println("millis = " + millis);

Duration对象表示两个时间点之间的距离,通过类似toMillis() toDays() getSeconds()等方法,
得到各种时间单位表示的Duration对象。如果确实需要使用纳秒来做一些计算,可以调用toNanos()
获得一个long类型的值,该值表示距离原点的纳秒值。大概300年的纳秒值会导致long值溢出

Duration内部使用一个long类型来保存秒钟的值,使用一个int来保存纳秒的值,与Instant类似,
这个纳秒保存的是距离该秒钟的纳秒值.

Instant与Duration都可以进行一些运算,来调整表示的时间,比如:plus() minus 方法,
表示增加或减少一段时间,plusSeconds() minusSeconds() plusXxx()等表示增加或减少相应时间单位的一段时间。

Duration可以进行multipliedBy()乘法和dividedBy()除法运算。negated()做取反运算,即1.2秒取反后为-1.2秒。

非常重要的是,Instant 和 Duration类都是不可变的,他们的所有方法都返回一个新的实例。不可变类有很多优点:
不可变类使用起来不容易出错,其本质上是线程安全的,对象可以被自由的共享,而不用担心被某个方法修改。

示例16 毫秒值 Instant LocalDateTime转换

// 计算两个毫秒值的时间间隔
Instant ins1 = Instant.ofEpochMilli(1502340527872l);
Instant ins2 = Instant.ofEpochMilli(1529661441623l);
System.out.println("时间间隔(d): " + Duration.between(ins1, ins2).toDays()); 

// 毫秒值转换为Instant再转换为LocalDateTime
LocalDateTime.ofInstant(Instant.ofEpochMilli(1529661441623l), ZoneId.of("Asia/Shanghai"));

Java 8中日期与时间API的几个关键点

  1. 它提供了javax.time.ZoneId用来处理时区。

  2. 它提供了LocalDate与LocalTime类

  3. Java 8中新的时间与日期API中的所有类都是不可变且线程安全的,这与之前的Date与Calendar API中的恰好相反,那里面像java.util.Date以及SimpleDateFormat这些关键的类都不是线程安全的。

  4. 新的时间与日期API中很重要的一点是它定义清楚了基本的时间与日期的概念,比方说,瞬时时间,持续时间,日期,时间,时区以及时间段。它们都是基于 ISO-8601日历体系的。