时间日期

9/26/2022 Java

心灵鸡汤

人生是一场追求,也是一场领悟。我们这一生,路要自己走,一个人,先得受伤才可以明了,先得摔倒才开始成长,先得丢失才会有获得。很多时候,就是在跌跌拌拌中,我们学会了生活。

# 一:旧API

# 1.1 Date

Java 提供了 java.util.Date 类来处理日期、时间,Date 对象既包含日期,也包含时间。从JDK 1.0 起就开始存在了,但正因为历史悠久,所以大部分构造器、方法都已经过时,不在推荐使用了。

Date类提供6个构造器,其中4个已经Deprecated,剩下两个构造器如下:

  • Date():生成一个代表当前日期时间的 Date 对象。该构造器在底层调用 System.currentTimeMillis() 获取long整数作为日期参数。
  • Date(long date):根据指定的long型整数来生成一个 Date 对象。该构造器的参数表示创建的 Date 对象和 GMT 1970 年 1 月 1 日 00:00:00 之间的时间差,以毫秒作为计时单位。

Date 对象的大部分方法也 Deprecated 了,剩下为数不多的几个方法。

  • boolean after(Date when):测试该日期是否在指定日期 when 之后。
  • boolean before(Date when):测试该日期是否在指定日期 when 之前。
  • long getTime():返回该时间对应的 long 型整数,即从 GMT 1970-01-01 00:00:00 到该 Date 对象之间的时间差,以毫秒作为计时单位。
public class DateTest {

    public static void main(String[] args) {
        Date d1 = new Date();
        // 获取当前时间后 100 ms 的时间
        Date d2 = new Date(System.currentTimeMillis() + 100);
        System.out.println(d2);                 // Mon Sep 26 15:03:00 CST 2022
        System.out.println(d1.compareTo(d2));   // -1
        System.out.println(d1.before(d2));      // true
    }

}
1
2
3
4
5
6
7
8
9
10
11
12

Date 是一个设计相当糟糕的类,因此 Java 官方推荐尽量少用 Date 的构造器和方法。如果需要对日期、时间进行加减运算,或获取指定时间的年、月、日、时、分、秒信息,可使用 Calendar 工具类。

# 1.2 Calendar

因为Date类在设计上存在一些缺陷,所以Java提供了Calendar类来更好地处理日期和时间。Calendar是一个抽象类,它用于表示日历。

历史上有着许多种纪年方法,它们的差异实在太大了,比如说一个人的生日是 "七月七日",那么一种可能是阳(公)历的七月七日,但也可以是阴(农)历的日期。为了统一计时,全世界通常选择最普及、最通用的日历:Gregorian Calendar,也就是日常介绍年份时常用的 "公元几几年"。

Calendar类本身是一个抽象类,它是所有日历类的模板,并提供了一些所有日历通用的方法;但它本身不能直接实例化,程序只能创建Calendar子类的实例,Java 本身提供了一个GregorianCalendar类,一个代表格里高利日历的子类,它代表了通常所说的公历。

Calendar类是一个抽象类,所以不能使用构造器来创建Calendar对象。但它提供了几个静态 getInstance() 方法来获取Calendar对象,这些方法根据TimeZone,Locale类来获取特定的Calendar,如果不指定TimeZone、Locale,则使用默认的TimeZone、Locale来创建Calendar。

类声明:public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>

常用API如下:

  • void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
  • int get(int field):返回指定日历字段的值。
  • int getActualMaximum(int field):返回指定日历字段可能拥有的最大值。例如月,最大值为11。
  • int getActualMinimum(int field):返回指定日历字段可能拥有的最小值。例如月,最小值为0。
  • void roll(int field, boolean up):与add()方法类似,区别在于加上amount后超过了该字段所能表示的最大范围时,也不会向上一个字段进位。
  • void set(int field, int value):将给定的日历字段设置为给定值。
  • void set(int year, int month, int date):设置Calendar对象的年、月、日三个字段的值。
  • void set(int year, int month, int date, int hourOfDay, int minute):设置Calendar对象的年、月、日、时、分、秒6个字段的值。

上面的很多方法都需要一个int类型的field参数,field是Calendar类的类变量,如 Calendar.YEARCalendar.MONTH 等等分别代表了年、月、日、小时、分钟、秒等时间字段。需要指出的是,Calendar.MONTH字段代表月份,月份的起始值不是1,而是0,所以要设置8月时,用7而不是8。

add与roll的区别?

add(int field, int amount) 的功能非常强大,add 主要用于改变Calendar的特定字段的值。如果需要增加某字段的值,则让amount为正数;如果需要减少某字段的值,则让amount为负数即可。add 有如下两条规则。

  1. 当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大;
  2. 如果下一级字段也需要改变,那么该字段会修正到变化最小的值。

roll() 的规则与 add() 的处理规则不同:

  1. 当被修改的字段超出它允许的范围时,上一级字段不会增大;
  2. 下一级字段的处理规则与 add() 相似。
public class CalendarTest1 {

