从能用到好用一、问题诊断 严重问题问题影响位置线程不安全SimpleDateFormat非线程安全多线程环境下会抛异常或数据错乱所有方法时区隐患未指定时区服务器时区变更会导致时间错乱全局方法命名混乱getdataString应为getDataString驼峰命名第 35 行逻辑错误getNextDay方法实际获取的是7天前而非下一天第 41 行大小写错误hh表示12小时制HH才是24小时制第 51 行资源泄露未使用泛型原始类型ArrayList有警告第 68 行魔法数字1和7代表周日和周六可读性差第 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(工具类禁止实例化); } // 核心方法 /** * 获取当前时间的指定格式字符串 * * pb示例/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 天的日期字符串 * * pdays 为正表示未来为负表示过去/p * * pb示例/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 * * pb注意/b使用 HH24小时制而非 hh12小时制/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 * * pb示例/b/p * pre * ListString 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 ListString getMonthWeekends(int year, int month) { if (month 1 || month 12) { throw new IllegalArgumentException(月份必须在 1-12 之间); } ListString 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包装SimpleDateFormatimport java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; /** * 线程安全的日期工具类兼容 Java 7 */ public class DateUtilLegacy { // 使用 ThreadLocal 保证线程安全 private static final ThreadLocalMapString, SimpleDateFormat FORMATTER_CACHE new ThreadLocalMapString, SimpleDateFormat() { Override protected MapString, SimpleDateFormat initialValue() { return new HashMap(); } }; private static SimpleDateFormat getFormatter(String pattern) { MapString, 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 ListString getMonthWeekends(int year, int month) { ListString 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-2314:30hh12小时制01-1202:30 PMMM月份01-1201月mm分钟00-5930分ss秒00-5900秒SSS毫秒000-999000毫秒// ❌ 原代码错误下午2点会变成凌晨2点 SimpleDateFormat df new SimpleDateFormat(yyyyMMddhhmmss); // ✅ 正确写法使用 HH SimpleDateFormat df new SimpleDateFormat(yyyyMMddHHmmss);四、快速对比表特性原代码优化后Java 8优化后Java 7线程安全❌ 危险✅ 天然安全✅ ThreadLocal时区处理❌ 隐式✅ 明确指定✅ 明确指定API 设计❌ 混乱✅ 语义清晰✅ 语义清晰异常处理❌ 粗糙✅ 详细校验✅ 详细校验性能⚠️ 一般✅ 更高✅ 缓存优化可读性❌ 差✅ 优秀✅ 良好五、落地建议立即执行替换getNextDay方法原方法逻辑是获取7天前确认业务方是否需要改为1或保持-7但改名修复hh→HH避免12小时制导致的上午下午错乱删除无用参数getMonthWeekends中的Trade参数渐进优化新项目直接使用 Java 8 Time API老项目引入ThreadLocal方案逐步替换单元测试为每个方法补充边界测试闰年、跨月、时区切换等依赖升级Maven!-- 如需更强大的日期处理可引入 -- dependency groupIdjoda-time/groupId artifactIdjoda-time/artifactId version2.12.5/version /dependency这份优化后的代码不仅修复了所有隐患还遵循了现代 Java 开发的最佳实践。建议优先采用Java 8 方案如果受限于老项目使用ThreadLocal 方案也能确保线程安全。