Skip to content

一、改进背景

Java 8针对时间处理进行了全面的改进,重新设计了所有日期时间、日历及时区相关的 API。并把它们都统一放置在 java.time 包和子包下

Java5的不足之处:

  1. 非线程安全:java.util.Date 并不是线程安全的,在使用这个类时必须自己处理多线程并发问题。

  2. 设计不佳 :日期和日期格式化分布在多个包中,java.util.Date 的默认日期,年是从1900开始,月从 1 开始,日从 0 开始,没有统一性。而且 Date 类也缺少直接操作日期的相关方法。

  3. 时区处理困难:因为设计不佳,不得不编写大量代码来处理时区问题。

Java8的改进方案:

  1. 线程安全:新的日期时间API是线程安全的不仅没有setter方法,而且任何对实例的变更都会返回一个新的实例而保证原来的实例不变。
  2. 日期修改:新的日期时间API提供了大量的方法,用于修改日期时间的各个部分,并返回一个新的实例。
  3. 域: 在时区方面,新的日期时间API引入了域这个概念。
  4. 组合拆分:针对原来复杂的 API 进行重新组合和拆分,分成了好多个类。

新API基于ISO标准日历系统,java.time包下的所有类都是不可变类型而且线程安全。

编号类的名称描述
1Instant时间戳
2Duration持续时间,时间差
3LocalDate只包含日期,比如:2020-01-15
4LocalTime只包含时间,比如:23:12:10
5LocalDateTime包含日期和时间,比如:2020-01-15 23:14:21
6Period时间段
7ZoneOffset时区偏移量,比如:+8:00
8ZonedDateTime带时区的时间
9Clock时钟,比如获取目前美国纽约的时间
10java.time.format.DateTimeFormatter时间格式化

一、使用 LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO-8601 (ISO-8601 日历系统是国际化组织制定的现代化公民的日期和时间的表达法)日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前时间的时间信息。也不包含与时区相关的信息。

LocalDate LocalTime LocalDateTime 三个类的使用方式一样,只是代表的不同而已。

下面就以LocalDateTime作为例子进行示范:

1.获取当前时间

java
/ * 获取当前时间
 */
@Test
public void test1() {
    LocalDateTime now = LocalDateTime.now();
}

2.获取指定时间日期

java
/ * 获取指定时间日期
 */
@Test
public void test2() {
    LocalDateTime now = LocalDateTime.of(2018, 6, 10, 10, 10, 10);
}

3.对时间进行加减操作

java
/ * 对时间进行加减操作
 */
@Test
public void test3() {
    LocalDateTime now = LocalDateTime.of(2018, 6, 10, 10, 10, 10);
    //加时间
    LocalDateTime time1 = now.plusYears(1).plusMonths(2).
            plusDays(3).plusHours(5).plusMinutes(45).
            plusSeconds(32);
    //剪时间
    LocalDateTime time2 = now.minusYears(1).minusMonths(2).
            minusDays(3).minusHours(5).minusMinutes(45).
            minusSeconds(32);
}

对旧的时间进行操作之后返回一个新的时间。

4.获取获取时间的年、月、日

java
/ * 获取时间的年、月、日
 */
@Test
public void test4() {
    LocalDateTime now = LocalDateTime.of(2018, 6, 10, 10, 10, 10);
    //获取年
    int year = now.getYear();
    //获取月
    Month month = now.getMonth();
    //获取月的value值
    int monthValue = now.getMonthValue();
    //获取在本月的第几天(日)
    int dayOfMonth = now.getDayOfMonth();
    //获取在本年的第几天
    int dayOfYear = now.getDayOfYear();
    //获取在本周的第几天(星期几)
    DayOfWeek dayOfWeek = now.getDayOfWeek();
    //获取小时
    int hour = now.getHour();
    //获取分钟
    int minute = now.getMinute();
    //获取秒
    int second = now.getSecond();
    //获取纳秒
    int nano = now.getNano();    
}

二、使用 Instant

时间戳(以Unix 元年 : 1970-01-01 00:00:00 到某个时间之间的毫秒数)

1.获取系统当前时间

java
/ * 获取当前时间 默认获取 UTC 时区
 */
@Test
public void test5() {
    Instant now = Instant.now();
}

上面时间默认获取到的是 UTC 时区的时间,与中国时间相差8小时,我们可以给它设置偏移量。

