java-日期

一、时间基础

一、计算机时间表示基础

  1. Epoch Time(时间戳)

    • 定义:从 1970 年 1 月 1 日零点(GMT+00:00)至今的毫秒数(Java 中用long类型表示)。
    • 获取方式:通过System.currentTimeMillis()获取当前时间戳。
    • 作用:作为计算机存储时间的统一格式,与展示格式分离(如2023-10-01 12:00:00是展示格式,存储为时间戳1696152000000)。
  2. 存储与展示分离

    • 存储:仅保存时间戳(整数),不包含时区、格式等信息。
    • 展示:通过格式化方法将时间戳转换为指定时区和格式的字符串(如SimpleDateFormat)。

二、旧版日期时间 API(java.util包)

1. Date

  • 作用:表示日期和时间,内部存储毫秒级时间戳。

  • 注意事项

    • java.sql.Date(仅存储日期)区分。

    • 获取字段需手动调整:

      • getYear()需加1900(如getYear()+1900)。
      • getMonth()返回0-11(需加1表示 1-12 月)。
      • getDate()直接返回1-31(无需调整)。
  • 格式化 :通过SimpleDateFormat自定义格式

    • 示例:"yyyy-MM-dd HH:mm:ss"表示年 - 月 - 日 时:分: 秒。
    • 不同语言环境下输出差异:如"E MMM dd, yyyy"在英文环境输出Sun Sep 15, 2019
  • 缺点

    • 无法直接处理时区,默认使用系统时区。
    • 日期运算复杂(如计算两天之差需手动转换)。

2. Calendar

  • 作用:用于获取、设置和计算日期时间字段(年、月、日、时等)。

  • 基本用法

    • 获取当前时间:Calendar c = Calendar.getInstance()
    • 获取字段:c.get(Calendar.YEAR)(年份直接返回)、c.get(Calendar.MONTH)+1(月份需加 1)。
    • 设置时间:c.set(Calendar.YEAR, 2023);需先调用c.clear()清除默认值。
  • 时区处理

    • 通过TimeZone指定时区:c.setTimeZone(TimeZone.getTimeZone("America/New_York"))
    • 格式化时需设置目标时区:SimpleDateFormatsetTimeZone()方法。
  • 日期运算 :通过add()方法实现加减

    • 示例:c.add(Calendar.DAY_OF_MONTH, 5)(加 5 天);c.add(Calendar.HOUR_OF_DAY, -2)(减 2 小时)。

3. TimeZone

  • 作用 :表示时区,唯一标识为字符串 ID(如"Asia/Shanghai""GMT+09:00")。
  • 获取方式TimeZone.getDefault()(当前时区);TimeZone.getTimeZone("ID")(指定时区)。

三、新版日期时间 API(Java 8+,java.time包)

  • 设计优势

    • 不可变对象,线程安全。
    • 明确区分日期(LocalDate)、时间(LocalTime)、带时区时间(ZonedDateTime)。
    • 内置时区支持(ZoneId),简化时区转换。
  • 核心类

    • LocalDateTime:本地日期时间(无时区)。
    • ZonedDateTime:带时区的日期时间。
    • DateTimeFormatter:格式化工具(替代SimpleDateFormat)。
    • Instant:时间戳(对应 Epoch Time,精确到纳秒)。

四、新旧 API 对比与选择

