0%

时间操作

Java 时间操作全解析:从传统类到 Java 8 新 API

在 Java 中,时间操作是日常开发的常见需求,包括获取当前时间、格式化时间、计算时间差等。随着 Java 版本的演进,时间 API 也从早期的 DateCalendar 发展到 Java 8 引入的 java.time 系列(如 LocalDateTimeZonedDateTime),后者解决了传统 API 的线程不安全、设计混乱等问题。本文将全面介绍 Java 中操作时间的主要方式及最佳实践。

传统时间类(Java 8 之前)

1. java.util.Date

Date 是最早期的时间类,存储自 1970 年 1 月 1 日 00:00:00(UTC)以来的毫秒数,但大部分方法已被废弃(如 getYear()getMonth()),仅保留少数核心方法(如 getTime())。

示例:创建 Date 对象
1
2
3
4
5
6
7
8
9
import java.util.Date;

public class DateDemo {
public static void main(String[] args) {
Date now = new Date(); // 获取当前时间
System.out.println(now); // 输出:Thu Aug 14 15:30:45 CST 2025
System.out.println(now.getTime()); // 输出:1755166245000(毫秒时间戳)
}
}
  • 缺点:
    • 线程不安全(无同步机制)。
    • 设计混乱(年从 1900 开始,月从 0 开始)。
    • 不支持时区和历法扩展。

2. java.util.Calendar

Calendar 是为了弥补 Date 的缺陷而设计的抽象类,提供了更丰富的时间操作方法(如获取年月日、加减时间),但仍存在线程安全问题。

示例:使用 Calendar 获取年月日时分秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Calendar;

public class CalendarDemo {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance(); // 获取默认时区的Calendar实例

// 获取年、月、日(注意:月份从0开始,需+1)
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 0~11 → 1~12
int day = cal.get(Calendar.DATE);

// 获取时、分、秒(24小时制用HOUR_OF_DAY,12小时制用HOUR)
int hour = cal.get(Calendar.HOUR_OF_DAY); // 24小时制
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);

System.out.printf("%d-%02d-%02d %02d:%02d:%02d%n",
year, month, day, hour, minute, second);
// 输出:2025-08-14 15:35:22
}
}
  • 常用字段:
    • Calendar.YEAR:年
    • Calendar.MONTH:月(0~11)
    • Calendar.DATE/DAY_OF_MONTH:日
    • Calendar.HOUR_OF_DAY:24 小时制小时
    • Calendar.HOUR:12 小时制小时
    • Calendar.MINUTE:分钟
    • Calendar.SECOND:秒
    • Calendar.MILLISECOND:毫秒

3. java.text.SimpleDateFormat

SimpleDateFormat 用于时间的格式化(Date → 字符串)和解析(字符串 → Date),但线程不安全(多线程并发使用可能导致异常)。

示例:时间格式化与解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatDemo {
public static void main(String[] args) throws ParseException {
// 格式化:Date → String
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date now = new Date();
String formatted = sdf.format(now);
System.out.println("格式化后:" + formatted); // 输出:2025-08-14 15:40:15

// 解析:String → Date
String timeStr = "2025-12-31 23:59:59";
Date parsedDate = sdf.parse(timeStr);
System.out.println("解析后:" + parsedDate); // 输出:Wed Dec 31 23:59:59 CST 2025
}
}
格式化字符含义
字符 描述 示例
y 年(4 位) yyyy → 2025
M 月(数字) MM → 08
d dd → 14
H 24 小时制小时 HH → 15
h 12 小时制小时 hh → 03
m 分钟 mm → 45
s ss → 30
S 毫秒 SSS → 123
E 星期(中文) E → 星期四
a 上 / 下午(A.M./P.M.) a → 下午
z 时区 z → CST
D 一年中的第几天 D → 226
w 一年中的第几周 w → 33

Java 8 新时间 API(java.time

Java 8 引入的 java.time 包(基于 JSR-310)是时间操作的推荐方案,解决了传统类的线程不安全、设计混乱等问题,核心类包括:

  • LocalDate:仅含日期(年、月、日)。
  • LocalTime:仅含时间(时、分、秒、毫秒)。
  • LocalDateTime:含日期和时间(无时区)。
  • ZonedDateTime:含日期、时间和时区。
  • DateTimeFormatter:线程安全的时间格式化器。

1. LocalDateTime:处理本地日期时间(无时区)

示例:获取年月日时分秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.LocalDateTime;

public class LocalDateTimeDemo {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now(); // 获取当前日期时间

// 获取各字段
int year = now.getYear();
int month = now.getMonthValue(); // 1~12(无需+1)
int day = now.getDayOfMonth();
int hour = now.getHour(); // 24小时制
int minute = now.getMinute();
int second = now.getSecond();
int nano = now.getNano(); // 纳秒(1秒=1e9纳秒)

System.out.printf("%d-%02d-%02d %02d:%02d:%02d.%03d%n",
year, month, day, hour, minute, second, nano / 1_000_000);
// 输出:2025-08-14 15:50:30.123
}
}
时间增减与修改
1
2
3
4
5
6
7
8
9
10
11
LocalDateTime now = LocalDateTime.now();

