news 2026/5/2 14:21:04

Java 日期处理工具类

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 日期处理工具类

📅 从"能用"到"好用"

一、问题诊断

🔴 严重问题

问题影响位置
线程不安全SimpleDateFormat非线程安全,多线程环境下会抛异常或数据错乱所有方法
时区隐患未指定时区,服务器时区变更会导致时间错乱全局
方法命名混乱getdataString应为getDataString(驼峰命名)第 35 行
逻辑错误getNextDay方法实际获取的是7天前,而非"下一天"第 41 行
大小写错误hh表示12小时制,HH才是24小时制第 51 行
资源泄露未使用泛型,原始类型ArrayList有警告第 68 行
魔法数字17代表周日和周六,可读性差第 84 行
无用参数getMonthWeekends传入Trade对象却未使用第 65 行

🟡 代码风格问题

  • 注释模板老旧(Javadoc 格式混乱)

  • 异常处理粗糙(直接抛出IOException不合理)

  • 缺少输入校验(空指针风险)


二、完整代码

✅ 方案一:Java 8+ 现代写法(推荐)

import java.time.*; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * 日期时间工具类 * * <p>基于 Java 8 Time API 实现,线程安全,时区明确</p> * * @author YourName * @since 2024-01-01 */ public class DateUtil { // ==================== 常量定义 ==================== /** 常用日期格式 */ public static final String FORMAT_DATE = "yyyy-MM-dd"; public static final String FORMAT_DATE_TIME = "yyyy-MM-dd HH:mm:ss"; public static final String FORMAT_COMPACT = "yyyyMMdd"; public static final String FORMAT_COMPACT_TIME = "yyyyMMddHHmmss"; /** 默认时区:系统时区(可根据业务改为 ZoneId.of("Asia/Shanghai")) */ private static final ZoneId DEFAULT_ZONE = ZoneId.systemDefault(); // ==================== 私有构造 ==================== private DateUtil() { throw new UnsupportedOperationException("工具类禁止实例化"); } // ==================== 核心方法 ==================== /** * 获取当前时间的指定格式字符串 * * <p><b>示例:</b></p> * <pre> * String today = DateUtil.getDataString("yyyyMMdd"); // 20240115 * String now = DateUtil.getDataString("yyyy-MM-dd HH:mm:ss"); // 2024-01-15 14:30:00 * </pre> * * @param pattern 日期格式模式,如 "yyyy-MM-dd" * @return 格式化后的时间字符串 * @throws IllegalArgumentException 如果 pattern 为空 */ public static String getDataString(String pattern) { Objects.requireNonNull(pattern, "日期格式不能为空"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern) .withZone(DEFAULT_ZONE); return formatter.format(Instant.now()); } /** * 获取指定日期前后 N 天的日期字符串 * * <p>days 为正表示未来,为负表示过去</p> * * <p><b>示例:</b></p> * <pre> * // 获取7天后 * String nextWeek = DateUtil.getRelativeDay(new Date(), "yyyy-MM-dd", 7); * // 获取3天前 * String threeDaysAgo = DateUtil.getRelativeDay(new Date(), "yyyy-MM-dd", -3); * </pre> * * @param date 基准日期 * @param pattern 输出格式 * @param days 偏移天数(正数往后,负数往前) * @return 格式化后的日期字符串 */ public static String getRelativeDay(Date date, String pattern, int days) { Objects.requireNonNull(date, "日期不能为空"); Objects.requireNonNull(pattern, "格式不能为空"); LocalDateTime localDateTime = date.toInstant() .atZone(DEFAULT_ZONE) .toLocalDateTime() .plusDays(days); return DateTimeFormatter.ofPattern(pattern) .format(localDateTime); } /** * 格式化日期字符串转换 * * <p>将 "yyyyMMddHHmmss" 格式转换为 "yyyy-MM-dd HH:mm:ss"</p> * * <p><b>注意:</b>使用 HH(24小时制),而非 hh(12小时制)</p> * * @param dateStr 源日期字符串,如 "20240115143000" * @return 格式化后的字符串,如 "2024-01-15 14:30:00" * @throws IllegalArgumentException 如果格式不匹配 */ public static String formatDateString(String dateStr) { if (dateStr == null || dateStr.length() != 14) { throw new IllegalArgumentException("日期字符串必须为14位数字格式:yyyyMMddHHmmss"); } try { // 注意:HH 是24小时制,hh 是12小时制 DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern(FORMAT_COMPACT_TIME); LocalDateTime dateTime = LocalDateTime.parse(dateStr, sourceFormatter); return DateTimeFormatter.ofPattern(FORMAT_DATE_TIME).format(dateTime); } catch (DateTimeParseException e) { throw new IllegalArgumentException("日期解析失败: " + dateStr, e); } } /** * 获取指定年月的所有周末日期 * * <p>返回该月所有周六和周日的日期数字列表</p> * * <p><b>示例:</b></p> * <pre> * List<String> weekends = DateUtil.getMonthWeekends(2024, 1); * // 返回 ["6", "7", "13", "14", "20", "21", "27", "28"] * </pre> * * @param year 年份,如 2024 * @param month 月份,1-12 * @return 周末日期列表(字符串形式,如 "1", "15") * @throws IllegalArgumentException 如果月份不合法 */ public static List<String> getMonthWeekends(int year, int month) { if (month < 1 || month > 12) { throw new IllegalArgumentException("月份必须在 1-12 之间"); } List<String> weekends = new ArrayList<>(); // 获取该月第一天 LocalDate firstDay = LocalDate.of(year, month, 1); // 获取该月最后一天 LocalDate lastDay = firstDay.with(TemporalAdjusters.lastDayOfMonth()); // 遍历该月每一天 for (LocalDate date = firstDay; !date.isAfter(lastDay); date = date.plusDays(1)) { DayOfWeek dayOfWeek = date.getDayOfWeek(); // DayOfWeek.SATURDAY = 6, DayOfWeek.SUNDAY = 7 if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) { weekends.add(String.valueOf(date.getDayOfMonth())); } } return weekends; } /** * 判断是否为闰年 * * <p>闰年规则:能被4整除但不能被100整除,或能被400整除</p> * * @param year 年份 * @return true 表示闰年 */ public static boolean isLeapYear(int year) { return Year.of(year).isLeap(); } /** * 获取指定月份的天数 * * <p>自动处理闰年2月的情况</p> * * @param year 年份 * @param month 月份,1-12 * @return 该月天数(28/29/30/31) */ public static int getLastDayOfMonth(int year, int month) { return YearMonth.of(year, month).lengthOfMonth(); } // ==================== 扩展实用方法 ==================== /** * 获取当前时间戳(毫秒) */ public static long currentTimeMillis() { return System.currentTimeMillis(); } /** * 获取今天的开始时间 00:00:00 */ public static Date getStartOfToday() { return Date.from(LocalDate.now() .atStartOfDay(DEFAULT_ZONE) .toInstant()); } /** * 获取今天的结束时间 23:59:59.999 */ public static Date getEndOfToday() { return Date.from(LocalDate.now() .atTime(LocalTime.MAX) .atZone(DEFAULT_ZONE) .toInstant()); } }

✅ 方案二:兼容旧项目(Java 7 及以下)

如果项目必须停留在 Java 7,使用ThreadLocal包装SimpleDateFormat

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; /** * 线程安全的日期工具类(兼容 Java 7) */ public class DateUtilLegacy { // 使用 ThreadLocal 保证线程安全 private static final ThreadLocal<Map<String, SimpleDateFormat>> FORMATTER_CACHE = new ThreadLocal<Map<String, SimpleDateFormat>>() { @Override protected Map<String, SimpleDateFormat> initialValue() { return new HashMap<>(); } }; private static SimpleDateFormat getFormatter(String pattern) { Map<String, SimpleDateFormat> cache = FORMATTER_CACHE.get(); SimpleDateFormat formatter = cache.get(pattern); if (formatter == null) { formatter = new SimpleDateFormat(pattern); formatter.setTimeZone(TimeZone.getDefault()); // 明确时区 cache.put(pattern, formatter); } return formatter; } /** * 获取当前时间的指定格式字符串 */ public static String getDataString(String pattern) { if (pattern == null || pattern.isEmpty()) { throw new IllegalArgumentException("格式不能为空"); } return getFormatter(pattern).format(new Date()); } /** * 获取相对日期的字符串(修复原方法命名歧义) * * @param date 基准日期 * @param pattern 格式 * @param days 偏移量(正数往后,负数往前) */ public static String getRelativeDay(Date date, String pattern, int days) { Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.add(Calendar.DAY_OF_MONTH, days); // 修复:原代码硬编码 -7 是 Bug return getFormatter(pattern).format(cal.getTime()); } /** * 转换日期格式(修复:HH 是24小时制) */ public static String formatDateString(String dateStr) throws ParseException { if (dateStr == null || dateStr.length() != 14) { throw new IllegalArgumentException("必须为14位数字"); } // 注意:HH 是24小时制,hh 是12小时制(原代码错误) SimpleDateFormat source = getFormatter("yyyyMMddHHmmss"); SimpleDateFormat target = getFormatter("yyyy-MM-dd HH:mm:ss"); return target.format(source.parse(dateStr)); } /** * 获取指定年月的周末日期列表 */ public static List<String> getMonthWeekends(int year, int month) { List<String> weekends = new ArrayList<>(); Calendar cal = Calendar.getInstance(); cal.set(year, month - 1, 1); int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); for (int day = 1; day <= maxDay; day++) { cal.set(Calendar.DAY_OF_MONTH, day); int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); // Calendar.SUNDAY = 1, Calendar.SATURDAY = 7 if (dayOfWeek == Calendar.SUNDAY || dayOfWeek == Calendar.SATURDAY) { weekends.add(String.valueOf(day)); } } return weekends; } public static boolean isLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } public static int getLastDayOfMonth(int year, int month) { Calendar cal = Calendar.getInstance(); cal.set(year, month - 1, 1); return cal.getActualMaximum(Calendar.DAY_OF_MONTH); } }

三、关键知识点详解

1️⃣ 线程安全:为什么SimpleDateFormat会出问题?

// ❌ 错误示范:共享实例 private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd"); // 多线程并发调用 SDF.format() 会抛出异常或数据错乱 // ✅ 解决方案: // 1. Java 8+ 使用 DateTimeFormatter(线程安全) // 2. Java 7 使用 ThreadLocal 隔离 // 3. 每次 new 新实例(性能较差)

2️⃣ 时区陷阱:为什么服务器时间会变?

// 隐患:服务器时区变更会导致时间错乱 new Date(); // 依赖系统时区 // 正确做法:明确指定时区 ZoneId shanghai = ZoneId.of("Asia/Shanghai"); ZonedDateTime now = ZonedDateTime.now(shanghai);

3️⃣ 格式字符大小写:HHvshh

符号含义示例
HH24小时制(00-23)14:30
hh12小时制(01-12)02:30 PM
MM月份(01-12)01月
mm分钟(00-59)30分
ss秒(00-59)00秒
SSS毫秒(000-999)000毫秒
// ❌ 原代码错误:下午2点会变成凌晨2点 SimpleDateFormat df = new SimpleDateFormat("yyyyMMddhhmmss"); // ✅ 正确写法:使用 HH SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");

四、快速对比表

特性原代码优化后(Java 8)优化后(Java 7)
线程安全❌ 危险✅ 天然安全✅ ThreadLocal
时区处理❌ 隐式✅ 明确指定✅ 明确指定
API 设计❌ 混乱✅ 语义清晰✅ 语义清晰
异常处理❌ 粗糙✅ 详细校验✅ 详细校验
性能⚠️ 一般✅ 更高✅ 缓存优化
可读性❌ 差✅ 优秀✅ 良好

五、落地建议

立即执行:

  1. 替换getNextDay方法:原方法逻辑是获取7天前,确认业务方是否需要改为+1或保持-7但改名

  2. 修复hhHH:避免12小时制导致的上午下午错乱

  3. 删除无用参数getMonthWeekends中的Trade参数

渐进优化:

  1. 新项目:直接使用 Java 8 Time API

  2. 老项目:引入ThreadLocal方案,逐步替换

  3. 单元测试:为每个方法补充边界测试(闰年、跨月、时区切换等)

依赖升级(Maven):

<!-- 如需更强大的日期处理,可引入 --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.12.5</version> </dependency>

这份优化后的代码不仅修复了所有隐患,还遵循了现代 Java 开发的最佳实践。建议优先采用Java 8+ 方案,如果受限于老项目,使用ThreadLocal 方案也能确保线程安全。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/2 14:18:59

Fan Control终极指南:Windows风扇智能控制系统完全配置手册

Fan Control终极指南&#xff1a;Windows风扇智能控制系统完全配置手册 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trendi…

作者头像 李华
网站建设 2026/4/15 16:31:38

深度解析HM3D数据集:1000个真实室内场景如何推动具身AI革命

深度解析HM3D数据集&#xff1a;1000个真实室内场景如何推动具身AI革命 【免费下载链接】habitat-matterport3d-dataset This repository contains code to reproduce experimental results from our HM3D paper in NeurIPS 2021. 项目地址: https://gitcode.com/gh_mirrors/…

作者头像 李华
网站建设 2026/4/15 20:35:56

告别版本混乱!CANoe多人协作项目文件管理实战指南(含DBC比对技巧)

告别版本混乱&#xff01;CANoe多人协作项目文件管理实战指南&#xff08;含DBC比对技巧&#xff09; 在汽车电子开发领域&#xff0c;CANoe作为主流的网络仿真与测试工具&#xff0c;其项目文件的管理质量直接影响团队协作效率。当多个工程师同时修改配置文件、DBC数据库或测试…

作者头像 李华
网站建设 2026/4/16 3:48:22

无涯教程-MySQL - GREATEST() 函数实战:从基础语法到多场景应用

1. GREATEST()函数基础入门 刚接触MySQL的朋友可能会好奇&#xff0c;这个GREATEST()函数到底是干嘛的&#xff1f;简单来说&#xff0c;它就像个智能比较器&#xff0c;能从一堆数值或字符串中帮你挑出最大的那个。我刚开始用的时候也觉得这功能太基础了&#xff0c;直到在实际…

作者头像 李华
网站建设 2026/4/16 7:36:44

LLaVA-Chef:多模态生成模型在智能食谱创作中的革新应用(2024)

1. 多模态AI如何重新定义智能食谱创作 当你打开冰箱面对一堆杂乱食材时&#xff0c;是否曾希望有个"数字厨师"能帮你设计菜单&#xff1f;这正是LLaVA-Chef正在实现的场景。这个基于多模态生成模型的新系统&#xff0c;正在彻底改变我们获取烹饪灵感的方式。 传统食谱…

作者头像 李华
网站建设 2026/4/15 22:34:49

Qwen2.5-14B-Instruct部署案例:像素剧本圣殿双GPU并行推理性能实测

Qwen2.5-14B-Instruct部署案例&#xff1a;像素剧本圣殿双GPU并行推理性能实测 1. 项目概述 像素剧本圣殿&#xff08;Pixel Script Temple&#xff09;是一款基于Qwen2.5-14B-Instruct深度微调的专业剧本创作工具。这个独特的创作环境将强大的AI推理能力与复古未来像素美学相…

作者头像 李华