    public static void main(String[] args) {
        // Calendar 和 Date 都是表示日期的工具类,他们之间可以自由转换
        Calendar calendar1 = Calendar.getInstance();
        // 从 Calendar 对象中去除 Date 对象
        Date date = calendar1.getTime();

        // 通过 Date 对象获取对应的 Calendar 对象
        // 因为 Calendar/GregorianCalendar 没有构造函数可以接收 Date 对象
        // 所以必须先获得一个 Calendar 实例,然后调用其 setTime() 方法
        Calendar calendar2 = Calendar.getInstance();
        calendar2.setTime(date);

        // =============================API=============================
        Calendar calendar3 = Calendar.getInstance();
        // 取出年
        System.out.println(calendar3.get(Calendar.YEAR));       // 2022
        // 去除月份
        System.out.println(calendar3.get(Calendar.MONTH));      // 8    // 代表九月
        // 取出日
        System.out.println(calendar3.get(Calendar.DATE));       // 26
        // 分别设置年、月、日、小时、分钟、秒
        calendar3.set(2022, 6, 8, 9, 10, 11); // 2022-07-08 09:10:11
        System.out.println(calendar3.getTime());                // Fri Jul 08 09:10:11 CST 2022
        // 将 Calendar 的年前推 1 年
        calendar3.add(Calendar.YEAR, -1);
        System.out.println(calendar3.getTime());                // Thu Jul 08 09:10:11 CST 2021
        // 将 Calendar 的月前推 5 个月
        calendar3.roll(Calendar.MONTH, -5);
        System.out.println(calendar3.getTime());                // Mon Feb 08 09:10:11 CST 2021
        System.out.println("====================================================");

        // =============================add/roll=============================
        // add() 规则一:当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大
        Calendar calendar4 = Calendar.getInstance();
        calendar4.set(2003, Calendar.AUGUST, 23, 0, 0, 0);  // 2003-08-23
        calendar4.add(Calendar.MONTH, 6);                                               // 2004-02-23
        System.out.println(calendar4.getTime());                                                // Mon Feb 23 00:00:00 CST 2004

        // add() 规则二:如果下一级字段也需要改变,那么该字段会修正到变化最小的值
        Calendar calendar5 = Calendar.getInstance();
        calendar5.set(2003, 7, 31, 0, 0, 0);          // 2003-08-31
        // 因为进位后月份改成2月,2月没有31日,自动变成29日
        calendar5.add(Calendar.MONTH, 6);                                               // 2004-02-29
        // 上面例子 08-31 变成 02-29,并不是变成 03-02。因为 MONTH 的下一级字段是 DATE,从 31 到 29 改变最小。
        System.out.println(calendar5.getTime());                                                // Sun Feb 29 00:00:00 CST 2004

        // roll() 规则一:当被修改的字段超出它允许的范围时,上一级字段不会增大
        Calendar calendar6 = Calendar.getInstance();
        calendar6.set(2003, Calendar.AUGUST, 23, 0, 0, 0);  // 2003-08-23
        // MONTH 增加,没进位到 YEAR
        calendar6.roll(Calendar.MONTH, 6);                                              // 2003-02-23
        System.out.println(calendar6.getTime());                                                // Sun Feb 23 00:00:00 CST 2003

        // roll() 规则二:下一级字段的处理规则与 add() 相似。
        Calendar calendar7 = Calendar.getInstance();
        calendar7.set(2003, Calendar.AUGUST, 31, 0, 0, 0);  // 2003-08-31
        // YEAR 字段不会改变,2022年2月有29天
        calendar7.roll(Calendar.MONTH, 6);                                              // 2003-02-28
        System.out.println(calendar7.getTime());                                                // Fri Feb 28 00:00:00 CST 2003
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

容错性

调用Calendar对象的 set() 方法来改变指定时间字段的值时,有可能传入一个不合法的参数,例如为MONTH字段设置13,这将会导致怎样的后果呢?

public class CalendarTest2 {

    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        // 结果是 YEAR 字段加1
        calendar.set(Calendar.MONTH, 13);
        System.out.println(calendar.getTime());

        // 关闭容错性
        calendar.setLenient(false);

        // 导致运行异常
        calendar.set(Calendar.MONTH, 13);
        System.out.println(calendar.getTime());
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Calendar提供了一个 setLenient() 用于设置它的容错性,Calendar默认支持较好的容错性,通过 setLenient(false) 可以关闭Calendar的容错性,让它进行严格的参数检查。

Calendar有两种解释日历字段的模式:lenient模式和non-lenient模式。当Calendar处于lenient模式时,每个时间字段可接受超出它允许范围的值;当Calendar处于non-lenient模式时,如果为某个时间字段设置的值超出了它允许的取值范围,程序将会抛出异常。

set(f, value) 方法将日历字段f更改为 value,此外它还设置了一个内部成员变量,以指示日历字段f已经被更改。尽管日历字段 f 是立即更改的,但该Calendar所代表的时间却不会立即修改,直到下次调用 get()、getTime()、getTimeInMillis()、add() 或 roll() 时才会重新计算日历的时间。这被称为 set() 方法的延迟修改,采用延迟修改的优势是多次调用 set() 不会触发多次不必要的计算(需要计算出一个代表实际时间的long型整数)。

# 1.3 TimeZone

TimeZone类是一个抽象类,主要包含了对于时区的各种操作,可以进行计算时间偏移量或夏令时等操作。

类声明:public abstract public class TimeZone implements Serializable, Cloneable

  • GMT:即格林尼治标准时间,也就是世界时。但由于地球自转不均匀不规则,导致GMT不精确,现在已经不再作为世界标准时间使用。用 "GMT+偏移量" 来表示时区。如 "GMT+1" 代表东一区,时间就是零时区时间加1小时;"GMT-1" 就是西1区,时间就是零时区时间减1小时;中国位于东八区,就是 "GMT+8"。

  • UTC:标准时间,即协调世界时。是经过平均太阳时(以格林威治时间 GMT 为准)、地轴运动修正后的新时标,以「秒」为单位的国际原子时所综合精算而成的时间。为确保UTC与GMT相差不会超过0.9秒,在有需要的情况下(例如 1998-12-31T23:59:60Z)会在UTC内加上正或负闰秒。协调世界时区会使用 "Z" 来表示,协调世界时也会被称为 "Zulu time"。UTC现在作为世界标准时间使用

所以,UTC与GMT基本上等同,误差不超过0.9秒。不过日常使用中,GMT 与 UTC 的功能与精确度是没有差别的。

JAVA关于时间的存储

Java存的是从 1997/1/1 00:00:00 到现在时间的毫秒数,按的是标准时间存储。如 setDate(0) 就是 1997/1/1 00:00:00。但是获取时会加上时间的偏移量,我们是东八区,所以 setDate(0) 的显示是 1997/1/1 08:00:00

时区之间的转化处理

由于java中存的是标准时区的时间,只是获取时转成本地时区,所以只要对时间设置时区,就能获得对应时区的时间。

public class TimeZoneTest {

    public static void main(String[] args) {
        // ======================= 获取本地时区 =======================
        Calendar cal1 = Calendar.getInstance();
        TimeZone timeZone1 = cal1.getTimeZone();
        System.out.println(timeZone1.getID());      // Asia/Shanghai

        TimeZone timeZone2 = TimeZone.getDefault();
        System.out.println(timeZone2.getID());      // Asia/Shanghai

        SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        TimeZone timeZone3 = simpleDateFormat1.getTimeZone();
        System.out.println(timeZone3.getID());      // Asia/Shanghai


        // ======================= 设置指定时区 =======================
        TimeZone timeZone4 = TimeZone.getTimeZone("GMT+2");
        System.out.println(timeZone4.getID());      // GMT+02:00

        TimeZone timeZone5 = TimeZone.getTimeZone("Asia/Shanghai");
        System.out.println(timeZone5.getID());      // Asia/Shanghai


        // ======================= 设置指定时区 =======================
        Calendar cal2 = Calendar.getInstance(TimeZone.getTimeZone("GMT+2"));
        cal2.setTime(new Date());
        System.out.println((cal2.get(Calendar.MONTH) + 1) + "-" + cal2.get(Calendar.DATE) + " "
                + cal2.get(Calendar.HOUR_OF_DAY) + ":" + cal2.get(Calendar.MINUTE));    // 9-30 10:7

        SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        simpleDateFormat2.setTimeZone(TimeZone.getTimeZone("GMT+2"));
        System.out.println(simpleDateFormat2.format(new Date()));   // 2022-09-30 10:07:31


        // ======================= 设置指定时区 =======================
        Calendar cal = Calendar.getInstance();
        long offset = cal.get(Calendar.ZONE_OFFSET);
        System.out.println(offset / 60 / 60 / 1000);        // 8

        TimeZone timeZone = TimeZone.getDefault();
        long offset1 = timeZone.getOffset(Calendar.ZONE_OFFSET);
        System.out.println(offset1 / 60 / 60 / 1000);       // 8
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

如何确保返回的时区是正确的?

  1. 启动参数设置:-Duser.timezone=Asia/Shanghai
  2. 启动类设置:System.setProperty("user.timezone", "Asia/Shanghai");

# 二:Java8新增

Java原本提供了Date和Calendar用于处理日期、时间的类,包括创建日期、时间对象,获取系统当前日期、时间等操作。但 Date 不仅无法实现国际化,而且它对不同属性也使用了前后矛盾的偏移量,比如月份与小时都是从0开始的,月份中的天数则从1开始的,年又是从1900开始的,而 java.util.Calendar 则显得过于复杂,Java8 吸取了 Joda-Time 库(一个被广泛使用的日期、时间库)的经验,提供了一套全新的日期时间库。

Java 8专门新增了一个java.time包,该包下包含了如下常用的类。

  • Clock:该类用于获取指定时区的当前日期、时间。该类可取代System类的currentTimeMillis()方法,而且提供了更多方法来获取当前日期、时间。该类提供了大量静态方法来获取Clock对象。
public class ClockTest {

    public static void main(String[] args) {
        // 系统默认时间 未加8小时
        Clock now = Clock.systemDefaultZone();
        System.out.println(now.instant());                   // 2022-10-01T16:26:19.896Z

        // 世界协调时UTC
        Clock utc = Clock.systemUTC();
        // 通过 Clock 获取当前时刻
        System.out.println("当前时刻为:" + utc.instant());     // 当前时刻为:2022-10-01T16:26:19.907Z
        // 获取 clock 对应的毫秒数,与 System.currentTimeMillis() 输出相同
        System.out.println(utc.millis());                     // 1664641579907
        System.out.println(System.currentTimeMillis());       // 1664641579907

        // 在clock基础上增加6000秒,返回新的Clock
        Clock offset = Clock.offset(utc, Duration.ofSeconds(6000));
        System.out.println(offset.instant());                 // 2022-10-01T18:06:19.907Z

        // 纽约时间 比 中国慢 12 小时
        Clock clock = Clock.system(ZoneId.of("America/New_York"));
        System.out.println(clock.instant());                  // 2022-10-01T16:26:19.908Z
        System.out.println(LocalDateTime.now(clock));         // 2022-10-01T12:26:19.908
        System.out.println(clock.millis());                   // 1664641579910
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • Duration:该类代表持续时间。该类可以非常方便地获取一段时间。
public class DurationTest {

    // 因为 Duration 表示时间段,所以 Duration 类中不包含 now() 静态方法
    // Duration只能处理LocalTime, LocalDateTime, ZonedDateTime; 如果传入的是LocalDate,将会抛出异常
    // 常用API
    // 静态方法 between():计算两个时间的间隔,默认是秒
    // toDays():将时间转换为以天为单位的
    // toHours():将时间转换为以时为单位的
    // toMinutes():将时间转换为以分钟为单位的
    // toMillis():将时间转换为以毫秒为单位的
    // toNanos():将时间转换为以纳秒为单位的
    public static void main(String[] args) {
        Duration d = Duration.ofSeconds(6000);
        System.out.println("6000秒相当于" + d.toMinutes() + "分钟");  // 6000秒相当于100分钟
        System.out.println("6000秒相当于" + d.toHours() + "小时");    // 6000秒相当于1小时
        System.out.println("6000秒相当于" + d.toDays() + "天");       // 6000秒相当于0天

        Clock c1 = Clock.systemUTC();
        System.out.println(c1.instant());                           // 2022-09-30T06:18:14.374Z
        // 在 clock 基础上增加 6000 秒,返回新的 Clock
        Clock c2 = Clock.offset(c1, d);
        System.out.println("当前时刻加上6000秒为:" + c2.instant());   // 当前时刻加上6000秒为:2022-09-30T07:58:14.439Z
        Duration duration = Duration.between(c1.instant(), c2.instant());
        System.out.println(duration.toMinutes());                   // 100
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • Period:在概念上和 Duration 类似,区别在于 Period 是以年月日来衡量一个时间段。Duration 用于计算两个时间间隔,Period 用于计算两个日期间隔,所以 between() 方法只能接收 LocalDate 类型的参数。
public class PeriodTest {

    // 静态方法 between():计算两个日期之间的间隔
    // getYears():获取年份
    // getMonths():获取月份
    // getDays():获取天数
    public static void main(String[] args) {
        // 计算两个日期的间隔
        LocalDate old = LocalDate.of(2021, 9, 1);
        LocalDate now = LocalDate.now();
        System.out.println(now);    // 2022-10-01

        // 计算两个时间间隔
        Period between = Period.between(old, now);
        int years = between.getYears();
        int months = between.getMonths();
        int days = between.getDays();
        System.out.printf("%d-%d-%d\n", years, months, days);   // 1-1-0
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • Instant:代表一个具体的时刻,可以精确到纳秒。

  • LocalDate:该类代表不带时区的日期,例如2007-12-03。该类提供了静态的 now() 方法来获取当前日期,也提供了静态的 now(Clock clock) 方法来获取clock对应的日期。除此之外,它还提供了 minusXxx() 方法在当前年份基础上减去几年、几月、几周或几日等,也提供了 plusXxx() 方法在当前年份基础上加上几年、几月、几周或几日等。

  • LocalTime:该类代表不带时区的时间,例如10:15:30。该类提供了静态的 now() 方法来获取当前时间,也提供了静态的 now(Clock clock) 方法来获取clock对应的时间。除此之外,它还提供了 minusXxx() 方法在当前年份基础上减去几小时、几分、几秒等,也提供了 plusXxx() 方法在当前年份基础上加上几小时、几分、几秒等。

  • LocalDateTime:该类代表不带时区的日期、时间,例如2007-12-03T10:15:30。该类提供了静态的 now() 方法来获取当前日期、时间,也提供了静态的 now(Clock clock) 方法来获取clock对应的日期、时间。除此之外,它还提供了 minusXxx() 方法在当前年份基础上减去几年、几月、几日、几小时、几分、几秒等,也提供了 plusXxx() 方法在当前年份基础上加上几年、几月、几日、几小时、几分、几秒等。

public class LocalDateTimeTest1 {

    public static void main(String[] args) {
        // ========================= LocalDate =========================
        LocalDate localDate = LocalDate.now();
        System.out.println(localDate);          // 2022-09-30
        // 获取 2022 年的第 146 天
        localDate = LocalDate.ofYearDay(2022, 146);
        System.out.println(localDate);          // 2022-05-26
        // 设置为 2022 年 5 月 21 日
        localDate = LocalDate.of(2022, Month.MAY, 21);
        System.out.println(localDate);          // 2022-05-21

        // ========================= LocalTime =========================
        // 获取当前时间
        LocalTime localTime = LocalTime.now();
        System.out.println(localTime);          // 14:58:16.049
        // 设置为 22 点 33 分
        localTime = LocalTime.of(22, 33);
        System.out.println(localTime);          // 22:33
        // 返回一天中的第 5503 秒
        localTime = LocalTime.ofSecondOfDay(5503);
        System.out.println(localTime);          // 01:31:43

        // ========================= LocalDateTime =========================
        // 获取当前日期、时间
        LocalDateTime localDateTime = LocalDateTime.now();
        // 当前日期、时间加上 25 小时 3 分钟
        LocalDateTime future = localDateTime.plusHours(25).plusMinutes(3);
        System.out.println("当前日期、时间的 25 小时 3 分之后:" + future);   // 当前日期、时间的 25 小时 3 分之后:2022-10-01T16:01:16.049
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  • MonthDay:该类仅代表月日,例如--04-12。该类提供了静态的 now() 方法来获取当前月日,也提供了静态的 now(Clock clock) 方法来获取clock对应的月日。

  • Year:该类仅代表年,例如2014。该类提供了静态的 now() 方法来获取当前年份,也提供了静态的 now(Clock clock) 方法来获取clock对应的年份。除此之外,它还提供了 minusYears() 方法在当前年份基础上减去几年,也提供了 plusYears() 方法在当前年份基础上加上几年。

  • YearMonth:该类仅代表年月,例如2014-04。该类提供了静态的 now() 方法来获取当前年月,也提供了静态的 now(Clock clock) 方法来获取clock对应的年月。除此之外,它还提供了 minusXxx() 方法在当前年月基础上减去几年、几月,也提供了 plusXxx() 方法在当前年月基础上加上几年、几月。

public class YearMonthDayTest {

    public static void main(String[] args) {
        Year year = Year.now();  // 获取当前年份
        System.out.println("当前年份:" + year);             // 当前年份:2022
        year = year.plusYears(5);   // 当前年份再加 5 年
        System.out.println("当前年份再过 5 年:" + year);     // 当前年份再过 5 年:2027

        // 根据指定月份获取 YearMonth
        YearMonth ym = year.atMonth(10);
        System.out.println("year 年 10 月:" + ym);        // year 年 10 月:2027-10
        // 当前年月再加 5 年、减 3 个月
        ym = ym.plusYears(5).minusMonths(3);
        System.out.println("year 年 10 月 再加 5 年、减 3 个月:" + ym);  // year 年 10 月 再加 5 年、减 3 个月:2032-07

        MonthDay md1 = MonthDay.now();
        System.out.println("当前月日:" + md1);             // 当前月日:--09-30
        // 设置为 5 月 23 日
        MonthDay md2 = md1.with(Month.MAY).withDayOfMonth(23);
        System.out.println("5 月 23 日 为:" + md2);        // 5 月 23 日 为:--05-23
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • ZonedDateTime:该类代表一个时区化的日期、时间。

  • ZoneId:该类代表一个时区。

  • DayOfWeek:这是一个枚举类,定义了周日到周六的枚举值。

  • Month:这也是一个枚举类,定义了一月到十二月的枚举值。

# 2.1 Instant

承载纳秒级精度的 Unix 时间戳,其 toString() 方法基于 ISO-8601 进行格式化。Instant 不承载时区信息。该类提供了静态的now()方法来获取当前时刻,也提供了静态的 now(Clock clock) 方法来获取clock对应的时刻。除此之外,它还提供了一系列 minusXxx() 方法在当前时刻基础上减去一段时间,也提供了 plusXxx() 方法在当前时刻基础上加上一段时间。不承载时区信息。

只能解析 T/Z 这种格式的时间,即UTC字符串; UTC时间字符串包含TZ,其中"T" 后面跟着时间,"Z" 表示UTC统一时间,也可以说代表0时区。例如:2022-10-01T15:53:54.139Z

类声明:public final class Instant implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable

获取对象的方法now() 注意默认获取出来的是默认时区,和我们相差八个小时(因为我们在东八时区); 设置偏移量的方法OffsetDateTime atOffset(ZoneOffset offset)获取系统默认时区时间的方法ZonedDateTime atZone(ZoneId zone) 方法的参数是要一个时区的编号(可以通过时区编号类获取ZonedDateTime类的对象)

get系列的方法

  • long getEpochSecond():获取从1970-01-01 00:00:00到当前时间的秒值
  • long toEpochMilli():获取从1970-01-01 00:00:00到当前时间的毫秒值
  • int getNano():把获取到的当前时间的秒数 换算成纳秒

ofEpoch系列方法

  • static Instant ofEpochSecond(long epochSecond):给计算机元年增加秒数
  • static Instant ofEpochMilli(long epochMilli):给计算机元年增加毫秒数
public class InstantTest {

    public static void main(String[] args) {
        // 获取当前时间,按照默认时区,获取的不是中国的时区
        Instant now = Instant.now();
        System.out.println(now);                // 2022-10-01T15:53:54.139Z
        // instant 添加 6000 秒(即 100 分钟),返回新的 Instant
        Instant instant1 = now.plusSeconds(6000);
        System.out.println(instant1);           // 2022-10-01T17:33:54.139Z
        // 根据字符串解析 Instant 对象
        Instant instant2 = Instant.parse("2022-09-30T06:18:14.374Z");
        System.out.println(instant2);           // 2022-09-30T06:18:14.374Z
        // 在 instant3 的基础上添加 5 小时 4 分钟
        Instant instant3 = instant2.plus(Duration.ofHours(5).plusMillis(4));
        System.out.println(instant3);           // 2022-09-30T11:18:14.378Z
        // 获取 instant4 的 5 天以前的时刻
        Instant instant4 = instant3.minus(Duration.ofDays(5));
        System.out.println(instant4);           // 2022-09-25T11:18:14.378Z

        // 获取当前时区,可以添加偏移量,通过偏移过后的日期
        ZoneOffset zoneOffset = ZoneOffset.ofHours(8);
        OffsetDateTime offsetDateTime = now.atOffset(zoneOffset);
        System.out.println(offsetDateTime);     // 2022-10-01T23:53:54.139+08:00

        // 获取从 1970-01-01 00:00:00 到当前时间间隔的秒值
        long epochSecond = now.getEpochSecond();
        System.out.println(epochSecond);        // 1664639634

        // 给计算机元年增加毫秒数
        Instant instant5 = Instant.ofEpochMilli(1000 * 60 * 60 * 24);
        System.out.println(instant5);           // 1970-01-02T00:00:00Z

        // 给计算机元年增加秒数
        Instant instant6 = Instant.ofEpochSecond(60 * 60 * 24);
        System.out.println(instant6);           // 1970-01-02T00:00:00Z
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 2.2 LocalDateTime

LocalDate、LocalTime、LocalDateTime 不承载时区信息,因此,其不能与 Instant 相互转换,必须提供时区信息LocalDateTime 不能解析的时间字符串末尾有**"Z"**的时间。

类声明:public final class LocalDateTime implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable

获取对象的方法

  1. 通过静态方法now()(获取的时间是系统当前的时间)、now(ZoneId zone)now(Clock clock)
  2. 通过静态方法of(...)(方法参数可以指定时间)
// 通过静态方法 now() 返回该类的实例
// 获取当前的日期时分秒
LocalDateTime now = LocalDateTime.now();
System.out.println(now);                // 2022-09-30T23:37:55.607

// 通过静态方法 of() 返回该类的实例化
// 指定日期时分秒
LocalDateTime localDateTime = LocalDateTime.of(2023, 01, 01, 01, 01, 01);
System.out.println(localDateTime);      // 2023-01-01T01:01:01
1
2
3
4
5
6
7
8
9

常用方法

get系列的方法

  • int getYear():获取年;
  • int getHour():获取小时;
  • int getMinute():获取分钟;
  • int getSecond():获取秒值;
  • int getDayOfMonth():获得月份天数(1-31);
  • int getDayOfYear():获得年份天数(1-366);
  • DayOfWeek getDayOfWeek():获得星期几(返回一个 DayOfWeek枚举值);
  • Month getMonth():获得月份(返回一个 Month 枚举值);
  • int getMonthValue():获得月份(1-12);
  • int getYear():获得年份。
// 获取日期时分秒
LocalDateTime now = LocalDateTime.now();
System.out.println(now);                // 2022-10-01T00:02:46.847

// 获取年份
int year = now.getYear();
System.out.println(year);               // 2022

// 获取月份枚举
// Month 枚举类,定义了十二个月份
Month month = now.getMonth();
System.out.println(month);              // OCTOBER
System.out.println(month.getValue());   // 10

// 获取月份的数值
int monthValue = now.getMonthValue();
System.out.println(monthValue);         // 10

// 获取当天在本月的第几天
int dayOfMonth = now.getDayOfMonth();
System.out.println(dayOfMonth);         // 1

// 获取星期
DayOfWeek dayOfWeek = now.getDayOfWeek();
System.out.println(dayOfWeek);          // SATURDAY
System.out.println(dayOfWeek.getValue());   // 6

// 获取小时
int hour = now.getHour();
System.out.println(hour);               // 0

// 获取分钟
int minute = now.getMinute();
System.out.println(minute);             // 2

// 获取秒值
int second = now.getSecond();
System.out.println(second);             // 46
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

转换的方法

  • LocalDate toLocalDate():将LocalDateTime转换为相应的LocalDate对象;
  • LocalTime toLocalTime():将LocalDateTime转换为相应的LocalTime对象。
LocalDateTime now = LocalDateTime.now();
System.out.println(now);                // 2022-10-01T00:11:58.736

// 转换为LocalDate对象
LocalDate localDate = now.toLocalDate();
System.out.println(localDate);          // 2022-10-01

// 转换为LocalTime对象
LocalTime localTime = now.toLocalTime();
System.out.println(localTime);          // 00:11:58.736
1
2
3
4
5
6
7
8
9
10

判断的方法

  • boolean isAfter(ChronoLocalDateTime<?> other):判断一个日期是否在指定日期之后;
  • boolean isBefore(ChronoLocalDateTime<?> other):判断一个日期是否在指定日期之前;
  • boolean isEqual(ChronoLocalDateTime<?> other):判断两个日期是否相同;
  • boolean isLeapYear():判断是否是闰年(注意是LocalDate类特有的方法)。
// 获取当前的日期
LocalDateTime now = LocalDateTime.now();
// 指定的日期
LocalDateTime of1 = LocalDateTime.of(2022, 10, 2, 1, 2, 3);
System.out.println(now);                    // 2022-10-01T00:24:47.960
System.out.println(of1);                    // 2022-10-02T01:02:03

System.out.println(of1.isBefore(now));      // false
System.out.println(of1.isAfter(now));       // true

// 判断一个日期是否在另一个日期之后
LocalDateTime of2 = LocalDateTime.of(2022, 10, 2, 1, 2, 3);
System.out.println(of1.equals(of2));        // true

// 判断这两个日期是否相等
System.out.println(now.toLocalDate().isLeapYear()); // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

plus/minus系列的方法

增加相关的方法

  • LocalDateTime plusYears(long years):增加指定年份;
  • LocalDateTime plusMonths(long months):增加指定月份;
  • LocalDateTime plusWeeks(long weeks):增加指定周;
  • LocalDateTime plusDays(long days):增加指定日;
  • LocalDateTime plusHours(long hours):增加指定小时;
  • LocalDateTime plusMinutes(long minutes):增加指定分;
  • LocalDateTime plusSeconds(long seconds):增加指定秒;
  • LocalDateTime plusNanos(long nanos):增加指定纳秒。

减少相关的方法

  • LocalDateTime minusYears(long years):减少指定年;
  • LocalDateTime minusMonths(long months):减少指定月;
  • LocalDateTime minusWeeks(long weeks):减少指定周;
  • LocalDateTime minusDays(long days):减少指定日;
  • LocalDateTime minusHours(long hours):减少指定小时;
  • LocalDateTime minusMinutes(long minutes):减少指定分;
  • LocalDateTime minusSeconds(long seconds):减少指定秒;
  • LocalDateTime minusNanos(long nanos):减少指定纳秒。
// 获取当前的日期
LocalDateTime now = LocalDateTime.now();
System.out.println(now);            // 2022-10-01T01:02:46.112

// 增加 1 年
LocalDateTime newDate1 = now.plusYears(1);
System.out.println(newDate1);       // 2023-10-01T01:02:46.112

// 减去 10 天
LocalDateTime newDate2 = now.minusDays(10);
System.out.println(newDate2);       // 2022-09-21T01:02:46.112
1
2
3
4
5
6
7
8
9
10
11

指定年月日时分秒的方法

  • LocalDateTime with(TemporalAdjuster adjuster):指定特殊时间
  • LocalDateTime withYear(int year):指定年
  • LocalDateTime withDayOfYear(int dayOfYear):指定日
  • LocalDateTime withMonth(int month):指定月
  • LocalDateTime withDayOfMonth(int dayOfMonth):指定日
// 指定某个日期的方法 with() 方法
LocalDateTime now = LocalDateTime.of(2022, 10, 7, 1, 2, 3);
System.out.println(now);                                // 2022-10-07T01:02:03
LocalDateTime localDateTime = now.withYear(2014);
System.out.println(localDateTime);                      // 2014-10-07T01:02:03

// TemporalAdjuster 工具类,提供了一些获取特殊日期的方法
LocalDateTime with1 = now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(with1);                              // 2022-10-01T01:02:03
LocalDateTime with2 = now.with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(with2);                              // 2022-11-01T01:02:03

// 代表这个月的第二个星期五是几号
LocalDateTime with3 = now.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.FRIDAY));
System.out.println(with3);                              // 2022-10-14T01:02:03
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 2.3 TemporalAdjuster

有时候,需要进行一些更加灵活复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,就需要时间调节器 TemporalAdjuster,可以更加灵活地处理日期。TemporalAdjusters 工具提供了一些通用的功能,并且还可以新增自定义的功能。

TemporalAdjusters类中部分静态方法:

方法名 描述
dayOfWeekInMonth 返回同一个月中每周的第几天
firstDayOfMonth 返回当月的第一天
firstDayOfNextMonth 返回下月的第一天
firstDayOfNextYear 返回下一年的第一天
firstDayOfYear 返回本年的第一天
firstInMonth 返回同一个月中第一个星期几
lastDayOfMonth 返回当月的最后一天
lastDayOfYear 返回本年的最后一天
lastInMonth 返回同一个月中最后一个星期几
next/previous 返回后一个/前一个给定的星期几
nextOrSame/previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回
LocalDateTime now = LocalDateTime.now();
// 对于一些特殊的日期,可以通过一个工具类 TemporalAdjusters 来指定
// 本月第一天
TemporalAdjuster temporalAdjuster = TemporalAdjusters.firstDayOfMonth();
LocalDateTime with = now.with(temporalAdjuster);
System.out.println(with);                       // 2022-10-01T19:51:55.171

// 下周周末
LocalDateTime with1 = now.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
System.out.println(with1);                      // 2022-10-08T19:51:55.171

// 自定义日期 - 下一个工作日
LocalDateTime with2 = now.with((nowDate) -> {
LocalDateTime date = (LocalDateTime) nowDate;
    if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
    	return date.plusDays(3);
    } else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
    	return date.plusDays(2);
    } else {
    	return date.plusDays(1);
	}
});
System.out.println("下一个工作日是:" + with2);     // 下一个工作日是:2022-10-03T19:51:55.171
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 2.4 ZoneId

ZonedDateTime:带时区的日期时间,这个类方法及用法和LocalDateTime 基本一样,只不过ZonedDateTime 带有特定时区。LocalDateTime 内部并没有存储时区,所以对于系统的依赖性很强,往往换一个时区可能就会导致程序中的日期时间不一致。可以被理解为 LocalDateTime 的外层封装,它的内部存储了一个 LocalDateTime的实例,专门用于普通的日期时间处理。此外,它还定义了 ZoneId 和 ZoneOffset 来描述时区的概念。

LocalDateTime 可以通过传入时区的名称,使用 ZoneId 进行匹配存储,也可以通过传入与零时区的偏移量,使用 ZoneOffset 存储时区信息。

ZoneId:世界时区类,Java 使用 ZoneId 来标识不同的时区。时区从基准 UTC 开始的一个固定偏移。ZoneId 的子类 ZoneOffset,代表了这种从伦敦格林威治零度子午线开始的时间偏移,也就是时差。相当于是原有 java.util.TimeZone 类的替代品。

类声明:public abstract class ZoneId implements Serializable

常用API

  • Set<String> getAvailableZoneIds():获取世界各个地方的时区的集合;
  • ZoneId systemDefault():获取系统默认时区的ID;
  • ZoneId of(String zoneName):根据各个地区的时区ID名创建对象。
public class ZoneIdTest {

    public static void main(String[] args) {
        // ZoneId 世界时区类
        // 获取世界各地的时区编号
        Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        for (String availableZoneId : availableZoneIds) {
            System.out.println(availableZoneId);
        }
        System.out.println("=============================");

        // 获取系统的默认时区编号
        ZoneId zoneId1 = ZoneId.systemDefault();
        System.out.println(zoneId1);

        // 获取其他国家的日期
        LocalDateTime now = LocalDateTime.now();
        // 获取指定时区的日期时间
        ZoneId zoneId2 = ZoneId.of("Europe/Monaco");
        // 获取指定时区的当前时间
        ZonedDateTime zonedDateTime = now.atZone(zoneId2);
        System.out.println(zonedDateTime);
        System.out.println("=============================");

        // 根据时区,获取该地区的日期
        // 获取指定时区的当前时间(不带时区信息)
        LocalDateTime now1 = LocalDateTime.now(ZoneId.of("America/Phoenix"));
        System.out.println(now1);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

另一种表示时区的方式是使用 ZoneOffset,它是以当前时间和**世界标准时间(UTC)/格林威治时间(GMT)**的偏差来计算,例如:

ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);                        // 2022-10-03T08:31:30.093
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset);
System.out.println(offsetDateTime);                       // 2022-10-03T08:31:30.093+09:00
System.out.println(offsetDateTime.toLocalDateTime());     // 2022-10-03T08:31:30.093
1
2
3
4
5
6

# 2.5 默认汇总

public class DefaultTest {

    public static void main(String[] args) {
        Clock clock = Clock.system(ZoneId.systemDefault());
        System.out.println(clock);                      // SystemClock[Asia/Shanghai]
        System.out.println(clock.instant());            // 2022-10-01T16:50:11.111Z
        System.out.println(Instant.now(clock));         // 2022-10-01T16:50:11.123Z
        System.out.println(LocalDate.now(clock));       // 2022-10-02
        System.out.println(LocalTime.now(clock));       // 00:50:11.124
        System.out.println(LocalDateTime.now(clock));   // 2022-10-02T00:50:11.124
        System.out.println(ZonedDateTime.now(clock));   // 2022-10-02T00:50:11.124+08:00[Asia/Shanghai]
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2.6 小结

新的 API 区分各种日期时间概念并且各个概念使用相似的方法定义模式,这种相似性非常有利于 API 的学习。总结一下一般的方法规律:

  • of:静态工厂方法,用于创建实例;
  • now:静态工厂方法,用当前时间创建实例;
  • parse:静态工厂方法,从字符串解析得到对象实例;
  • get:获取对象的部分状态;
  • is:检查某些东西的是否是 true,例如比较时间前后;
  • with:返回一个部分状态改变了的时间日期对象拷贝(单独一个with方法,参数为TemporalAdjusters类型);
  • plus:返回一个时间增加了的时间日期对象拷贝;
  • minus:返回一个时间减少了的时间日期对象拷贝;
  • to:把当前时间日期对象转换成另外一个,可能会损失部分状态。;
  • at:把这个对象与另一个对象组合起来,例如:date.atTime(time);
  • format:将时间日期格式化为字符串。

# 三:相互转换

在转换中,需要注意,因为java8之前Date是包含日期和时间的,而LocalDate只包含日期,LocalTime只包含时间,所以与Date在互转中,势必会丢失日期或者时间,或者会使用起始时间。如果转LocalDateTime,那么就不存在信息误差。

# 3.1 Date和Instant互相转换

// Date 与 Instant 互相转换
Instant instant1 = Instant.now();
Date date = Date.from(instant1);
System.out.println(date);           // Sun Oct 02 00:54:54 CST 2022

Instant instant2 = date.toInstant();
System.out.println(instant2);       // 2022-10-01T16:54:54.650Z
1
2
3
4
5
6
7

# 3.2 Date与LocalDateTime互相转换

// Date -> LocalDateTime
Date date = new Date();
System.out.println("current date:" + date);                 // current date:Sun Oct 02 01:01:58 CST 2022

LocalDateTime localDateTime1 = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("localDateTime1:" + localDateTime1);     // localDateTime1:2022-10-02T01:01:58.160

LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("localDateTime2:" + localDateTime2);     // localDateTime2:2022-10-02T01:01:58.160

// LocalDateTime -> Date
LocalDateTime localDateTime3 = LocalDateTime.now();
System.out.println("localDateTime:" + localDateTime3);      // localDateTime:2022-10-02T01:01:58.218

Date date1 = Date.from(localDateTime3.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("date1:" + date1);                       // date1:Sun Oct 02 01:01:58 CST 2022
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3.2 Date与LocalDate互相转换

Date date = new Date();
System.out.println("current date:" + date);             // current date:Sun Oct 02 01:07:19 CST 2022

// Date -> LocalDate
LocalDate localDate1 = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
System.out.println("localDate:" + localDate1);          // localDate:2022-10-02

// LocalDate -> Date
LocalDate localDate2 = LocalDate.now();
// 因为 LocalDate 不包含时间,所以转 Date 时,会默认转为当天的起始时间,00:00:00
Instant instant = localDate2.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
Date date1 = Date.from(instant);
System.out.println("date1:" + date1);                   // date1:Sun Oct 02 00:00:00 CST 2022
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3.3 GregorianCalendar与ZonedDateTime相互转换

ZonedDateTime zonedDateTime = new GregorianCalendar().toZonedDateTime();
System.out.println("zonedDateTime:" + zonedDateTime);       // zonedDateTime:2022-10-02T01:11:25.859+08:00[Asia/Shanghai]
GregorianCalendar calendar = GregorianCalendar.from(zonedDateTime);
System.out.println("calendar:" + calendar.getTime());       // calendar:Sun Oct 02 01:11:25 CST 2022
1
2
3
4

# 3.4 TimeZone与ZoneId相互转换

TimeZone timeZone1 = TimeZone.getDefault();
ZoneId zoneId = timeZone1.toZoneId();
System.out.println(zoneId);             // Asia/Shanghai

TimeZone timeZone2 = TimeZone.getTimeZone(zoneId);
System.out.println(timeZone2.getID());  // Asia/Shanghai
1
2
3
4
5
6

# 3.5 long与Instant与LocalDateTime相互转换

// 13位long类型
long now = System.currentTimeMillis();
System.out.println(now);            // 1664758548971

// long -> Instant
Instant instant = Instant.ofEpochMilli(now);
System.out.println(instant);        // 2022-10-03T00:55:48.971Z

// Instant -> LocalDateTime
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
System.out.println(localDateTime);  // 2022-10-03T08:55:48.971

// LocalDateTime -> Instant
ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
instant = localDateTime.toInstant(zoneOffset);
System.out.println(instant);        // 2022-10-02T23:55:48.971Z

// Instant -> 13位long类型
now = instant.toEpochMilli();
System.out.println(now);            // 1664754948971
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 四:参考文献

最后更新: 10/3/2022, 9:53:11 AM