ZonedDateTime(含时区的日期时间)

ZonedDateTime 是用于表示带时区的日期时间的类,位于 Java 8  的 java.time 包中,它整合了 LocalDateTime(本地日期时间)和 ZoneId(时区)信息,可精确到纳秒,且是不可变、线程安全的。

ZonedDateTime 主要特点:

  • 包含完整的日期(年、月、日)、时间(时、分、秒、纳秒)和时区(ZoneId)信息。

  • 解决了时区转换、夏令时调整等复杂时间问题(相比 LocalDateTime 仅表示本地时间,无时区概念)。

  • 所有修改方法(如增减时间)都会返回新实例,原实例不变(不可变性)。

  

常用方法及示例

实例创建方法

用于创建 ZonedDateTime 对象,常用方法如下:

now()

根据系统默认时区(ZoneId.systemDefault()),获取当前的日期时间(包含时区信息)。系统默认时区通常由运行环境决定,如操作系统的时区设置。例如:

package com.hxstrive.java_date_time.zonedDateTime;

import java.time.ZonedDateTime;

public class ZonedDateTimeExample {
    public static void main(String[] args) {
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println("当前默认时区时间:" + now);
    }
}

运行结果:

当前默认时区时间:2025-09-21T11:18:32.685785300+08:00[Asia/Shanghai]

上述示例,输出结果中包含了完整的日期时间(2025-09-21T11:18:32.685785300)、时区偏移量(+08:00,即 UTC+8)和时区 ID([Asia/Shanghai])。

now(ZoneId zone)

根据传入的 ZoneId(时区 ID),获取该时区下的当前日期时间。时区 ZoneId 可以通过 ZoneId.of(String zoneId) 方法创建,常用时区 ID 格式为区域/城市(如 Asia/Shanghai、Europe/London、America/New_York)。例如:

package com.hxstrive.java_date_time.zonedDateTime;

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

public class ZonedDateTimeExample2 {
    public static void main(String[] args) {
        // 指定时区:纽约(西五区)
        ZoneId newYorkZone = ZoneId.of("America/New_York");
        ZonedDateTime newYorkTime = ZonedDateTime.now(newYorkZone);
        System.out.println("纽约当前时间:" + newYorkTime);

        // 指定时区:伦敦(零时区)
        ZoneId londonZone = ZoneId.of("Europe/London");
        ZonedDateTime londonTime = ZonedDateTime.now(londonZone);
        System.out.println("伦敦当前时间:" + londonTime);
    }
}

运行结果:

纽约当前时间:2025-09-20T23:21:59.837686200-04:00[America/New_York]
伦敦当前时间:2025-09-21T04:21:59.861041500+01:00[Europe/London]

注意,不同时区的 now(ZoneId) 会返回对应时区的当前时间,偏移量会根据时区和是否为夏令时自动调整(如纽约夏令时为 UTC-4,冬令时为 UTC-5)。

of(...)

手动指定年、月、日、时、分、秒、纳秒和时区,创建一个精确的 ZonedDateTime 对象。of() 方法有多个重载版本,最常用的是指定完整的日期时间字段,定义如下:

ZonedDateTime.of(
    int year,          // 年份(如2023)
    int month,         // 月份(1-12,1=1月,12=12月)
    int dayOfMonth,    // 日(1-31,需符合当月天数)
    int hour,          // 时(0-23)
    int minute,        // 分(0-59)
    int second,        // 秒(0-59)
    int nanoOfSecond,  // 纳秒(0-999,999,999)
    ZoneId zone        // 时区
)

注意,必须确保所有参数符合实际历法(如月份不能为 0 或 13,日期不能超过当月最大天数),否则会抛出 DateTimeException。

示例代码:

package com.hxstrive.java_date_time.zonedDateTime;

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

public class ZonedDateTimeExample3 {
    public static void main(String[] args) {
        // 创建上海时区的2023年10月1日12:00:00
        ZonedDateTime shanghaiTime = ZonedDateTime.of(
            2023, 10, 1,   // 年月日
            12, 0, 0, 0,   // 时分秒纳秒
            ZoneId.of("Asia/Shanghai")  // 时区
        );
        System.out.println("上海指定时间:" + shanghaiTime);

        // 创建纽约时区的2023年12月25日8:30:00
        ZonedDateTime newYorkXmas = ZonedDateTime.of(
            2023, 12, 25, 8, 30, 0, 0,
            ZoneId.of("America/New_York")
        );
        System.out.println("纽约圣诞时间:" + newYorkXmas);
    }
}

