日期处理
日期处理是Java中处理日期、时间、时间戳及时区转换的重要功能。
时间戳(Timestamp)是一个表示特定时间点的数值,通常是从某个固定时间点(如1970年1月1日UTC)开始计算的毫秒数。
不得不说的时区
时区的概念
时区(Time Zone)是地球上按照经度划分的区域,每个区域采用统一的标准时间。全球共划分为24个时区,通常以格林威治标准时间(GMT/UTC)为基准。
计算机系统的时区常识
- 操作系统通常有默认时区(如中国大陆为 Asia/Shanghai,欧美常用 UTC 或本地时区)。
- 程序运行时若未指定时区,往往采用系统默认时区,容易导致跨时区部署或数据交互时出现时间偏差。
- Java 推荐使用
ZoneId
明确指定时区,避免隐式依赖。 - 常见时区标识如:
UTC
、Asia/Shanghai
、America/New_York
、Asia/Tokyo
。
标准时间
- UTC(Coordinated Universal Time,协调世界时):全球通用的时间标准,替代了早期的 GMT。
- GMT(Greenwich Mean Time,格林威治标准时间):历史上广泛使用,现多用 UTC 替代。
- 本地时间(Local Time):指当前时区下的时间。
- 夏令时(Daylight Saving Time, DST):部分国家/地区会在夏季调整时间,需注意时间换算。
例子
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.Month;
import java.time.Duration;
import java.time.Period;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.sql.Timestamp;
public class DateTimeDemo {
public static void main(String[] args) {
// 获取当前日期时间
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();
LocalDateTime current = LocalDateTime.now();
System.out.println("今天: " + today);
System.out.println("现在时间: " + now);
System.out.println("当前日期时间: " + current);
// 时间戳相关操作
// 1. 获取当前时间戳
long currentTimestamp = System.currentTimeMillis();
System.out.println("当前时间戳(毫秒): " + currentTimestamp);
// 2. 使用Instant处理时间戳
Instant instant = Instant.now();
System.out.println("当前Instant: " + instant);
System.out.println("Instant转时间戳: " + instant.toEpochMilli());
// 3. 时间戳转日期时间
LocalDateTime fromTimestamp = LocalDateTime.ofInstant(
Instant.ofEpochMilli(currentTimestamp),
ZoneId.systemDefault()
);
System.out.println("时间戳转日期时间: " + fromTimestamp);
// 4. 使用SQL Timestamp
Timestamp sqlTimestamp = new Timestamp(currentTimestamp);
System.out.println("SQL Timestamp: " + sqlTimestamp);
// 5. 时间戳与时区转换
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(
Instant.ofEpochMilli(currentTimestamp),
ZoneId.of("Asia/Shanghai")
);
System.out.println("上海时区时间: " + zonedDateTime);
// 创建特定日期时间
LocalDate birthday = LocalDate.of(1990, Month.MAY, 15);
LocalTime meetingTime = LocalTime.of(14, 30);
LocalDateTime event = LocalDateTime.of(2023, 12, 31, 23, 59, 59);
System.out.println("生日: " + birthday);
System.out.println("会议时间: " + meetingTime);
System.out.println("事件时间: " + event);
// 日期时间计算
LocalDate nextWeek = today.plusWeeks(1);
LocalTime afterTwoHours = now.plusHours(2);
LocalDateTime nextMonth = current.plusMonths(1);
System.out.println("下周今天: " + nextWeek);
System.out.println("两小时后: " + afterTwoHours);
System.out.println("下个月: " + nextMonth);
// 时区处理
ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYorkTime = shanghaiTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("上海时间: " + shanghaiTime);
System.out.println("纽约时间: " + newYorkTime);
// 格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = current.format(formatter);
System.out.println("格式化后: " + formatted);
// 解析
LocalDateTime parsed = LocalDateTime.parse("2023-01-01 12:00:00", formatter);
System.out.println("解析后: " + parsed);
// 与传统API互转
Date oldDate = new Date();
Instant instant2 = oldDate.toInstant();
LocalDateTime newDateTime = LocalDateTime.ofInstant(instant2, ZoneId.systemDefault());
System.out.println("转换后: " + newDateTime);
// 时间差计算
LocalDateTime start = LocalDateTime.of(2023, 1, 1, 0, 0);
LocalDateTime end = LocalDateTime.of(2023, 1, 2, 12, 30);
Duration duration = Duration.between(start, end);
System.out.println("相差小时: " + duration.toHours());
Period period = Period.between(LocalDate.of(2022, 1, 1), LocalDate.of(2023, 1, 1));
System.out.println("相差年月: " + period.getYears() + "年" + period.getMonths() + "月");
// 日期时间比较
System.out.println("是否在之前: " + start.isBefore(end));
System.out.println("是否在之后: " + end.isAfter(start));
System.out.println("是否相等: " + start.isEqual(end));
}
}
时间戳使用注意事项
-
时间戳精度
- Java中的时间戳精度为毫秒级
- 如果需要更高精度,可以使用
Instant
类的getNano()
方法获取纳秒部分
-
时区转换
- 时间戳本身是UTC时间,不包含时区信息
- 转换为本地时间时,需要明确指定时区
- 建议使用
ZoneId.systemDefault()
获取系统默认时区
-
数据库存储
- 使用
java.sql.Timestamp
存储数据库时间戳 - 注意数据库时区设置与应用程序时区的一致性
- 使用
-
跨系统传输
- 时间戳是跨系统传输时间的最佳选择
- 建议使用毫秒级时间戳,避免精度损失
- 接收方需要知道时间戳的基准时间(通常是1970-01-01 UTC)
提醒
假如你做了一个国际支付业务,涉及实时汇率,请记住:
- 所有汇率计算必须基于 UTC 时间
- 不同时区的用户看到同一笔交易的时间可能不同
- 跨日交易时,汇率可能发生变化,需要特别注意 UTC 时间点
- 时间戳是记录交易时间的最佳选择,因为它不依赖于时区