749 字
2 分钟
Java日期和时间处理详解
Java的日期时间API进化史简直就是一部踩坑史。从Date到Calendar,再到java.time包,每次改版都在修复上一版的槽点。我最早用Calendar的时候,被那个月份从0开始坑过不止一次。这篇文章把各个版本的用法和坑都整理出来了。
Java日期和时间处理详解
日期时间API进化史
| 版本 | API | 槽点 |
|---|---|---|
| Java 1.0 | Date | 大部分方法已废弃 |
| Java 1.2 | Calendar | 月份从0开始,线程不安全 |
| Java 8+ | java.time | 这才是正确的打开方式 |
一句话:新项目直接用java.time包,别再用Date和Calendar了。
传统API(了解即可,不建议新项目使用)
Date类
Date now = new Date();System.out.println("当前时间: " + now);
long timestamp = now.getTime(); // 时间戳,这个最常用Date的大部分方法都标记为@Deprecated了,别琢磨怎么用了,就记住getTime()和new Date(long)这两个就够了。
Calendar类 —— 坑最多的API
Calendar的月份从0开始,这个设计真的让人无语。有次我写calendar.set(2023, 1, 1)以为设置的是1月1号,结果跑出来是2月1号,debug了半天才发现:
Calendar calendar = Calendar.getInstance();calendar.set(2023, 1, 1); // 这是2月1日!不是1月1日!calendar.set(2023, Calendar.JANUARY, 1); // 正确做法:用常量SimpleDateFormat —— 线程不安全!
SimpleDateFormat不是线程安全的,多个线程共用同一个实例会导致诡异的结果:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 错误用法:多个线程共用同一个sdf实例// 会出现各种奇怪的结果,比如月份变成13、时间错乱
// 正确用法:每次new一个,或者用ThreadLocal包装建议:新代码直接用DateTimeFormatter,它是线程安全的。
Java 8+ 新API(java.time包)
几个核心类
| 类 | 用途 | 示例 |
|---|---|---|
LocalDate | 日期(年月日) | 2023-01-01 |
LocalTime | 时间(时分秒) | 12:00<00>00> |
LocalDateTime | 日期+时间 | 2023-01-01T12:00<00>00> |
ZonedDateTime | 带时区的日期时间 | 2023-01-01T12:00<00>00>+08<00>00> |
Instant | 时间戳 | 1970-01-01T00:00<00z>00z> |
Duration | 时间间隔(时分秒) | 2小时 |
Period | 日期间隔(年月日) | 1年6个月 |
LocalDate —— 最常用的日期类
// 创建LocalDate now = LocalDate.now();LocalDate specific = LocalDate.of(2023, 1, 1); // 月份是1,不是0!舒服了LocalDate parsed = LocalDate.parse("2023-01-01");
// 获取字段int year = now.getYear();int month = now.getMonthValue(); // 1-12,不用+1了int day = now.getDayOfMonth();
// 日期计算LocalDate nextWeek = now.plusDays(7);LocalDate lastMonth = now.minusMonths(1);
// 日期比较boolean isAfter = now.isAfter(specific);boolean isBefore = now.isBefore(specific);
// 计算天数差long daysBetween = ChronoUnit.DAYS.between(specific, now);
// 闰年判断boolean isLeapYear = now.isLeapYear();LocalTime —— 时间类
LocalTime now = LocalTime.now();LocalTime specific = LocalTime.of(12, 0, 0);LocalTime parsed = LocalTime.parse("12:00:00");
int hour = now.getHour();int minute = now.getMinute();int second = now.getSecond();
LocalTime plusHours = now.plusHours(1);LocalTime minusMinutes = now.minusMinutes(30);
long minutesBetween = ChronoUnit.MINUTES.between(specific, now);LocalDateTime —— 日期+时间
LocalDateTime now = LocalDateTime.now();LocalDateTime specific = LocalDateTime.of(2023, 1, 1, 12, 0, 0);
// 拆分LocalDate date = now.toLocalDate();LocalTime time = now.toLocalTime();ZonedDateTime —— 带时区
// 当前时间(系统时区)ZonedDateTime now = ZonedDateTime.now();
// 指定时区ZonedDateTime newYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
// 时区转换ZonedDateTime shanghai = now.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));Instant —— 时间戳
Instant now = Instant.now();long epochMilli = now.toEpochMilli(); // 毫秒时间戳long epochSecond = now.getEpochSecond(); // 秒时间戳格式化与解析
DateTimeFormatter是线程安全的,可以放心共用:
LocalDateTime now = LocalDateTime.now();
// 预定义格式DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// 自定义格式DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String formatted = now.format(formatter); // 2023-01-01 12:00:00
// 解析LocalDateTime parsed = LocalDateTime.parse("2023-01-01 12:00:00", formatter);Duration 和 Period
// Duration:时间间隔(时分秒)LocalTime start = LocalTime.of(10, 0);LocalTime end = LocalTime.of(12, 30);Duration duration = Duration.between(start, end);long hours = duration.toHours(); // 2long minutes = duration.toMinutes(); // 150
// Period:日期间隔(年月日)LocalDate startDate = LocalDate.of(2020, 1, 1);LocalDate endDate = LocalDate.of(2023, 6, 15);Period period = Period.between(startDate, endDate);int years = period.getYears(); // 3int months = period.getMonths(); // 5int days = period.getDays(); // 14实战贴士
- 新项目一律用java.time包:Date、Calendar、SimpleDateFormat都是历史遗留,别在新代码里用了
- SimpleDateFormat不是线程安全的:多线程共用会出诡异bug,用
DateTimeFormatter替代 - 存储时间用UTC:数据库里存UTC时间,展示时再转用户时区
- DateTimeFormatter可以缓存:它是线程安全的,定义为
static final复用 - 注意不可变性:java.time的类都是不可变的,
now.plusDays(1)返回新对象,now本身不变
常见坑
- Calendar月份从0开始:1月是0,12月是11。这个坑我踩过无数次,用
java.time包就没事了。 - SimpleDateFormat线程不安全:高并发下会出现各种奇怪的结果,比如时间错乱、月份不对。用
DateTimeFormatter替代。 - 时区问题:不指定时区就默认用系统时区,部署到不同地区的服务器上,结果可能不一样。建议统一用UTC。
- 格式化解析异常:输入的字符串格式不对会抛异常,记得try-catch。
分享
如果这篇文章对你有帮助,欢迎分享给更多人!






