
用 System.currentTimeMillis() 拿时间戳最直接
它返回的是从 1970-01-01 00:00:00 UTC 到现在的毫秒数,类型是 long,不依赖时区,也不需要对象创建,开销几乎为零。
常见错误是把它当成秒级时间戳用——System.currentTimeMillis() 是毫秒级,要转成秒得除以 1000,但别直接用 int 接,会溢出:(int) System.currentTimeMillis() / 1000 在 2038 年后就炸了。
- 存数据库或传给前端做时间比较,优先用
System.currentTimeMillis() - 如果接口明确要秒级,写成
System.currentTimeMillis() / 1000L,确保结果仍是long - 别在循环里反复调用它来“测微秒级耗时”,精度不够,而且 JVM 可能缓存系统时钟,两次调用可能返回相同值
LocalDateTime.now() 和 ZonedDateTime.now() 别混用
LocalDateTime 没有时区信息,只是“本地日历时间”;ZonedDateTime 才真正代表带时区的完整时刻。拿错会导致跨时区场景下时间错乱,比如服务器在东八区、用户在太平洋时间,用 LocalDateTime.now() 存的时间,查出来永远比用户本地时间快 16 小时。
- 记录日志、展示给用户看的“当前时间”,用
ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))显式指定时区 - 做纯日期计算(比如“本月第一天”),用
LocalDate.now()更安全,它不涉及时钟偏移 - 把
LocalDateTime直接塞进数据库 timestamp 字段,PostgreSQL 或 MySQL 可能自动按服务端时区解释,结果不可控
SimpleDateFormat 是线程不安全的,别当静态变量用
它内部维护一个 Calendar 对象,多线程并发调用 format() 或 parse() 会互相覆盖状态,导致格式化错乱(比如 2024-03-15 变成 2024-13-15)或解析抛 java.lang.NumberFormatException。
立即学习“Java免费学习笔记(深入)”;
- 每次用都 new 一个:
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - 或者改用线程安全的
DateTimeFormatter(Java 8+):DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),它是不可变对象,可复用 - 注意
DateTimeFormatter默认不处理时区,要格式化带时区的时间,得用ZonedDateTime.format(...),不能传LocalDateTime
MySQL 的 TIMESTAMP 和 DATETIME 对 Java 时间类型映射不同
JDBC 驱动默认把数据库 TIMESTAMP 映射为 java.time.LocalDateTime,但其实 TIMESTAMP 在 MySQL 内部是按 UTC 存储、查询时转成连接时区的;而 DATETIME 是原样存储、不自动转换。这会导致用 LocalDateTime 读 TIMESTAMP 字段时,看似正确,实则丢失了时区上下文。
- 如果数据库字段是
TIMESTAMP,Java 端该用OffsetDateTime或ZonedDateTime接收,避免隐式时区转换 - MyBatis 或 JPA 中,用
@Column(columnDefinition = "TIMESTAMP")时,务必确认实体类字段类型是否匹配语义 - Spring Boot 2.3+ 默认禁用
java.util.Date自动转换,若还用老项目里的Date,记得配spring.jackson.date-format和spring.jackson.time-zone
时间戳本身没毛病,麻烦全出在“你以为它代表什么”和“它实际被怎么解释”之间的偏差。尤其跨系统传时间、存数据库、做定时任务的时候,差一秒都可能让排查变成翻三天日志。