特性 旧 API(java.util 新 API(java.time
线程安全 非线程安全(DateSimpleDateFormat 线程安全(不可变对象)
时区处理 需通过TimeZoneCalendar间接处理 直接支持ZoneIdZonedDateTime内置时区
日期运算 需手动计算或依赖Calendar.add() 提供plusDays()minusHours()等链式方法
易用性 字段获取需手动调整(如月份 + 1) 字段直接对应实际含义(如getMonthValue()返回 1-12)
推荐场景 兼容遗留代码 新项目开发,优先使用新 API

五、关键代码示例

  1. 旧 API 格式化日期

    java 复制代码
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(sdf.format(date)); // 输出:2023-10-01 12:30:00
  2. Calendar 时区转换

    java 复制代码
    Calendar c = Calendar.getInstance();
    c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
    c.set(2023, 9, 1, 8, 0, 0); // 注意:10月对应9(0-based)
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
    System.out.println(sdf.format(c.getTime())); // 输出:2023-09-30 19:00:00(纽约时区)

二、LocalDateTime 核心操作

  1. 创建实例

    • 获取当前时间LocalDateTime.now()(基于系统默认时区);LocalDate.now()/LocalTime.now()分别获取日期和时间。
    • 指定日期时间LocalDateTime.of(年, 月, 日, 时, 分, 秒)LocalDateTime.of(LocalDate, LocalTime)
    • 解析字符串LocalDateTime.parse("2023-10-01T12:30:00")(需符合 ISO 8601 格式,日期和时间用T分隔)。
  2. 日期时间运算

    • 加减操作dt.plusDays(5).minusHours(3)(返回新实例,原对象不变);支持DaysHoursMonths等单位。
    • 调整操作dt.withDayOfMonth(1)(设置为当月第 1 天);dt.with(TemporalAdjusters.lastDayOfMonth())(获取当月最后一天)。
  3. 比较先后 :使用isBefore()isAfter()方法判断两个日期时间的先后顺序,例如now.isBefore(target)

1、格式化与解析(DateTimeFormatter)

  1. 自定义格式输出 :通过DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")创建自定义格式器,例如dtf.format(LocalDateTime.now())
  2. 解析非标准字符串LocalDateTime.parse("2023/10/01 12:30:00", dtf),需确保字符串格式与DateTimeFormatter模式一致。

2、时间间隔(Duration vs Period)

类型 作用 示例 格式
Duration 表示两个时刻的时间间隔(精确到秒 / 纳秒) Duration.between(start, end) PT1235H10M30S(1235 小时 10 分 30 秒)
Period 表示两个日期的间隔(精确到天 / 月 / 年) LocalDate.of(...).until(LocalDate.of(...)) P1M21D(1 个月 21 天)

3、与旧 API 对比及设计背景

  • 替代旧 API :取代DateCalendarSimpleDateFormat,解决旧 API 线程不安全、设计不合理等问题。
  • 设计来源:借鉴开源库 Joda Time 的设计,由 Joda Time 作者参与开发,确保易用性和稳定性。

三、ZonedDateTime 概述

  • 定义 :表示带时区的日期和时间,可看作LocalDateTimeZoneId的组合。
  • 核心作用:用于处理不同时区的日期时间,解决跨时区的时间转换问题。
  • 与旧时区类区别 :使用ZoneId(属于java.time包),摒弃旧的java.util.TimeZone,设计更简洁且线程安全。

1、创建 ZonedDateTime 的方法

  1. 通过now()获取当前时间

    • 默认时区ZonedDateTime.now(),会使用系统默认时区。
    • 指定时区ZonedDateTime.now(ZoneId.of("时区ID")),例如ZoneId.of("America/New_York")
  2. 由 LocalDateTime 转换而来 :通过LocalDateTime.atZone(ZoneId)方法,为本地日期时间附加时区,如ldt.atZone(ZoneId.systemDefault())

2、时区转换操作

  • 核心方法withZoneSameInstant(ZoneId),可在不同时区间转换同一时刻的时间,转换后会自动调整日期和时间。

  • 夏令时影响:转换时需考虑时区的夏令时规则,不同日期转换结果可能存在差异,例如北京时间 11 月和 9 月转换为纽约时间有时差变化,所以切勿手动计算时差。

  • 与 LocalDateTime 互转

    • 转 LocalDateTime :使用toLocalDateTime()方法,此操作会丢弃时区信息。
    • 由 LocalDateTime 转 ZonedDateTime :通过atZone(ZoneId)方法为其添加时区。

3、实战练习:计算跨时区到达时间

  • 问题描述:已知北京起飞的本地时间和飞行时长,要求计算到达纽约的当地时间。

  • 解决思路

    1. 将北京起飞的LocalDateTime转换为带北京时区(Asia/Shanghai)的ZonedDateTime
    2. 给该时间加上飞行时长,得到到达时的北京时间(带时区)。
    3. 使用withZoneSameInstant()方法将其转换为纽约时区(America/New_York)的ZonedDateTime
    4. 最后通过toLocalDateTime()获取纽约当地时间。
  • 示例代码逻辑LocalDateTimeZonedDateTime(北京时区)→加飞行时间→ZonedDateTime(纽约时区)LocalDateTime

四、DateTimeFormatter 核心内容

1. 作用与背景

  • 用途 :用于格式化LocalDateTimeZonedDateTime(Java 8 + 新日期时间 API),替代旧版非线程安全的SimpleDateFormat

  • 核心优势

    • 不变性与线程安全 :可创建单例实例重复使用,避免SimpleDateFormat的线程安全问题。
    • 支持国际化(Locale) :按不同地区习惯格式化日期时间。

2. 创建方式

  • 基础格式化字符串

    java 复制代码
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");  

    格式符号与SimpleDateFormat一致(如yyyy年、MM月、dd日等)。

  • 指定 Locale

    java 复制代码
    DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, yyyy-MMMM-dd HH:mm", Locale.US);  

    根据地区习惯调整显示(如美国使用 "Sun, September/15/2019",中国使用 "周日,2019 年 9 月 15 日")。

3. 格式化示例

  • 代码演示

    java 复制代码
    ZonedDateTime zdt = ZonedDateTime.now();  
    // 默认格式(含时区)  
    System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ").format(zdt));  
    // 中国Locale:年 月 日 星期 时分  
    System.out.println(DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA).format(zdt));  
    // 美国Locale:星期, 月/日/年 时分  
    System.out.println(DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US).format(zdt));  
  • 输出结果

    plaintext 复制代码
    2019-09-15T23:16 GMT+08:00  
    2019 9月 15 周日 23:16  
    Sun, September/15/2019 23:16  
  • 固定字符输出

    格式字符串中用单引号包裹固定内容,如"yyyy-'年'-MM-'月'"输出 "2023 - 年 - 06 - 月"。

五、Instant 专题:高精度时间戳

1. 基本概念

  • 定义:表示计算机存储的高精度时间戳,本质是不断递增的整数。

  • 作用 :替代传统的System.currentTimeMillis(),提供更高精度(纳秒级)。

  • 获取方式 :通过Instant.now()获取当前时间戳,示例:

    java 复制代码
    Instant now = Instant.now();  
    System.out.println(now.getEpochSecond()); // 秒级时间戳  
    System.out.println(now.toEpochMilli()); // 毫秒级时间戳  

2. 内部结构

  • 核心字段:

    • long seconds:以秒为单位的时间戳。
    • int nanos:纳秒级精度(补充秒的小数部分)。
  • 对比:比System.currentTimeMillis()long类型(毫秒级)精度更高。

3. 类型转换

  • 与 ZonedDateTime / 时区转换

    通过atZone(ZoneId)为时间戳附加时区,生成带时区的日期时间:

    java 复制代码
    Instant ins = Instant.ofEpochSecond(1568568760);  
    ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());  
    // 输出:2019-09-16T01:32:40+08:00[Asia/Shanghai]  
  • 转换关系图

    arduino 复制代码
    LocalDateTime ─┬─> ZonedDateTime  
    ZoneId        ─┘           ▲  
                      ┌─────────┴─────────┐  
                      │                   │  
                      ▼                   ▼  
                 Instant ◀───▶ long(注意毫秒/秒单位)  
    • 转换时需注意long类型的单位(毫秒或秒)。
相关推荐
爱勇宝33 分钟前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
AskHarries1 小时前
工具失败时怎么办:重试、回滚、人工确认和风险提示
后端·程序员
苏三说技术2 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎3 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode3 小时前
Redis 在生产项目的使用
前端·后端
用户559822481223 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode3 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战3 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha4 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn4 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端