時間點
表示某種粒度的時間點
2004 年 3 月 7 日
這是 進階企業應用程式架構開發 的一部分,我在 2000 年代中期撰寫。遺憾的是,自此以後有太多其他事情吸引了我的注意力,因此我沒有時間進一步研究它們,而且在可預見的未來我也看不到多少時間。因此,這份資料很大程度上仍是草稿形式,在我有時間再次研究它之前,我不會進行任何更正或更新。
時間點,顧名思義,就是時間中的某個點。我在 2000 年 8 月 28 日撰寫這篇文章。那是一個時間點。這引出了問題,為什麼要將時間點寫成模式?畢竟,它們現在是許多語言和大多數類別庫的一部分。
問題在於時間點有一些細微差別,即使對於建立類別庫的人來說也不明顯。
運作方式
時間點最常見的問題是它們具有不同的精確度。當我說我在 2000 年 8 月 28 日撰寫這篇文章,而我說我在 2000 年 8 月 28 日下午 2:33:34 撰寫這篇文章時,我說的是兩件不同的事情。一個陳述是到天為精確度,另一個是到秒為精確度。請注意,第二個精確度比第一個精確度更高,但在這種情況下碰巧不太準確。任何時間點都需要知道其精確度,以便您可以回答問題,例如此事件是否與另一個事件同時發生。
關鍵在於,對於大多數領域,您不能只依賴精確度。許多業務都以天為精確度進行。我的電話轉帳請求在一天中的什麼時候發生並不重要,它只是根據我這樣做的日期進行處理。否則生活可能會變得非常煩人。如果我想在帳單出示付款時當天轉帳,我是否應該擔心帳單出示的確切時間。常見的商業做法是,如果是在同一天,我就不會冒透支或拒絕付款的風險。
小心使用過於精確的時間點。許多平台僅提供秒或更精確的時間點。那麼,我該如何表示 2000 年 8 月 28 日的任何時間?通常,您會使用慣例,例如在午夜「00:00:00」時。這在某些情況下可能有效,但問題會逐漸浮現。將幾毫秒洩漏到時間點中通常非常容易,此時您會遇到問題,因為 2000 年 8 月 28 日並不等於 2000 年 8 月 28 日。
時間點的另一個棘手領域是如何處理時區。與精確度類似,對於所有應用程式都沒有正確的答案。有時您需要帶有時區的時間點,有時則不需要。沒有時區的時間點是完全合理的,表示在它的內容的當地時間內。您會發現進一步的時區資訊無用或可以從其內容中取得。在不需要時請小心使用時區。
這個問題的一個例子讓我非常惱火,出現在 Microsoft 的 Outlook 中。您輸入的任何約會時間(至少在 Outlook 98 中)都是特定時區的。因此,如果我移動時區並變更筆記型電腦上的時間,會議時間就會變更(除非我在一開始將時間輸入 Outlook 時就考慮到這一點,但大多數人並不會這麼做)。這件事會變得更複雜,因為如果我選擇全天會議,它不會將這一天當成時間點的日精度,而是選擇一個時間點的範圍。因此,當我飛到芝加哥時,我在波士頓輸入的全天會議變成晚上 11 點到晚上 11 點。更糟糕的是,我的 WinCE 電腦只會將全天會議顯示為日精度,使用範圍的開始時間,因此將我的全天約會移到前一天。
在我對時間點的討論中,時間點的一個重要特點是它們是錨定的,也就是說時區是指時間軸上的特定點。值為下午 2:30 的時間物件沒有錨定,因為它可能表示任何一天的下午 2.30。如果有一個日精度時間點來提供下午 2.30 的內容,那麼下午 2.30,加上日精度時間,就表示一個(適當錨定的)時間點。
時間點類別的明顯且常見服務是取得目前時間點。通常這會透過使用作業系統來詢問系統時鐘來完成。然而在此處加入間接呼叫會是個好主意。測試通常需要穩定的時間,而且許多作業甚至可能需要在星期一執行上星期五的系統。因此除了存取系統日期外,也值得存取處理日期,而處理日期可能相同或不同。此外,處理日期應該可以在作業期間設定。當您使用記錄時,您可能需要在記錄中使用處理日期和系統日期。
何時使用
時間點(某種形式或其他形式)幾乎隨時都在使用。使用它們的關鍵問題是決定上述的精確度和時區問題。只有在您的網域需要時才使用天精確度時間點,不要嘗試使用慣例或 範圍 來偽造它,即使需要撰寫您自己的包裝類別。同樣地,除非您真的需要,否則不要使用時區。想想使用系統的人如何看待世界,許多人在不需要時並不會想到時區。
範例:簡單的日期精確度包裝器(Java)
以下是這裡用於範例的簡單日期精確度包裝器的一部分。基本結構包裝一個 Java Gregorian Calendar 實例
類別 MfDate...
private GregorianCalendar _base; public MfDate() { this(new GregorianCalendar()); } public MfDate(int year, int month, int day) { initialize (new GregorianCalendar(year, month - 1, day)); } private void initialize (GregorianCalendar arg) { _base = trimToDays(arg); } private GregorianCalendar trimToDays(GregorianCalendar arg) { GregorianCalendar result = arg; result.set(Calendar.HOUR_OF_DAY,0); result.set(Calendar.MINUTE, 0); result.set(Calendar.SECOND, 0); result.set(Calendar.MILLISECOND, 0); return result; }
請注意,我強制修剪,方法是將 Gregorian Calendar 中的適當值設定為零。我也提供一個使用美國風格數字引數的建構函式。由於我在書籍程式碼中大量使用這些,因此我可能會讓它變得容易....
以下是比較運算,這是行為範例,我只是將它委派給基礎物件。
類別 MfDate...
public boolean after (MfDate arg) { return getTime().after(arg.getTime()); } public boolean before (MfDate arg) { return getTime().before(arg.getTime()); } public int compareTo(Object arg) { MfDate other = (MfDate) arg; return getTime().compareTo(other.getTime()); } public boolean equals(Object arg) { if (! (arg instanceof MfDate)) return false; MfDate other = (MfDate) arg; return (_base.equals(other._base)); } public Date getTime() { return _base.getTime(); }
包裝器也允許我加入我認為方便的行為,但基礎類別中沒有。
類別 MfDate...
public MfDate addDays(int arg) { return new MfDate(new GregorianCalendar(getYear(), getMonth(), getDayOfMonth() + arg)); } public MfDate minusDays(int arg) { return addDays(-arg); }