java
/ * 获取中国当前时间
 */
@Test
public void test6() {
    Instant now = Instant.now().
            atOffset(ZoneOffset.ofHours(8)).toInstant();
}

2. 获取当前时间的时间戳

java
/ * 获取中国当前时间戳
 */
@Test
public void test7() {
    long now = Instant.now().
            atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();
}

三、Duration

计算两个时间之间的间隔

java
 /  * 获取两个时间之间的间隔
  */
 @Test
 public void test8() {
     Instant now1 = Instant.now();
     Instant now2 = Instant.now().
             atOffset(ZoneOffset.ofHours(8)).toInstant();
     Duration.between(now1, now2);
     
     LocalDateTime now3 = LocalDateTime.now();
     LocalDateTime now4 = LocalDateTime.of(2018, 6, 10, 10, 10, 10);
     
     Duration.between(now3, now4);
 }

四、Period

计算两个日期之间的间隔

java
/ * 获取两个日期之间的间隔
 */
@Test
public void test9() {
    LocalDate now1 = LocalDate.now();
    LocalDate now2 = LocalDate.of(2018, 6, 10);
    
    Period.between(now1, now2);
}

五、时间校正器

TemporalAdjuster:时间矫正器。有时我们可能需要获取一个周末,或者下一个工作日等时间,这里 java8 就为我们提供了一个时间校正器,让我们对时间进行校准。

TemporalAdjusters:该类通过静态方法提供了大量的常用的TemporalAdjuster的实现供我们使用。

在localDateTime中,有一个with方法,其中可以让我们去写一TemporalAdjuster接口,而TemporalAdjusters类中,有许多常用的方法,下面就来看看:

java
/ * 时间校正器TemporalAdjuster
 */
@Test
public void test10() {
    LocalDateTime now1 = LocalDateTime.now();
    //获取月中 第一天
    now1.with(TemporalAdjusters.firstDayOfMonth());
    //获取下一年的第一天
    now1.with(TemporalAdjusters.firstDayOfNextYear());
    //获取年中第一天
    now1.with(TemporalAdjusters.lastDayOfYear());
    //获取月中最后一天
    now1.with(TemporalAdjusters.lastDayOfMonth());
    //获取下个星期一
    now1.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
    //自定时时间:下一个工作日,因为这里需要一个接口,所以完全可以自定义方法
    now1.with((e) -> {
        LocalDateTime now = (LocalDateTime)e; 
        DayOfWeek dow = now.getDayOfWeek();
        if (dow.equals(DayOfWeek.FRIDAY)) 
            return now.plusDays(3);
        else if (dow.equals(DayOfWeek.SATURDAY))
            return now.plusDays(2);
        return  now.plusDays(1);
    });
}

六、格式化

时间、日期格式化 DateTimeFormatter

1.时间转字符串

在 DateTimeFormatter 中为我们提供了一下多种格式转换方式,我们可以去选择使用。

java
/ * The printer and/or parser to use, not null.
 */
private final CompositePrinterParser printerParser;
/ * The locale to use for formatting, not null.
 */
private final Locale locale;
/ * The symbols to use for formatting, not null.
 */
private final DecimalStyle decimalStyle;
/ * The resolver style to use, not null.
 */
private final ResolverStyle resolverStyle;
/ * The fields to use in resolving, null for all fields.
 */
private final Set<TemporalField> resolverFields;
/ * The chronology to use for formatting, null for no override.
 */
private final Chronology chrono;
/ * The zone to use for formatting, null for no override.
 */
private final ZoneId zone;

当然,我们也可以自定义时间格式,进行转换。

java
 /*
  * 将时间转换为字符串
  */
 @Test
 public void test11() {
     LocalDate now = LocalDate.now();
     DateTimeFormatter dtf = DateTimeFormatter.ofPattern("YYYY-MM-DD HH:mm:ss");
     now.format(dtf);
 }

2.将字符串转换为时间

java
 /*
  * 将字符串转换为时间
  */
 @Test
 public void test12() {
     DateTimeFormatter dtf = DateTimeFormatter.ofPattern("YYYY-MM-DD HH:mm:ss");
     LocalDateTime.parse("2018-06-18 21:45:23", dtf);
 }

七、案例

示例1:Java 8中获取今天的日期

**Java 8 中的 LocalDate 用于表示当天日期。和java.util.Date不同,它只有日期,不包含时间。当你仅需要表示日期时就用这个类。

