Java日期时间操作指南:Date与Calendar使用详解
Date本质是无时区的毫秒数,toString()因隐式使用JVM默认时区而显示有时区;Calendar.set()延迟计算,需getTime()强制触发;SimpleDateFormat非线程安全,应避免static共享。
Date 本质是无时区的毫秒数,toString() 因隐式使用 JVM 默认时区而显示有时区;Calendar.set() 延迟计算,需 getTime() 强制触发;SimpleDateFormat 非线程安全,应避免 static 共享。

Date 和 Calendar 是 Java 早期(JDK 1.0 / 1.1)引入的时间类,不推荐在新项目中使用——它们线程不安全、设计混乱、API 反直觉,且已被 java.time 包(Java 8+)全面取代。
但如果你必须维护旧代码、对接遗留系统,或面试被问到,下面这些实操要点能帮你避开高频翻车点。
为什么 new Date() 返回的是“当前时间”,但 toString() 却带时区?
Date 本质只是一个毫秒数(自 1970-01-01T00:00:00Z 起的 long),它**不包含时区信息**。但 Date.toString() 会隐式调用 JVM 默认时区(如 Asia/Shanghai)格式化输出,造成“Date 有时区”的错觉。
实操建议:
- 永远别依赖
date.toString()做逻辑判断或日志解析 - 需要格式化显示时,用
SimpleDateFormat显式指定时区(注意:它也不是线程安全的) - 跨系统传时间,优先用毫秒值
date.getTime()或 ISO 8601 字符串(如"2024-05-20T14:30:00Z")
Calendar.getInstance() 返回的对象,set() 后为什么没立即生效?
Calendar 是可变对象,set() 只是标记字段已修改,真正计算时间戳要等第一次调用 get()、getTime() 或 add() —— 这叫“懒计算”。中间若再 set() 其他字段,可能触发冲突校验(比如 2 月设了 30 日)。
常见错误现象:
cal.set(Calendar.MONTH, 12)→ 实际变成下一年 1 月(MONTH从 0 开始,12 是非法值,自动进位)cal.set(2024, 1, 30)后立刻cal.get(Calendar.DATE)→ 可能返回 2 或 3(因 2 月无 30 日,自动归整)- 多线程共用同一个
Calendar实例 → 结果不可预测
实操建议:
- 每次使用前调用
Calendar.getInstance()新建实例,别复用 - 设完关键字段后,立刻调用
cal.getTime()强制计算,避免后续 get() 出意外 - 避免混合使用
set(int field, int value)和set(year, month, date)—— 后者会重置小时/分/秒为 0
如何安全地做“日期加减”而不踩 Calendar 的陷阱?
用 add() 比 roll() 更可靠:add() 会处理进位(如 1 月 31 日 + 1 月 → 2 月 28/29 日),roll() 只在本字段内循环(1 月 31 日 roll +1 月 → 1 月 31 日,没变)。
但 add() 仍有坑:
cal.add(Calendar.MONTH, 1)不等于“加 30 天”,而是按日历月逻辑(可能加 28~31 天)- 跨夏令时切换时,
add(Calendar.HOUR, 1)可能跳过或重复 1 小时(取决于时区规则) - 对
Calendar做多次add()后,再set()某个字段,可能覆盖之前计算结果
实操建议:
- 如果业务明确要“加 N 天”,直接操作毫秒值:
new Date(date.getTime() + N * 24L * 3600_000) - 如果必须用日历单位(如“加 1 个月”),用
add(),但之后立刻getTime()获取结果并丢弃原Calendar - 涉及跨月/跨年计算,务必用单元测试覆盖边界:1 月 31 日 +1 月、2 月 29 日 +1 年等
和 SimpleDateFormat 配合使用时,最常被忽略的线程安全问题
SimpleDateFormat 是典型的“伪线程安全”陷阱:它内部缓存了 Calendar 实例,多个线程并发调用 format() 或 parse() 会导致状态错乱,典型表现是解析出错日期(如 "2024-01-01" 解成 2023 年)、格式化丢失时区、甚至抛 NumberFormatException。
实操建议:
- 绝对不要把
SimpleDateFormat设为 static 全局变量 - 方案一:每次用都新建(适合低频场景):
new SimpleDateFormat("yyyy-MM-dd") - 方案二:用
ThreadLocal管理(推荐):private static final ThreadLocal
SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); - 方案三:升级到
DateTimeFormatter(Java 8+),它是不可变且线程安全的
旧 API 的复杂性不在语法,而在隐式状态、模糊契约和时区纠缠。哪怕只是读一段老代码,也要先确认:它用的是毫秒值比较,还是字符串比较?Calendar 实例是否被共享?SimpleDateFormat 在哪初始化的?这些细节比“怎么写”更容易决定程序是否稳定。
Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。
Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。
Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。
PixPix 是面向品牌、卖家和内容团队的在线 AI 图片视频创作平台,提供图视频 Agent、无限画布、图片生成、视频生成、自然语言编辑及商品套图、产品精修和带货视频等电商工具。
Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等
Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。













