32_Java日期时间处理

32_Java日期时间处理 Java日期时间处理文章目录Java日期时间处理前言一、Date类传统API二、Calendar类三、LocalDateTimeJava 8新API四、DateTimeFormatter格式化五、Instant与时间戳总结✅ 亮点总结适用场景扩展方向前言日期和时间处理是Java开发中最常见的需求之一无论是记录日志、计算订单超时、还是生成报表都离不开对时间的操作。Java的日期时间API经历了多次演进从早期的Date和Calendar到Java 8引入的全新java.time包。本文将全面梳理这些API的用法帮你应对各种时间处理场景。一段血泪史如果你用过早期的Java日期API一定被Date.getMonth()返回0表示1月坑过也一定在SimpleDateFormat的线程安全问题上栽过跟头。这些设计缺陷导致了社区大量抱怨最终促成Java 8新API的诞生。新API的设计汲取了Joda-Time库的优秀理念是不可变和线程安全的。对于新项目请直接使用java.time包对于遗留代码也应有计划地迁移。很多面试官会问SimpleDateFormat为什么线程不安全以及新旧API的区别这正是本文要讲透的内容。一、Date类传统APIjava.util.Date是Java最早的日期类虽然大部分方法已被弃用但在遗留代码中仍频繁出现importjava.util.Date;// 创建当前时间的Date对象DatenownewDate();System.out.println(now);// Fri May 29 10:30:00 CST 2026// 获取毫秒时间戳longtimestampnow.getTime();System.out.println(时间戳: timestamp);// 通过时间戳创建DateDatepastnewDate(0L);// Unix纪元: 1970-01-01 08:00:00 CSTSystem.out.println(纪元: past);// 比较两个日期Datedate1newDate(1000);Datedate2newDate(2000);System.out.println(date1.before(date2));// trueSystem.out.println(date2.after(date1));// trueDate的缺点月份从0开始0表示1月容易出错大部分方法已过时线程不安全。更重要的是Date实际上不表示日期它表示的是一个精确到毫秒的时间瞬间自1970-01-01 00:00:00 UTC以来的毫秒数名称本身就有误导性。如果你只想表示一个日期如生日用Date会让你带上不必要的时间部分。这也是为什么Java 8将LocalDate纯日期和LocalTime纯时间分离的原因。二、Calendar类Calendar是一个抽象类GregorianCalendar是其最常用的实现importjava.util.Calendar;importjava.util.Date;CalendarcalendarCalendar.getInstance();System.out.println(当前时间: calendar.getTime());// 获取各个字段intyearcalendar.get(Calendar.YEAR);intmonthcalendar.get(Calendar.MONTH)1;// 月份从0开始intdaycalendar.get(Calendar.DAY_OF_MONTH);inthourcalendar.get(Calendar.HOUR_OF_DAY);intminutecalendar.get(Calendar.MINUTE);System.out.println(year-month-day hour:minute);// 日期计算 - 3天后calendar.add(Calendar.DAY_OF_MONTH,3);System.out.println(三天后: calendar.getTime());// 设置为本月最后一天calendar.set(Calendar.DAY_OF_MONTH,calendar.getActualMaximum(Calendar.DAY_OF_MONTH));System.out.println(本月最后一天: calendar.getTime());// Calendar与Date互转Datedatecalendar.getTime();calendar.setTime(date);虽然Calendar比Date更灵活但它的API设计依然存在瑕疵月份从0开始、线程不安全、可变性导致bug风险。最典型的坑是你调用了calendar.add(Calendar.MONTH, 1)想算下个月的同一天但如果当前是1月31日下个月2月没有31日Calendar会自动进位到3月3日左右——这是一个很难排查的bug。Java 8的LocalDate.plusMonths(1)则是采用调整为当月最后一天的策略更加符合直觉。三、LocalDateTimeJava 8新APIJava 8的java.time包提供了全新的日期时间API设计清晰、不可变、线程安全importjava.time.*;importjava.time.temporal.ChronoUnit;// 获取当前日期、时间LocalDatedateLocalDate.now();// 2026-05-29LocalTimetimeLocalTime.now();// 10:30:45.123LocalDateTimedateTimeLocalDateTime.now();// 2026-05-29T10:30:45.123// 指定日期时间LocalDateTimespecificLocalDateTime.of(2026,5,1,14,30,0);System.out.println(指定时间: specific);// 日期计算返回新对象原对象不变LocalDateTimenextWeekspecific.plusWeeks(1);LocalDateTimetwoHoursLaterspecific.plus(2,ChronoUnit.HOURS);LocalDateTimelastMonthspecific.minusMonths(1);// 比较日期System.out.println(specific.isBefore(nextWeek));// trueSystem.out.println(specific.isAfter(nextWeek));// false// 获取年月日时分秒intyspecific.getYear();Monthmspecific.getMonth();intdspecific.getDayOfMonth();DayOfWeekdowspecific.getDayOfWeek();System.out.println(y年m.getValue()月d日 dow);// 计算两个日期之间的差值LocalDatestartLocalDate.of(2026,1,1);LocalDateendLocalDate.of(2026,5,29);longdaysChronoUnit.DAYS.between(start,end);System.out.println(相差天数: days);// 148四、DateTimeFormatter格式化DateTimeFormatter是线程安全的格式化/解析工具替代了线程不安全的SimpleDateFormat。为什么SimpleDateFormat线程不安全因为它的内部维护了一个Calendar对象作为共享状态。多个线程同时调用format()或parse()时一个线程可能修改了Calendar的值另一个线程读取到脏数据导致格式化结果错乱或抛出异常。DateTimeFormatter是不可变对象天然线程安全可以作为静态常量全局共享无需每次创建。importjava.time.LocalDateTime;importjava.time.format.DateTimeFormatter;LocalDateTimenowLocalDateTime.now();// 预定义格式DateTimeFormatterisoDateTimeFormatter.ISO_LOCAL_DATE_TIME;System.out.println(now.format(iso));// 2026-05-29T10:30:45.123// 自定义格式DateTimeFormatterformatterDateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss);Stringformattednow.format(formatter);System.out.println(格式化: formatted);// 2026-05-29 10:30:45// 解析字符串为日期对象StringdateStr2026-05-29 14:30:00;LocalDateTimeparsedLocalDateTime.parse(dateStr,formatter);System.out.println(解析结果: parsed);// 带中文的格式化DateTimeFormattercnFormatterDateTimeFormatter.ofPattern(yyyy年MM月dd日 HH时mm分ss秒);System.out.println(now.format(cnFormatter));// LocalDate和LocalTime的格式化DateTimeFormatterdateFmtDateTimeFormatter.ofPattern(yyyy/MM/dd);DateTimeFormattertimeFmtDateTimeFormatter.ofPattern(HH:mm:ss);System.out.println(日期: LocalDate.now().format(dateFmt));System.out.println(时间: LocalTime.now().format(timeFmt));五、Instant与时间戳Instant代表时间线上的一个瞬时点用于处理时间戳。与Date类似但设计更合理——它自1970-01-01T00:00:00ZUTC时区起计算以秒和纳秒两部分存储精度更高。时间戳避坑指南System.currentTimeMillis()返回的是毫秒级时间戳适合大多数场景而Instant.now().getEpochSecond()返回的是秒级——如果当做毫秒传给下游系统可能产生几万倍的误差。在前后端交互时一定要明确时间戳的精度秒/毫秒建议在接口文档里显式标注。importjava.time.Instant;importjava.time.ZoneId;importjava.time.ZonedDateTime;// 获取当前时间戳InstantinstantInstant.now();System.out.println(UTC时间: instant);// 时间戳互转longepochSecondinstant.getEpochSecond();System.out.println(秒时间戳: epochSecond);longepochMilliinstant.toEpochMilli();System.out.println(毫秒时间戳: epochMilli);// 带时区的日期时间ZonedDateTimezonedinstant.atZone(ZoneId.of(Asia/Shanghai));System.out.println(上海时区: zoned);// 获取所有可用时区ZoneId.getAvailableZoneIds().stream().filter(z-z.contains(Asia)).limit(5).forEach(System.out::println);总结Java日期时间API的演进路线清晰Date→Calendar→java.time。在新项目中强烈建议使用Java 8的java.time包它提供了不可变、线程安全、API直观的LocalDate、LocalTime、LocalDateTime等类。DateTimeFormatter取代了SimpleDateFormat避免了线程安全问题。对于老旧项目也应逐步将日期处理逻辑迁移到新API。掌握好这些工具能够让你在日期时间处理上事半功倍。快速选型指南需要纯日期如生日→LocalDate需要纯时间如闹钟→LocalTime需要日期时间如订单创建时间→LocalDateTime需要带时区的时间如国际化系统→ZonedDateTime需要时间戳存储/传输 →Instant转毫秒值。避免在数据库中用字符串存储日期——这是最糟糕的做法会让你丧失所有日期计算和比较能力。✅ 亮点总结LocalDate/LocalTime/LocalDateTime不可变且线程安全彻底告别SimpleDateFormat的并发隐患Period日期差和Duration时间差让时间间隔计算直观易读时间加减与调整方法plusDays、withYear、TemporalAdjusters一行代码完成复杂日期运算DateTimeFormatter内置常用格式 自定义Pattern支持解析和格式化双向操作InstantZoneId的时间戳与时区互转轻松处理全球化时间展示适用场景日志系统——统一用LocalDateTime.now()记录时间配合DateTimeFormatter规范输出格式定时任务——计算下次执行时间使用plusMinutes/plusHours实现 Cron 表达式解析报表统计——用Period.between()计算用户注册天数用TemporalAdjusters.lastDayOfMonth()获取月末日期扩展方向Spring Boot中的日期处理JsonFormat、DateTimeFormat注解与Jackson序列化配置JPA/Hibernate中的日期映射Java 8时间类型与数据库字段的映射关系ThreeTen-Extra库提供Interval、LocalDateRange等java.time官方扩展推荐阅读上一篇Java枚举类型详解