运行结果:

上海指定时间:2023-10-01T12:00+08:00[Asia/Shanghai]
纽约圣诞时间:2023-12-25T08:30-05:00[America/New_York]

注意,of() 方法适合需要精确控制日期时间的场景(如预约时间、历史事件时间等),但需严格保证参数合法(例如 2 月不能传入 30 日)。

  

信息获取方法

用于获取 ZonedDateTime 中的日期、时间或时区信息:

  • getYear()  获取 ZonedDateTime 中的年份(四位数)。例如,若 zdt 表示 2024 年 8 月 28 日的时间,则 zdt.getYear() 返回 2024。

  • getMonthValue()  获取月份的数值(1-12,1 代表 1 月,12 代表 12 月)。例如,若 zdt 表示 8 月的时间,则 zdt.getMonthValue() 返回 8。

  • getDayOfMonth()  获取当月的日期(1-31,具体范围取决于月份)。例如,若 zdt 表示 8 月 28 日,则 zdt.getDayOfMonth() 返回 28。

  • getHour()  获取小时数(24 小时制,0-23,0 代表午夜,23 代表晚上 11 点)。例如,若 zdt 表示下午 3 点 30 分,则 zdt.getHour() 返回 15。

  • getMinute()  获取分钟数(0-59)。例如,若 zdt 表示 15:30,则 zdt.getMinute() 返回30。

  • getZone()  获取 ZonedDateTime 对象关联的时区(ZoneId 对象)。例如,若 zdt 是上海时区的时间,则 zdt.getZone() 返回 ZoneId.of("Asia/Shanghai")。

  • toLocalDateTime()  将 ZonedDateTime 转换为 LocalDateTime 对象,忽略时区信息,仅保留日期和时间部分。当需要单独处理本地日期时间(不关心时区)时使用。例如,若 zdt 为 2024-08-28T15:30+08:00[Asia/Shanghai],则 zdt.toLocalDateTime() 返回 2024-08-28T15:30。

  • toInstant()  将 ZonedDateTime 转换为 Instant 对象(UTC 时间戳),表示从 1970 年 1 月 1 日 00:00:00 UTC 开始的毫秒数(精确到纳秒)。用于跨时区时间比较(不同时区的时间转换为 Instant 后可直接比较先后)、时间戳存储等。例如,若 zdt 为上海时区的 2024-08-28T15:30:00+08:00(即 UTC 时间 2024-08-28T07:30:00),则 zdt.toInstant() 返回对应 UTC 时间的 Instant 对象。

示例代码:

package com.hxstrive.java_date_time.zonedDateTime;

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

public class ZonedDateTimeGettersExample4 {
    public static void main(String[] args) {
        // 创建一个上海时区的ZonedDateTime对象(2024年8月28日15:30:45)
        ZonedDateTime zdt = ZonedDateTime.of(
            2024, 8, 28,    // 年月日
            15, 30, 45, 0,  // 时分秒纳秒
            ZoneId.of("Asia/Shanghai")  // 上海时区(UTC+8)
        );
        
        // 1. 获取日期信息
        int year = zdt.getYear();
        int month = zdt.getMonthValue();
        int day = zdt.getDayOfMonth();
        
        // 2. 获取时间信息
        int hour = zdt.getHour();
        int minute = zdt.getMinute();
        
        // 3. 获取时区信息
        ZoneId zone = zdt.getZone();
        
        // 4. 转换为LocalDateTime(忽略时区)
        LocalDateTime localDateTime = zdt.toLocalDateTime();
        
        // 5. 转换为Instant(UTC时间戳)
        Instant instant = zdt.toInstant();
        
        // 打印结果
        System.out.println("原始ZonedDateTime: " + zdt);
        System.out.println("年份: " + year);
        System.out.println("月份: " + month);
        System.out.println("日期: " + day);
        System.out.println("小时: " + hour);
        System.out.println("分钟: " + minute);
        System.out.println("时区: " + zone);
        System.out.println("转换为LocalDateTime: " + localDateTime);
        System.out.println("转换为Instant(UTC时间): " + instant);
    }
}

运行结果:

