Java 日期与时间类

发布于 2026-07-30 20:20 更新于 2026-07-30 20:20 1275 字 7 min read ... 访问量

文章总结了Java中日期时间相关的API演变,指出旧版Date、Calendar和SimpleDateFormat类虽仍可用于历史代码维护,但存在可变、线程不安全、易出错等问题;推荐在新项目中使用Java 8引入的java.time包,其API设计更清晰、类型不可变且线程安全。重点介绍了LocalDate、LocalDateTime等类的使用,强调应根据业务需求选择合适类型,避免使用Date或字符串存储时间,以提升代码的可读性、安全性和准确性。

Java 日期与时间类

Java 中常见的日期时间 API 可以分为两类:

  • 旧版 API:DateCalendarSimpleDateFormat
  • Java 8 新 API:LocalDateLocalDateTimeInstantDateTimeFormatter 等。

旧版 API 在维护历史代码时仍会遇到,但新项目通常优先使用 java.time 包。

一、时间戳与时区

Unix 时间戳通常表示从 1970-01-01T00:00:00Z 开始经过的时间。Date 使用毫秒精度保存时间点。

北京时间通常采用 UTC+8。时区会影响时间点如何显示为年月日和时分秒,但不会改变时间点本身。

二、Date

java.util.Date 表示时间轴上的一个时间点,内部核心值是从 Unix 纪元开始计算的毫秒数。它并不是简单地分别保存年、月、日、时、分、秒。

Date 中许多直接操作年月日的方法已经过时,日期计算应使用 Calendar 或更推荐的 java.time API。

构造器

Date now = new Date();

无参构造器创建表示当前时间的对象。

Date date = new Date(1_000_000L);

传入的 long 值表示从 Unix 纪元开始经过的毫秒数。

常用方法

Date date = new Date();

long timestamp = date.getTime();
date.setTime(timestamp + 1_000L);
  • getTime():获取毫秒时间戳。
  • setTime(long time):修改该对象表示的时间点。

Date 是可变对象,调用 setTime() 会改变原对象。

三、Calendar

Calendar 是旧版日历计算类,可以根据时区和区域设置处理年月日等字段。

它的构造器通常不直接调用,而是通过 getInstance() 获取实例:

Calendar calendar = Calendar.getInstance();

获取和修改日期字段

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 3);

int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1;
int day = calendar.get(Calendar.DAY_OF_MONTH);

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

注意:

  • Calendar.MONTH0 开始,因此显示月份时通常需要加 1
  • add() 的参数可以为正数或负数,用于增加或减少时间。
  • Calendar 也是可变对象。

Date 相互转换

Calendar calendar = Calendar.getInstance();
Date dateFromCalendar = calendar.getTime();

Date date = new Date(100_000_000L);
Calendar anotherCalendar = Calendar.getInstance();
anotherCalendar.setTime(date);
  • calendar.getTime():将 Calendar 当前表示的时间转换为 Date
  • calendar.setTime(date):让 Calendar 使用指定 Date 的时间点。

四、SimpleDateFormat

SimpleDateFormat 用于在 Date 与字符串之间进行格式化和解析。

常见格式符号如下:

符号含义示例
yyyy四位年份2026
MM两位月份01
dd两位日期25
HH24 小时制小时16
mm分钟30
ss45

格式符号区分大小写。例如,MM 表示月份,mm 表示分钟。

Date 格式化为字符串

import java.text.SimpleDateFormat;
import java.util.Date;

public class FormatDateDemo {

    public static void main(String[] args) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date now = new Date();

        String text = formatter.format(now);
        System.out.println(text);
    }
}

将字符串解析为 Date

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ParseDateDemo {

    public static void main(String[] args) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        formatter.setLenient(false);

        String text = "2026-01-25";

        try {
            Date date = formatter.parse(text);
            System.out.println(date);
        } catch (ParseException e) {
            System.out.println("日期格式不正确");
        }
    }
}

setLenient(false) 用于关闭宽松解析。例如,在严格模式下,2026-02-30 会解析失败,而不是自动调整到下一个月。

SimpleDateFormat 不是线程安全的,不应把同一个实例作为共享对象供多个线程同时使用。

五、旧版 API 应用

指定日期增加十五天

20260122 增加十五天,并按中文日期格式输出:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class AddDaysLegacyDemo {

    public static void main(String[] args) {
        String text = "20260122";
        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
        formatter.setLenient(false);

        try {
            Date date = formatter.parse(text);

            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            calendar.add(Calendar.DATE, 15);

            formatter.applyPattern("yyyy年MM月dd日");
            System.out.println(formatter.format(calendar.getTime()));
        } catch (ParseException e) {
            System.out.println("日期格式不正确");
        }
    }
}

计算两个时间点的毫秒差

long differenceMillis = endDate.getTime() - startDate.getTime();

直接除以一天的毫秒数只能得到固定时长意义上的天数。如果时间范围跨越夏令时变化,结果可能与日历日期差不同。因此,计算日期之间相差多少天时,更适合使用 LocalDateChronoUnit.DAYS

六、Java 8 日期时间 API

java.time API 的类型设计更清晰,并且大多数核心类不可变、线程安全。

LocalDate

LocalDate 表示不带时区的日期,例如生日或课程日期。

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

public class LocalDateDemo {

    public static void main(String[] args) {
        DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");

        LocalDate date = LocalDate.parse("20260122", inputFormatter);
        LocalDate result = date.plusDays(15);

        System.out.println(result.format(outputFormatter));
    }
}

计算日期差

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

public class DateDifferenceDemo {

    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");

        LocalDate startDate = LocalDate.parse("20260122", formatter);
        LocalDate endDate = LocalDate.parse("20260302", formatter);

        long days = ChronoUnit.DAYS.between(startDate, endDate);
        System.out.println(days);
    }
}

常用类型

类型适用场景
LocalDate只需要年月日,不需要时区
LocalTime只需要时分秒
LocalDateTime需要日期和时间,但不包含时区
Instant时间轴上的时间点,适合记录时间戳
ZonedDateTime需要明确时区规则的日期时间
Duration表示基于秒和纳秒的时长
Period表示基于年、月、日的日期差
DateTimeFormatter日期时间的格式化和解析

新代码应根据业务含义选择类型,而不是所有时间都使用 Date 或字符串保存。

喜欢的话,留下你的评论吧~

... 访问量
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka