跳转至

日期处理

日期处理是Java中处理日期、时间、时间戳及时区转换的重要功能。

时间戳(Timestamp)是一个表示特定时间点的数值,通常是从某个固定时间点(如1970年1月1日UTC)开始计算的毫秒数。

不得不说的时区

时区的概念

时区(Time Zone)是地球上按照经度划分的区域,每个区域采用统一的标准时间。全球共划分为24个时区,通常以格林威治标准时间(GMT/UTC)为基准。

计算机系统的时区常识

  • 操作系统通常有默认时区(如中国大陆为 Asia/Shanghai,欧美常用 UTC 或本地时区)。
  • 程序运行时若未指定时区,往往采用系统默认时区,容易导致跨时区部署或数据交互时出现时间偏差。
  • Java 推荐使用 ZoneId 明确指定时区,避免隐式依赖。
  • 常见时区标识如:UTCAsia/ShanghaiAmerica/New_YorkAsia/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));
    }
}

时间戳使用注意事项

  1. 时间戳精度

    • Java中的时间戳精度为毫秒级
    • 如果需要更高精度,可以使用Instant类的getNano()方法获取纳秒部分
  2. 时区转换

    • 时间戳本身是UTC时间,不包含时区信息
    • 转换为本地时间时,需要明确指定时区
    • 建议使用ZoneId.systemDefault()获取系统默认时区
  3. 数据库存储

    • 使用java.sql.Timestamp存储数据库时间戳
    • 注意数据库时区设置与应用程序时区的一致性
  4. 跨系统传输

    • 时间戳是跨系统传输时间的最佳选择
    • 建议使用毫秒级时间戳,避免精度损失
    • 接收方需要知道时间戳的基准时间(通常是1970-01-01 UTC)

提醒

假如你做了一个国际支付业务,涉及实时汇率,请记住:

  1. 所有汇率计算必须基于 UTC 时间
  2. 不同时区的用户看到同一笔交易的时间可能不同
  3. 跨日交易时,汇率可能发生变化,需要特别注意 UTC 时间点
  4. 时间戳是记录交易时间的最佳选择,因为它不依赖于时区