為什麼現在連Date類都不建議使用了?

sum墨發表於2024-03-07

一、有什麼問題嗎java.util.Date

java.util.DateDate從現在開始)是一個糟糕的型別,這解釋了為什麼它的大部分內容在 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.Datejava.sql.Date類和其提供的API,考慮使用java.time.Instant類或java.time.LocalDateTime類及其提供的API替代。

三、怎麼改?

只能說這種基礎的類改起來牽一髮動全身,需要從DO實體類看起,然後就是各種Converter,最後是DTO。由於我們還是微服務架構,業務服務依賴於基礎服務的API,所以必須要一起改否則就會報錯。這裡就不細說修改流程了,主要說一下我們在改造的時候遇到的一些問題。

1. 耐心比對資料庫日期欄位和DO的對映

(1)確定欄位型別

首先你需要確定資料物件中的 Date 欄位代表的是日期、時間還是時間戳。

  • 如果欄位代表日期和時間,則可能需要使用 LocalDateTime
  • 如果欄位僅代表日期,則可能需要使用 LocalDate
  • 如果欄位僅代表時間,則可能需要使用 LocalTime
  • 如果欄位需要儲存時間戳(帶時區的),則可能需要使用 InstantZonedDateTime

(2)更新資料物件類

更新資料物件類中的欄位,把 Date 型別改為適當的 java.time 型別。

2. 將DateUtil中的方法改造

(1)替換原來的new Date()和Calendar.getInstance().getTime()

原來的方式:

Date nowDate = new Date();
Date nowCalendarDate = Calendar.getInstance().getTime();

使用 java.time 改造後:

// 使用Instant代表一個時間點,這與Date類似
Instant nowInstant = Instant.now();

// 如果需要用到具體的日期和時間(例如年、月、日、時、分、秒)
LocalDateTime nowLocalDateTime = LocalDateTime.now();

// 如果你需要和特定的時區互動,可以使用ZonedDateTime
ZonedDateTime nowZonedDateTime = ZonedDateTime.now();

// 如果你需要轉換回java.util.Date,你可以這樣做(假設你的程式碼其他部分還需要使用Date)
Date nowFromDateInstant = Date.from(nowInstant);

// 如果需要與java.sql.Timestamp互動
java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);

一些注意點:

  1. Instant 表示的是一個時間點,它是時區無關的,相當於舊的 Date 類。它通常用於表示時間戳。
  2. LocalDateTime 表示沒有時區資訊的日期和時間,它不能直接轉換為時間戳,除非你將其與時區結合使用(例如透過 ZonedDateTime)。
  3. ZonedDateTime 包含時區資訊的日期和時間,它更類似於 Calendar,因為 Calendar 也包含時區資訊。
  4. 當你需要將 java.time 物件轉換回 java.util.Date 物件時,可以使用 Date.from(Instant) 方法。這在你的程式碼需要與舊的API或庫互動時非常有用。

(2)一些基礎的方法改造

a. dateFormat

原來的方式

public static String dateFormat(Date date, String dateFormat) {
    SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
    return formatter.format(date);
}

使用java.time改造後

public static String dateFormat(LocalDateTime date, String dateFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
    return date.format(formatter);
}

b. addSecond、addMinute、addHour、addDay、addMonth、addYear

原來的方式

public static Date addSecond(Date date, int second) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(13, second);
    return calendar.getTime();
}

public static Date addMinute(Date date, int minute) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(12, minute);
    return calendar.getTime();
}

public static Date addHour(Date date, int hour) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(10, hour);
    return calendar.getTime();
}

public static Date addDay(Date date, int day) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(5, day);
    return calendar.getTime();
}

public static Date addMonth(Date date, int month) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(2, month);
    return calendar.getTime();
}

public static Date addYear(Date date, int year) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(1, year);
    return calendar.getTime();
}

使用java.time改造後

public static LocalDateTime addSecond(LocalDateTime date, int second) {
    return date.plusSeconds(second);
}

public static LocalDateTime addMinute(LocalDateTime date, int minute) {
    return date.plusMinutes(minute);
}

public static LocalDateTime addHour(LocalDateTime date, int hour) {
    return date.plusHours(hour);
}

public static LocalDateTime addDay(LocalDateTime date, int day) {
    return date.plusDays(day);
}

public static LocalDateTime addMonth(LocalDateTime date, int month) {
    return date.plusMonths(month);
}

public static LocalDateTime addYear(LocalDateTime date, int year) {
    return date.plusYears(year);
}

c. dateToWeek

原來的方式

public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"週日", "週一", "週二", "週三", "週四", "週五", "週六"};
public static String dateToWeek(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    return WEEK_DAY_OF_CHINESE[cal.get(7) - 1];
}

使用java.time改造後

public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"週日", "週一", "週二", "週三", "週四", "週五", "週六"};

public static String dateToWeek(LocalDate date) {
    DayOfWeek dayOfWeek = date.getDayOfWeek();
    return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7];
}

d. getStartOfDay和getEndOfDay

原來的方式

public static Date getStartTimeOfDay(Date date) {
    if (date == null) {
        return null;
    } else {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
        LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
        return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }
}

public static Date getEndTimeOfDay(Date date) {
    if (date == null) {
        return null;
    } else {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
        LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
        return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }
}

使用java.time改造後

public static LocalDateTime getStartTimeOfDay(LocalDateTime date) {
    if (date == null) {
        return null;
    } else {
        // 獲取一天的開始時間,即00:00
        return date.toLocalDate().atStartOfDay();
    }
}

public static LocalDateTime getEndTimeOfDay(LocalDateTime date) {
    if (date == null) {
        return null;
    } else {
        // 獲取一天的結束時間,即23:59:59.999999999
        return date.toLocalDate().atTime(LocalTime.MAX);
    }
}

e. betweenStartAndEnd

原來的方式

public static Boolean betweenStartAndEnd(Date nowTime, Date beginTime, Date endTime) {
    Calendar date = Calendar.getInstance();
    date.setTime(nowTime);
    Calendar begin = Calendar.getInstance();
    begin.setTime(beginTime);
    Calendar end = Calendar.getInstance();
    end.setTime(endTime);
    return date.after(begin) && date.before(end);
}

使用java.time改造後

public static Boolean betweenStartAndEnd(Instant nowTime, Instant beginTime, Instant endTime) {
    return nowTime.isAfter(beginTime) && nowTime.isBefore(endTime);
}

我這裡就只列了一些,如果有缺失的可以自己補充,不會寫的話直接問問ChatGPT,它最會幹這事了。最後把這些修改後的方法替換一下就行了。

四、小結一下

這個改造難度不高,但是複雜度非常高,一個地方沒改好,輕則介面報錯,重則啟動失敗,非常耗費精力,真不想改。

相關文章