一、有什么问题吗java.util.Date?
java.util.Date(Date从现在开始)是一个糟糕的类型,这解释了为什么它的大部分内容在 Java 1.1 中被弃用(但不幸的是仍在使用)。
设计缺陷包括:
- 它的名称具有误导性:它并不代表一个日期,而是代表时间的一个瞬间。所以它应该被称为
Instant——正如它的java.time等价物一样。 - 它是非最终的:这鼓励了对继承的不良使用,例如
java.sql.Date(这意味着代表一个日期,并且由于具有相同的短名称而也令人困惑) - 它是可变的:日期/时间类型是自然值,可以通过不可变类型有效地建模。可变的事实
Date(例如通过setTime方法)意味着勤奋的开发人员最终会在各处创建防御性副本。 - 它在许多地方(包括)隐式使用系统本地时区,
toString()这让许多开发人员感到困惑。有关此内容的更多信息,请参阅“什么是即时”部分 - 它的月份编号是从 0 开始的,是从 C 语言复制的。这导致了很多很多相差一的错误。
- 它的年份编号是基于 1900 年的,也是从 C 语言复制的。当然,当 Java 出现时,我们已经意识到这不利于可读性?
- 它的方法命名不明确:
getDate()返回月份中的某一天,并getDay()返回星期几。给这些更具描述性的名字有多难? - 对于是否支持闰秒含糊其辞:“秒由 0 到 61 之间的整数表示;值 60 和 61 仅在闰秒时出现,即使如此,也仅在实际正确跟踪闰秒的 Java 实现中出现。” 我强烈怀疑大多数开发人员(包括我自己)都做了很多假设,认为 for 的范围
getSeconds()实际上在 0-59 范围内(含)。 - 它的宽容没有明显的理由:“在所有情况下,为这些目的而对方法给出的论据不必落在指定的范围内; 例如,日期可以指定为 1 月 32 日,并被解释为 2 月 1 日。” 多久有用一次?
关键原因如下:

原文如下:为什么要避免使用Date类?
二、为啥要改?
我们要改的原因很简单,我们的代码缺陷扫描规则认为这是一个必须修改的缺陷,否则不给发布,不改不行,服了。

