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类型的单位(毫秒或秒)。
相关推荐
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse创建一个WEB项目?(三)
java·ide·java-ee·myeclipse
ai小鬼头2 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
idolyXyz2 小时前
[java: Cleaner]-一文述之
java
一碗谦谦粉2 小时前
Maven 依赖调解的两大原则
java·maven
萧曵 丶2 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
netyeaxi2 小时前
Java:使用spring-boot + mybatis如何打印SQL日志?
java·spring·mybatis
收破烂的小熊猫~3 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
猴哥源码3 小时前
基于Java+SpringBoot的动物领养平台
java·spring boot
老任与码3 小时前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
小兵张健3 小时前
武汉拿下 23k offer 经历
java·面试·ai编程