原始ZonedDateTime: 2024-08-28T15:30:45+08:00[Asia/Shanghai]
年份: 2024
月份: 8
日期: 28
小时: 15
分钟: 30
时区: Asia/Shanghai
转换为LocalDateTime: 2024-08-28T15:30:45
转换为Instant(UTC时间): 2024-08-28T07:30:45Z

  

时间修改方法

用于增减时间或修改日期时间字段(返回新实例,原实例不变):

plusYears(long years)

在当前 ZonedDateTime 基础上增加指定的年数,返回一个新的 ZonedDateTime 对象(原对象不变,因为日期时间类是不可变的)。参数 years 可为正数或负数,负数表示减少年数。

注意:

(1)若计算结果超过 ZonedDateTime 支持的最大年份(通常是 +999999999)或小于最小年份(通常是 -999999999),会抛出 DateTimeException。

(2)调整年份时会自动处理月份和日期的合法性(例如 2024 年 2 月 29 日加 1 年变为 2025 年 2 月 28 日)。

minusDays(long days)  

在当前 ZonedDateTime 基础上减少指定的天数,返回新对象(原对象不变)。参数 days 可为正数或负数,负数表示增加天数,等效于 plusDays()。

注意:天数可以是任意整数(包括 0),计算时会自动处理跨月、跨年的情况。例如从 2024 年 3 月 1 日减少 1 天,会得到 2024 年 2 月 29 日(闰年)。示例:

zdt.minusDays(5); // 表示减少 5 天
zdt.minusDays(-3); // 等效于增加 3 天

withMonth(int month)  

将当前 ZonedDateTime 的月份修改为指定值,返回新对象(原对象不变)。参数 month 必须在 1-12 之间(1 代表 1 月,12 代表 12 月)。

注意:若参数超出 1-12 范围,会抛出 DateTimeException。若原日期的“日”在新月份中不存在(例如原时间是 3 月 31 日,修改为 2 月),会自动调整为新月份的最后一天(如 2024 年 2 月 29 日)。

withHour(int hour)

将 ZonedDateTime 的小时数修改为指定值(24 小时制),返回新对象(原对象不变)。参数 hour 必须在 0-23 之间(0 代表午夜,23 代表晚上 11 点)。

注意:若参数超出 0-23 范围,会抛出 DateTimeException。仅修改小时部分,分钟、秒、纳秒及日期、时区保持不变。

示例代码:

package com.hxstrive.java_date_time.zonedDateTime;

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

public class ZonedDateTimeAdjustExample5 {
    public static void main(String[] args) {
        // 创建初始ZonedDateTime:2024年3月15日14:30:00,上海时区(UTC+8)
        ZonedDateTime original = ZonedDateTime.of(
            2024, 3, 15, 14, 30, 0, 0,
            ZoneId.of("Asia/Shanghai")
        );
        System.out.println("初始时间: " + original);
        
        // 1. 增加2年(plusYears)
        ZonedDateTime after2Years = original.plusYears(2);
        System.out.println("增加2年后: " + after2Years);
        
        // 2. 减少5天(minusDays)
        ZonedDateTime minus5Days = original.minusDays(5);
        System.out.println("减少5天后: " + minus5Days);
        
        // 3. 减少负3天(等效于增加3天)
        ZonedDateTime plus3Days = original.minusDays(-3);
        System.out.println("增加3天后(通过minusDays(-3)): " + plus3Days);
        
        // 4. 修改月份为10月(withMonth)
        ZonedDateTime october = original.withMonth(10);
        System.out.println("修改为10月后: " + october);
        
        // 5. 修改小时为9点(withHour)
        ZonedDateTime nineOClock = original.withHour(9);
        System.out.println("修改为9点后: " + nineOClock);
        
        // 6. 综合调整:增加1年,减少10天,修改为6月,小时改为18点
        ZonedDateTime combined = original
            .plusYears(1)
            .minusDays(10)
            .withMonth(6)
            .withHour(18);
        System.out.println("综合调整后: " + combined);
    }
}

运行结果:

初始时间: 2024-03-15T14:30+08:00[Asia/Shanghai]
增加2年后: 2026-03-15T14:30+08:00[Asia/Shanghai]
减少5天后: 2024-03-10T14:30+08:00[Asia/Shanghai]
增加3天后(通过minusDays(-3)): 2024-03-18T14:30+08:00[Asia/Shanghai]
修改为10月后: 2024-10-15T14:30+08:00[Asia/Shanghai]
修改为9点后: 2024-03-15T09:30+08:00[Asia/Shanghai]
综合调整后: 2025-06-05T18:30+08:00[Asia/Shanghai]

  