// 增加1天,减少2小时
LocalDateTime modified = now.plusDays(1).minusHours(2);

// 修改年份为2026,月份为10
LocalDateTime updated = now.withYear(2026).withMonth(10);

System.out.println("原时间:" + now); // 2025-08-14T15:55:00
System.out.println("增减后:" + modified); // 2025-08-15T13:55:00
System.out.println("修改后:" + updated); // 2026-10-14T15:55:00

2. DateTimeFormatter:线程安全的格式化器

DateTimeFormatter 替代了 SimpleDateFormat,支持格式化和解析,且线程安全,可直接定义为静态变量复用。

示例:格式化与解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTimeFormatterDemo {
// 定义静态格式化器(线程安全,可复用)
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();

// 格式化:LocalDateTime → String
String formatted = FORMATTER.format(now);
System.out.println("格式化后:" + formatted); // 2025-08-14 16:00:45

// 解析:String → LocalDateTime
String timeStr = "2025-01-01 00:00:00";
LocalDateTime parsed = LocalDateTime.parse(timeStr, FORMATTER);
System.out.println("解析后:" + parsed); // 2025-01-01T00:00:00
}
}

3. ZonedDateTime:处理时区

ZonedDateTime 包含时区信息,可用于跨时区时间转换(如 UTC 与北京时间的转换)。

示例:时区转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.time.ZonedDateTime;
import java.time.ZoneId;

public class ZonedDateTimeDemo {
public static void main(String[] args) {
// 获取 UTC 时间
ZonedDateTime utcTime = ZonedDateTime.now(ZoneId.of("UTC"));
// 获取北京时区时间(Asia/Shanghai)
ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

System.out.println("UTC 时间:" + utcTime); // 2025-08-14T08:05:30Z[UTC]
System.out.println("北京时区:" + beijingTime); // 2025-08-14T16:05:30+08:00[Asia/Shanghai]

// 北京时区转纽约时区(America/New_York)
ZonedDateTime newYorkTime = beijingTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("纽约时区:" + newYorkTime); // 2025-08-14T04:05:30-04:00[America/New_York]
}
}

4. 时间间隔计算(DurationPeriod

  • Duration:计算两个时间(LocalTime/LocalDateTime)之间的间隔(秒、纳秒)。
  • Period:计算两个日期(LocalDate)之间的间隔(年、月、日)。
示例:计算时间差
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.LocalDate;

public class DurationPeriodDemo {
public static void main(String[] args) {
// 计算时间差(Duration)
LocalDateTime start = LocalDateTime.of(2025, 8, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 8, 1, 12, 30, 0);
Duration duration = Duration.between(start, end);
System.out.println("相差小时:" + duration.toHours()); // 2
System.out.println("相差分钟:" + duration.toMinutes()); // 150

// 计算日期差(Period)
LocalDate date1 = LocalDate.of(2025, 1, 1);
LocalDate date2 = LocalDate.of(2025, 12, 31);
Period period = Period.between(date1, date2);
System.out.println("相差月数:" + period.getMonths()); // 11
System.out.println("相差天数:" + period.getDays()); // 30(实际总天数需计算)
}
}

传统类与 Java 8 新 API 的对比

特性 传统类(Date/Calendar Java 8 新 API(java.time
线程安全 不安全(SimpleDateFormat 等) 安全(所有类均为不可变对象)
设计合理性 混乱(月从 0 开始,年偏移 1900) 直观(月从 1 开始,无偏移)
时区支持 弱(需手动处理) 强(ZonedDateTime 原生支持)
格式化 SimpleDateFormat(线程不安全) DateTimeFormatter(线程安全)
时间计算 繁琐(add() 方法) 简洁(plusXxx()/minusXxx()
推荐使用 不推荐(仅兼容旧代码) 推荐(新代码首选)

最佳实践

  1. 优先使用 Java 8 新 APILocalDateTimeZonedDateTimeDateTimeFormatter 等,避免传统类的线程安全和设计问题。
  2. 线程安全SimpleDateFormat 线程不安全,多线程环境下应使用 DateTimeFormatterThreadLocal<SimpleDateFormat>(不推荐)。
  3. 时区处理:涉及跨时区时间时,使用 ZonedDateTime 而非 LocalDateTime,避免时区混淆。
  4. 格式化模式:日期用 yyyy(4 位年)而非 yy(2 位年,易歧义);24 小时制用 HH,12 小时制用 hh 并配合 a(上 / 下午)。

欢迎关注我的其它发布渠道