java
import java.time.LocalDate;

public class Demo01 {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        System.out.println("今天的日期:"+today);
    }
}

示例2:Java 8中获取年、月、日信息

java
import java.time.LocalDate;

public class Demo02 {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        int year = today.getYear();
        int month = today.getMonthValue();
        int day = today.getDayOfMonth();

        System.out.println("year:"+year);
        System.out.println("month:"+month);
        System.out.println("day:"+day);

    }
}

示例3:Java 8中处理特定日期

**我们通过静态工厂方法now()非常容易地创建了当天日期,你还可以调用另一个有用的工厂方法LocalDate.of()创建任意日期, 该方法需要传入年、月、日做参数,返回对应的LocalDate实例。这个方法的好处是没再犯老API的设计错误,比如年度起始于1900,月份是从0开 始等等。

java
import java.time.LocalDate;

public class Demo03 {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2018,2,6);
        System.out.println("自定义日期:"+date);
    }
}

示例4:Java 8中判断两个日期是否相等

java
import java.time.LocalDate;

public class Demo04 {
    public static void main(String[] args) {
        LocalDate date1 = LocalDate.now();

        LocalDate date2 = LocalDate.of(2018,2,5);

        if(date1.equals(date2)){
            System.out.println("时间相等");
        }else{
            System.out.println("时间不等");
        }

    }
}

示例5:Java 8中检查像生日这种周期性事件

java
import java.time.LocalDate;
import java.time.MonthDay;

public class Demo05 {
    public static void main(String[] args) {
        LocalDate date1 = LocalDate.now();

        LocalDate date2 = LocalDate.of(2018,2,6);
        MonthDay birthday = MonthDay.of(date2.getMonth(),date2.getDayOfMonth());
        MonthDay currentMonthDay = MonthDay.from(date1);

        if(currentMonthDay.equals(birthday)){
            System.out.println("是你的生日");
        }else{
            System.out.println("你的生日还没有到");
        }

    }
}

示例6:Java 8中获取当前时间

java
import java.time.LocalTime;

public class Demo06 {
    public static void main(String[] args) {
        LocalTime time = LocalTime.now();
        System.out.println("获取当前的时间,不含有日期:"+time);

    }
}

示例7:Java 8中获取当前时间

**通过增加小时、分、秒来计算将来的时间很常见。Java 8除了不变类型和线程安全的好处之外,还提供了更好的plusHours()方法替换add(),并且是兼容的。注意,这些方法返回一个全新的LocalTime实例,由于其不可变性,返回后一定要用变量赋值。

java
import java.time.LocalTime;

public class Demo07 {
    public static void main(String[] args) {
        LocalTime time = LocalTime.now();
        LocalTime newTime = time.plusHours(3);
        System.out.println("三个小时后的时间为:"+newTime);

    }
}

示例8:Java 8如何计算一周后的日期

**和上个例子计算3小时以后的时间类似,这个例子会计算一周后的日期。LocalDate日期不包含时间信息,它的plus()方法用来增加天、周、月,ChronoUnit类声明了这些时间单位。由于LocalDate也是不变类型,返回后一定要用变量赋值。

java
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class Demo08 {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        System.out.println("今天的日期为:"+today);
        LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
        System.out.println("一周后的日期为:"+nextWeek);

    }
}

可以看到新日期离当天日期是7天,也就是一周。你可以用同样的方法增加1个月、1年、1小时、1分钟甚至一个世纪,更多选项可以查看Java 8 API中的ChronoUnit类

示例9:Java 8计算一年前或一年后的日期

**利用minus()方法计算一年前的日期

java
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class Demo09 {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();

        LocalDate previousYear = today.minus(1, ChronoUnit.YEARS);
        System.out.println("一年前的日期 : " + previousYear);

        LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
        System.out.println("一年后的日期:"+nextYear);

    }
}

示例10:Java 8的Clock时钟类

**Java 8增加了一个Clock时钟类用于获取当时的时间戳,或当前时区下的日期时间信息。以前用到System.currentTimeInMillis()和TimeZone.getDefault()的地方都可用Clock替换。

java
import java.time.Clock;