时区转换方法

用于将ZonedDateTime转换到其他时区:

withZoneSameInstant(ZoneId zone)

将当前 ZonedDateTime 转换到指定时区,保持绝对时刻(Instant)不变,即转换前后表示的是同一物理时间点,只是时区不同。

Instant 是基于 UTC 时间戳计算目标时区的本地时间。例如,上海(UTC+8)的 10:00 对应的 UTC 时间是 02:00,转换到纽约(UTC-4,夏令时)时,本地时间为 02:00-4=前一天 22:00。

Instant 适用于需要表示“同一时刻在不同时区的本地时间”(如跨国会议的同一时间点在各地的显示时间)。示例:

// 2024-09-11T10:00:00+08:00
ZonedDateTime original = ZonedDateTime.of(
        2024, 9, 11, 10, 0, 0, 0, ZoneId.of("Asia/Shanghai")
);
ZonedDateTime target = original.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(target); // 2024-09-10T22:00-04:00[America/New_York]

withZoneSameLocal(ZoneId zone)

将当前 ZonedDateTime 的本地时间(年月日时分秒)原样复制到目标时区,不保证绝对时刻相同,即转换前后的本地时间数值相同,但可能是不同的物理时间点。

适合需要在不同时区使用相同的本地时间数值,如无论在哪个时区,都在本地时间 10 点执行任务。示例:

// 2024-09-11T10:00:00+08:00
ZonedDateTime original = ZonedDateTime.of(
        2024, 9, 11, 10, 0, 0, 0,
        ZoneId.of("Asia/Shanghai")
);
ZonedDateTime target = original.withZoneSameLocal(ZoneId.of("America/New_York"));
System.out.println(target); // 2024-09-11T10:00-04:00[America/New_York]

注意:

(1)若目标时区在该本地时间存在夏令时切换等异常(如时间不存在或重复),会自动调整(例如跳过不存在的时间,或选择较早的时间)。

(2) 转换结果的绝对时刻(Instant)会发生变化,因为时区不同。

下面是一个综合示例:

package com.hxstrive.java_date_time.zonedDateTime;

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

public class ZonedDateTimeZoneConversion {
    public static void main(String[] args) {
        // 1. 创建初始时间:上海时区(Asia/Shanghai)的2024-09-11 10:00:00
        ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
        ZonedDateTime shanghaiTime = ZonedDateTime.of(
            2024, 9, 11, 10, 0, 0, 0,
            shanghaiZone
        );
        System.out.println("初始时间(上海): " + shanghaiTime);
        System.out.println("对应的UTC时刻: " + shanghaiTime.toInstant()); // 打印绝对时刻
        
        // 2. 转换到纽约时区(保持瞬间不变:withZoneSameInstant)
        ZoneId newYorkZone = ZoneId.of("America/New_York");
        ZonedDateTime newYorkSameInstant = shanghaiTime.withZoneSameInstant(newYorkZone);
        System.out.println("\n纽约同一时刻(withZoneSameInstant): " + newYorkSameInstant);
        System.out.println("对应的UTC时刻(应与初始相同): " + newYorkSameInstant.toInstant());
        
        // 3. 转换到纽约时区(保持本地时间不变:withZoneSameLocal)
        ZonedDateTime newYorkSameLocal = shanghaiTime.withZoneSameLocal(newYorkZone);
        System.out.println("\n纽约相同本地时间(withZoneSameLocal): " + newYorkSameLocal);
        System.out.println("对应的UTC时刻(与初始不同): " + newYorkSameLocal.toInstant());
        
        // 4. 验证两个纽约时间的时间差
        long hoursDiff = java.time.Duration.between(
            newYorkSameInstant, newYorkSameLocal
        ).toHours();
        System.out.println("\n两个纽约时间的时差(小时): " + hoursDiff);
    }
}

运行结果:

初始时间(上海): 2024-09-11T10:00+08:00[Asia/Shanghai]
对应的UTC时刻: 2024-09-11T02:00:00Z

纽约同一时刻(withZoneSameInstant): 2024-09-10T22:00-04:00[America/New_York]
对应的UTC时刻(应与初始相同): 2024-09-11T02:00:00Z