解决思路:避免使用
java.util.Date与java.sql.Date类和其提供的API,考虑使用java.time.Instant类或java.time.LocalDateTime类及其提供的API替代。
三、怎么改?
只能说这种基础的类改起来牵一发动全身,需要从DO实体类看起,然后就是各种Converter,最后是DTO。由于我们还是微服务架构,业务服务依赖于基础服务的API,所以必须要一起改否则就会报错。这里就不细说修改流程了,主要说一下我们在改造的时候遇到的一些问题。
1. 耐心比对数据库日期字段和DO的映射
(1)确定字段类型
首先你需要确定数据对象中的 Date 字段代表的是日期、时间还是时间戳。
- 如果字段代表日期和时间,则可能需要使用
LocalDateTime。 - 如果字段仅代表日期,则可能需要使用
LocalDate。 - 如果字段仅代表时间,则可能需要使用
LocalTime。 - 如果字段需要保存时间戳(带时区的),则可能需要使用
Instant或ZonedDateTime。
(2)更新数据对象类
更新数据对象类中的字段,把 Date 类型改为适当的 java.time 类型。
2. 将DateUtil中的方法改造
(1)替换原来的new Date()和Calendar.getInstance().getTime()
原来的方式:
1Date nowDate = new Date(); 2Date nowCalendarDate = Calendar.getInstance().getTime();
使用 java.time 改造后:
1// 使用Instant代表一个时间点,这与Date类似 2Instant nowInstant = Instant.now(); 3 4// 如果需要用到具体的日期和时间(例如年、月、日、时、分、秒) 5LocalDateTime nowLocalDateTime = LocalDateTime.now(); 6 7// 如果你需要和特定的时区交互,可以使用ZonedDateTime 8ZonedDateTime nowZonedDateTime = ZonedDateTime.now(); 9 10// 如果你需要转换回java.util.Date,你可以这样做(假设你的代码其他部分还需要使用Date) 11Date nowFromDateInstant = Date.from(nowInstant); 12 13// 如果需要与java.sql.Timestamp交互 14java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);
一些注意点:
Instant表示的是一个时间点,它是时区无关的,相当于旧的Date类。它通常用于表示时间戳。LocalDateTime表示没有时区信息的日期和时间,它不能直接转换为时间戳,除非你将其与时区结合使用(例如通过ZonedDateTime)。ZonedDateTime包含时区信息的日期和时间,它更类似于Calendar,因为Calendar也包含时区信息。- 当你需要将
java.time对象转换回java.util.Date对象时,可以使用Date.from(Instant)方法。这在你的代码需要与旧的API或库交互时非常有用。
(2)一些基础的方法改造
a. dateFormat
原来的方式
1public static String dateFormat(Date date, String dateFormat) { 2 SimpleDateFormat formatter = new SimpleDateFormat(dateFormat); 3 return formatter.format(date); 4}
使用java.time改造后
1public static String dateFormat(LocalDateTime date, String dateFormat) { 2 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat); 3 return date.format(formatter); 4}
b. addSecond、addMinute、addHour、addDay、addMonth、addYear
原来的方式
1public static Date addSecond(Date date, int second) { 2 Calendar calendar = Calendar.getInstance(); 3 calendar.setTime(date); 4 calendar.add(13, second); 5 return calendar.getTime(); 6} 7 8public static Date addMinute(Date date, int minute) { 9 Calendar calendar = Calendar.getInstance(); 10 calendar.setTime(date); 11 calendar.add(12, minute); 12 return calendar.getTime(); 13} 14 15public static Date addHour(Date date, int hour) { 16 Calendar calendar = Calendar.getInstance(); 17 calendar.setTime(date); 18 calendar.add(10, hour); 19 return calendar.getTime(); 20} 21 22public static Date addDay(Date date, int day) { 23 Calendar calendar = Calendar.getInstance(); 24 calendar.setTime(date); 25 calendar.add(5, day); 26 return calendar.getTime(); 27} 28 29public static Date addMonth(Date date, int month) { 30 Calendar calendar = Calendar.getInstance(); 31 calendar.setTime(date); 32 calendar.add(2, month); 33 return calendar.getTime(); 34} 35 36public static Date addYear(Date date, int year) { 37 Calendar calendar = Calendar.getInstance(); 38 calendar.setTime(date); 39 calendar.add(1, year); 40 return calendar.getTime(); 41}
使用java.time改造后
1public static LocalDateTime addSecond(LocalDateTime date, int second) { 2 return date.plusSeconds(second); 3} 4 5public static LocalDateTime addMinute(LocalDateTime date, int minute) { 6 return date.plusMinutes(minute); 7} 8 9public static LocalDateTime addHour(LocalDateTime date, int hour) { 10 return date.plusHours(hour); 11} 12 13public static LocalDateTime addDay(LocalDateTime date, int day) { 14 return date.plusDays(day); 15} 16 17public static LocalDateTime addMonth(LocalDateTime date, int month) { 18 return date.plusMonths(month); 19} 20 21public static LocalDateTime addYear(LocalDateTime date, int year) { 22 return date.plusYears(year); 23}
c. dateToWeek
原来的方式
1public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}; 2public static String dateToWeek(Date date) { 3 Calendar cal = Calendar.getInstance(); 4 cal.setTime(date); 5 return WEEK_DAY_OF_CHINESE[cal.get(7) - 1]; 6}
使用java.time改造后
1public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}; 2 3public static String dateToWeek(LocalDate date) { 4 DayOfWeek dayOfWeek = date.getDayOfWeek(); 5 return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7]; 6}
d. getStartOfDay和getEndOfDay
原来的方式
1public static Date getStartTimeOfDay(Date date) { 2 if (date == null) { 3 return null; 4 } else { 5 LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault()); 6 LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN); 7 return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant()); 8 } 9} 10 11public static Date getEndTimeOfDay(Date date) { 12 if (date == null) { 13 return null; 14 } else { 15 LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault()); 16 LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX); 17 return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant()); 18 } 19}
使用java.time改造后
1public static LocalDateTime getStartTimeOfDay(LocalDateTime date) { 2 if (date == null) { 3 return null; 4 } else { 5 // 获取一天的开始时间,即00:00 6 return date.toLocalDate().atStartOfDay(); 7 } 8} 9 10public static LocalDateTime getEndTimeOfDay(LocalDateTime date) { 11 if (date == null) { 12 return null; 13 } else { 14 // 获取一天的结束时间,即23:59:59.999999999 15 return date.toLocalDate().atTime(LocalTime.MAX); 16 } 17}
e. betweenStartAndEnd
原来的方式
1public static Boolean betweenStartAndEnd(Date nowTime, Date beginTime, Date endTime) { 2 Calendar date = Calendar.getInstance(); 3 date.setTime(nowTime); 4 Calendar begin = Calendar.getInstance(); 5 begin.setTime(beginTime); 6 Calendar end = Calendar.getInstance(); 7 end.setTime(endTime); 8 return date.after(begin) && date.before(end); 9}
使用java.time改造后
1public static Boolean betweenStartAndEnd(Instant nowTime, Instant beginTime, Instant endTime) { 2 return nowTime.isAfter(beginTime) && nowTime.isBefore(endTime); 3}
我这里就只列了一些,如果有缺失的可以自己补充,不会写的话直接问问ChatGPT,它最会干这事了。最后把这些修改后的方法替换一下就行了。
四、小结一下
这个改造难度不高,但是复杂度非常高,一个地方没改好,轻则接口报错,重则启动失败,非常耗费精力,真不想改。