public class Demo10 {
    public static void main(String[] args) {
        // Returns the current time based on your system clock and set to UTC.
        Clock clock = Clock.systemUTC();
        System.out.println("Clock : " + clock.millis());

        // Returns time based on system clock zone
        Clock defaultClock = Clock.systemDefaultZone();
        System.out.println("Clock : " + defaultClock.millis());

    }
}

示例11:如何用Java判断日期是早于还是晚于另一个日期

**另一个工作中常见的操作就是如何判断给定的一个日期是大于某天还是小于某天?在Java 8中,LocalDate类有两类方法isBefore()和isAfter()用于比较日期。调用isBefore()方法时,如果给定日期小于当前日期则返回true。

java
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;


public class Demo11 {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();

        LocalDate tomorrow = LocalDate.of(2018,2,6);
        if(tomorrow.isAfter(today)){
            System.out.println("之后的日期:"+tomorrow);
        }

        LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
        if(yesterday.isBefore(today)){
            System.out.println("之前的日期:"+yesterday);
        }
    }
}

示例12:Java 8中处理时区

**Java 8不仅分离了日期和时间,也把时区分离出来了。现在有一系列单独的类如ZoneId来处理特定时区,ZoneDateTime类来表示某时区下的时间。这在Java 8以前都是 GregorianCalendar类来做的。下面这个例子展示了如何把本时区的时间转换成另一个时区的时间。

java
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Demo12 {
    public static void main(String[] args) {
        // 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);
    }
}

示例13:如何表示信用卡到期这类固定日期,答案就在YearMonth

**与 MonthDay检查重复事件的例子相似,YearMonth是另一个组合类,用于表示信用卡到期日、FD到期日、期货期权到期日等。还可以用这个类得到 当月共有多少天,YearMonth实例的lengthOfMonth()方法可以返回当月的天数,在判断2月有28天还是29天时非常有用。

java
import java.time.*;

public class Demo13 {
    public static void main(String[] args) {
        YearMonth currentYearMonth = YearMonth.now();
        System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
        YearMonth creditCardExpiry = YearMonth.of(2019, Month.FEBRUARY);
        System.out.printf("Your credit card expires on %s %n", creditCardExpiry);
    }
}

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

java
import java.time.LocalDate;

public class Demo14 {
    public static void main(String[] args) {
        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");
        }

    }
}

示例15:计算两个日期之间的天数和月数

**有一个常见日期操作是计算两个日期之间的天数、周数或月数。在Java 8中可以用java.time.Period类来做计算。 **下面这个例子中,我们计算了当天和将来某一天之间的月数。

java
import java.time.LocalDate;
import java.time.Period;

public class Demo15 {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();

        LocalDate java8Release = LocalDate.of(2018, 12, 14);

        Period periodToNextJavaRelease = Period.between(today, java8Release);
        System.out.println("Months left between today and Java 8 release : "
                + periodToNextJavaRelease.getMonths() );


    }
}

示例16:在Java 8中获取当前的时间戳

**Instant类有一个静态工厂方法now()会返回当前的时间戳,如下所示:

java
import java.time.Instant;

public class Demo16 {
    public static void main(String[] args) {
        Instant timestamp = Instant.now();
        System.out.println("What is value of this instant " + timestamp.toEpochMilli());
    }
}

时间戳信息里同时包含了日期和时间,这和java.util.Date很像。实际上Instant类确实等同于 Java 8之前的Date类,你可以使用Date类和Instant类各自的转换方法互相转换,例如:Date.from(Instant) 将Instant转换成java.util.Date,Date.toInstant()则是将Date类转换成Instant类。

示例17:Java 8中如何使用预定义的格式化工具去解析或格式化日期

java
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class Demo17 {
    public static void main(String[] args) {
        String dayAfterTommorrow = "20180205";
        LocalDate formatted = LocalDate.parse(dayAfterTommorrow,
                DateTimeFormatter.BASIC_ISO_DATE);
        System.out.println(dayAfterTommorrow+"  格式化后的日期为:  "+formatted);
    }
}

示例18:字符串互转日期类型

java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Demo18 {
    public static void main(String[] args) {
        LocalDateTime date = LocalDateTime.now();

        DateTimeFormatter format1 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        //日期转字符串
        String str = date.format(format1);

        System.out.println("日期转换为字符串:"+str);

        DateTimeFormatter format2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        //字符串转日期
        LocalDate date2 = LocalDate.parse(str,format2);
        System.out.println("日期类型:"+date2);

    }
}

Released under the MIT License.