纽约相同本地时间(withZoneSameLocal): 2024-09-11T10:00-04:00[America/New_York]
对应的UTC时刻(与初始不同): 2024-09-11T14:00:00Z

两个纽约时间的时差(小时): 12

Process finished with exit code 0

  

比较方法

用于比较两个ZonedDateTime的先后:

isBefore(ZonedDateTime other)

判断当前 ZonedDateTime 对象表示的时间是否在参数 other 表示的时间之前。无论两个对象的时区是否相同,都会先转换为同一时间基准(本质是比较对应的 Instant 时间戳),再判断先后。

若当前时间在参数时间之前返回 true,否则返回 false。

isAfter(ZonedDateTime other)

判断当前 ZonedDateTime 对象表示的时间是否在参数 other 表示的时间之后。与 isBefore() 一致,基于实际时间戳 Instant 比较,不受时区显示差异影响。

若当前时间在参数时间之后返回 true,否则返回 false。

isEqual(ZonedDateTime other)

判断两个 ZonedDateTime 对象是否表示同一时刻(忽略时区的显示差异)。即使两个对象的时区不同,只要它们对应的 UTC 时间戳(Instant)相同,就会返回 true。

示例代码:

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

public class ZonedDateTimeComparison {
    public static void main(String[] args) {
        // 创建三个不同时区的时间对象
        // 1. 上海时间:2024-09-11 10:00:00(UTC+8)
        ZonedDateTime shanghaiTime = ZonedDateTime.of(
            2024, 9, 11, 10, 0, 0, 0,
            ZoneId.of("Asia/Shanghai")
        );
        
        // 2. 纽约时间:2024-09-10 22:00:00(UTC-4,夏令时)
        ZonedDateTime newYorkTime = ZonedDateTime.of(
            2024, 9, 10, 22, 0, 0, 0,
            ZoneId.of("America/New_York")
        );
        
        // 3. 伦敦时间:2024-09-11 02:00:00(UTC+1,夏令时)
        ZonedDateTime londonTime = ZonedDateTime.of(
            2024, 9, 11, 2, 0, 0, 0,
            ZoneId.of("Europe/London")
        );
        
        // 打印各时间的原始值和对应的UTC时间
        System.out.println("上海时间: " + shanghaiTime + "(UTC时间: " + shanghaiTime.toInstant() + ")");
        System.out.println("纽约时间: " + newYorkTime + "(UTC时间: " + newYorkTime.toInstant() + ")");
        System.out.println("伦敦时间: " + londonTime + "(UTC时间: " + londonTime.toInstant() + ")\n");
        
        // 比较上海时间和纽约时间
        System.out.println("上海时间是否在纽约时间之前?" + shanghaiTime.isBefore(newYorkTime));
        System.out.println("上海时间是否在纽约时间之后?" + shanghaiTime.isAfter(newYorkTime));
        System.out.println("上海时间与纽约时间是否为同一时刻?" + shanghaiTime.isEqual(newYorkTime) + "\n");
        
        // 比较上海时间和伦敦时间
        System.out.println("上海时间是否在伦敦时间之前?" + shanghaiTime.isBefore(londonTime));
        System.out.println("上海时间是否在伦敦时间之后?" + shanghaiTime.isAfter(londonTime));
        System.out.println("上海时间与伦敦时间是否为同一时刻?" + shanghaiTime.isEqual(londonTime));
    }
}

运行结果:

上海时间: 2024-09-11T10:00+08:00[Asia/Shanghai](UTC时间: 2024-09-11T02:00:00Z)
纽约时间: 2024-09-10T22:00-04:00[America/New_York](UTC时间: 2024-09-11T02:00:00Z)
伦敦时间: 2024-09-11T02:00+01:00[Europe/London](UTC时间: 2024-09-11T01:00:00Z)

上海时间是否在纽约时间之前?false
上海时间是否在纽约时间之后?false
上海时间与纽约时间是否为同一时刻?true

上海时间是否在伦敦时间之前?false
上海时间是否在伦敦时间之后?true
上海时间与伦敦时间是否为同一时刻?false

Process finished with exit code 0

总之,ZonedDateTime 类提供了简洁、安全的时区时间处理方案,避免了旧 API(如 Date、Calendar)的线程不安全和设计缺陷。

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号