共用方式為


在 .NET Framework 中使用 DateTime 撰寫最佳做法程式碼

 

Dan Rogers
Microsoft Corporation

2004 年 2 月

適用於
   Microsoft® .NET Framework
   Microsoft® ASP.NET Web 服務
   XML 序列化

總結:撰寫程式,以使用 Microsoft .NET Framework中的 DateTime 類型來儲存、執行計算及序列化時間值,需要知道 Windows 和 .NET 中可用的時程表示法相關聯的不同問題。 本文著重于涉及時間的重要測試和開發案例,並定義撰寫在 Microsoft 中使用 DateTime 類型之程式的最佳作法建議。以 NET 為基礎的應用程式和元件。 (18 個列印頁面)

目錄

背景
   何謂 DateTime,仍然為何?
   規則
儲存體策略
   最佳做法 #1
   最佳做法 #2
執行計算
   不要再次遇到問題
   最佳做法 #3
   排序日期時間方法
XML 的特殊案例
   最佳做法 #4
   最佳做法 #5
類別編解碼器 Quandary
   最佳做法 #6
處理日光節約時間
   最佳做法 #7
格式化和剖析User-Ready值
   未來考慮
DateTime.Now () 方法的問題
   最佳做法 #8
幾個已知的小額外專案
結論

背景

許多程式設計人員遇到指派,需要指派才能正確儲存及處理包含日期和時間資訊的資料。 一目了然,Common Language Runtime (CLR) DateTime 資料類型似乎很適合這些工作。 不過,對於程式設計人員,但較可能測試者而言,在程式只會失去正確時間值的追蹤的情況下,並不常見。 本文著重于與涉及 DateTime 的邏輯相關的問題,並這麼做時,發現撰寫和測試擷取、儲存、擷取及傳輸 DateTime 資訊的最佳做法。

何謂 DateTime,仍然為何?

當我們查看 NET Framework 類別庫檔時,我們看到「CLR System.DateTime 數值型別代表介於 12:00:00 午夜,0001 AD 到 11:59:59 PM,9999 AD 的日期和時間。」進一步閱讀時,我們會瞭解 DateTime 值代表某個時間點的立即,而且常見的作法是在國際標準時間 (UCT (UCT) 中記錄時間點值,這通常稱為 Greenwich Mean Time (GMT) 。

然後,程式設計人員發現 DateTime 類型很適合用來儲存在目前程式設計問題中可能發生的時間值,例如在商務應用程式中。 有了這個信心,許多不建議的程式設計人員都會開始撰寫程式碼,確信他們可以如往後所需一樣瞭解時間。 這個「學習即用」方法可引導您遇到一些問題,因此讓我們開始找出它們。 其範圍從檔中的問題到需要納入程式設計的行為。

System.DateTime 的 V1.0 和 1.1 檔會進行一些一般化,以擲回未建議的程式設計人員追蹤。例如,檔目前仍然指出在 DateTime 類別上找到的方法和屬性一律會使用值代表本機電腦本機時區的假設進行計算或比較。 這種一般化會變成非建構,因為有某些類型的日期和時間計算會假設 GMT,而其他則假設當地時區檢視。 本文稍後會說明這些區域。

因此,讓我們藉由列出一系列規則和最佳做法來探索 DateTime 類型,以協助您第一次正確運作程式碼。

規則

  1. 只有在比較或使用的實例是來自相同時區觀點的時間點表示時,DateTime 實例的計算和比較才有意義。
  2. 開發人員負責透過某些外部機制追蹤與 DateTime 值相關聯的時區資訊。 一般而言,當您儲存 DateTime 數值型別時,定義用來記錄時區資訊的另一個欄位或變數,即可達成此目的。 這種方法 (將時區意義與 DateTime 值一起儲存) 是最精確的,並可讓程式生命週期中不同時間點的不同開發人員清楚瞭解 DateTime 值的意義。 另一個常見的方法是在設計中將其設為「規則」,所有時間值都會儲存在特定時區內容中。 這種方法不需要額外的儲存體來儲存使用者對時區內容的檢視,但會引入時間值被錯誤解譯或儲存在未察覺規則的開發人員下的風險。
  3. 對代表電腦當地時間的值執行日期和時間計算,不一定會產生正確的結果。 在練習日光節約時間的時區內容中執行時間值的計算時,您應該在執行日期算術計算之前,將值轉換成通用時程表示法。 如需作業和適當時區內容的特定清單,請參閱排序 DateTime 方法一節中的資料表。
  4. DateTime 值的實例計算不會修改 實例的值,因此呼叫 MyDateTime.ToLocalTime () 不會修改 DateTime 實例的值。 與 Visual Basic®) 中 Date (相關聯的方法,以及 .NET CLR) 類別中的 DateTime (,會傳回代表計算或作業結果的新實例。
  5. 使用 .NET Framework 1.0 和 1.1 版時,請勿傳送代表System.XML UCT 時間的 DateTime 值。序列化。 這適用于 Date、Time 和 DateTime 值。 對於涉及 System.DateTime 之 XML 的 Web 服務和其他類型的序列化,請務必確定 DateTime 值中的值代表目前的電腦當地時間。 序列化程式會正確解碼以 GMT 編碼的 XML 架構定義的 DateTime 值 (位移值 = 0) ,但會將它解碼為本機電腦時間觀點。
  6. 一般而言,如果您要處理絕對經過的時間,例如測量逾時、執行算術,或執行不同 DateTime 值的比較,您應該盡可能嘗試並使用通用時間值,以便取得最佳可能的正確性,而不會影響時區和/或日光節約。
  7. 處理高層級、使用者面向的概念,例如排程,而且您可以放心地假設每天從使用者的觀點來看有 24 小時,藉由在當地時間執行算術和 cetera 來計數器 Rule #6。

在本文中,此簡單的規則清單可作為撰寫及測試處理日期之應用程式的一組最佳做法的基礎。

現在,您有數個已經查看您的程式碼,並說:「Oh darn,它未執行我預期執行的動作」,這是本文的目的。 對於我們目前尚未閱讀到此階段的暫時性,讓我們看看目前處理 DateTime 值 (相關的問題,我只會將此縮短為 「日期」) 。以 NET 為基礎的應用程式。

儲存體策略

根據上述規則,只有在瞭解與所處理日期值相關聯的時區資訊時,日期值的計算才有意義。 這表示無論您是暫時將值儲存在類別成員變數中,還是選擇儲存您收集的值到資料庫或檔案中,您都負責套用策略,讓相關聯的時區資訊稍後瞭解。

最佳做法 #1

撰寫程式碼時,將與 DateTime 類型相關聯的時區資訊儲存在輔助變數中。

另一種較不可靠,但較不可靠的策略是讓預存日期一律在儲存前轉換成特定時區,例如 GMT。 這似乎合理,而且許多小組都可以讓它運作。 不過,缺少超額訊號,指出資料庫中資料表中的特定 DateTime 資料行不一定會導致在專案稍後反復專案中解譯時發生錯誤。

在不同 的非正式問卷中看到的常見策略。以 NET 為基礎的應用程式是想要一律以通用 (GMT) 時程表示的日期。 我說「想要」,因為這不一定可行。 透過 Web 服務序列化具有 DateTime 成員變數的類別時,就會發生案例。 原因是 DateTime 數值型別會對應至 XSD:DateTime 類型 (,因為預期) ,而 XSD 類型會容納代表任何時區的時間點。 我們稍後將討論 XML 案例。 更有趣的是,這些專案的良好百分比實際上並未達到其目標,而且會將日期資訊儲存在伺服器時區中,而不會實現。

在這些情況下,有趣的事實是測試人員未看到時間轉換問題,因此沒有人注意到應該將本機日期資訊轉換為 UCT 時間的程式碼失敗。 在這些特定情況下,資料稍後會透過 XML 序列化,並已正確轉換,因為日期資訊是在電腦本機時間開始。

讓我們看看一些 無法運作的程式碼

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM") 'date assignment
d.ToUniversalTime()

上述程式會採用變數 d 中的值,並將它儲存至資料庫,預期預存的值代表時間的 UCT 檢視。 這個範例會辨識 Parse 方法會在當地時間轉譯結果,除非某些非預設文化特性是當做剖析系列方法的選擇性引數使用。

先前顯示的程式碼實際上無法將 DateTime 變數 d 中的值轉換成第三行的通用時間,因為範例違反 Rule #4 (DateTime 類別上的方法不會轉換基礎值) 。 注意:此程式碼是在已測試的實際應用程式中看到。

其傳遞方式為何? 涉及的應用程式能夠成功比較預存日期,因為在測試期間,所有資料都來自設定為相同時區的電腦,因此規則 #1 已滿足 (所有要比較和計算的日期都會當地語系化為相同的時區檢視點) 。 此程式碼中的 Bug 是難以找出的種類,這是執行但不會執行任何 (提示的語句:範例中的最後一個語句是寫入的無作業) 。

最佳做法 #2

測試時,請檢查儲存的值是否代表您想要在時區中想要的時間點值

修正程式碼範例很簡單:

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM").ToUniversalTime()

由於與 DateTime 實數值型別相關聯的計算方法永遠不會影響基礎值,而是傳回計算的結果,因此程式必須記得儲存轉換的值 (,當然) 。 接下來,我們將檢查即使這種看似適當的計算在涉及日光節約時間的特定情況下,仍無法達到預期的結果。

執行計算

第一眼,System.DateTime 類別隨附的計算函式非常有用。 支援將間隔新增至時間值、對時間值執行算術,甚至將 .NET 時間值轉換成適用于 Win32® API 呼叫的對應實值型別,以及 OLE Automation 呼叫。 查看圍繞 DateTime 類型的支援方法,可回想一下 MS-DOS® 和 Windows® 在一段時間內處理時間和時間戳記的不同方式。

所有這些元件仍存在於作業系統的各個部分,與 Microsoft 維護的回溯相容性需求相關。 對程式設計人員而言,這表示如果您要移動代表檔案、目錄上時間戳記的資料,或執行涉及日期和 DateTime 值的 COM/OLE Interop,則必須很熟悉處理 Windows 中不同時間世代之間的轉換。

不要再次遇到問題

假設您已採用「我們會將所有專案儲存在 UCT 時間」策略中,假設可避免需要儲存時區位移 (的額外負荷,以及可能是以使用者眼檢視的時區檢視,例如太平洋標準時間或 PST) 。 使用 UCT 時間執行計算有數個優點。 其中的主要在於,當以通用時程表示時,每天都有固定長度,而且沒有要處理的時區位移。

如果您意外地看到一天可以有不同的長度,請注意,在任何允許日光節約時間的時區中,一年中的兩天 (通常) ,天數會有不同的長度。 因此,即使您使用當地時間值,例如太平洋標準時間 (PST) ,如果您嘗試並將時間範圍新增至特定的 DateTime 實例值,您可能不會得到您認為應該新增的間隔會帶您超過日光節約時間開始或結束日期的變更時間。

讓我們看看無法在美國的太平洋時區運作的程式代碼範例

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.AddHours(3.0)
' - displays 10/26/2003 03:00:00 AM – an ERROR!
MsgBox(d.ToString)

從此計算顯示的結果,在初次流覽時看起來可能正確;不過,在 2003 年 10 月 26 日,上午 1:59 PST 之後的一分鐘,日光節約時間變更就會生效。 正確的答案應該是 10/26/2003,上午 02:00:00,因此根據當地時間值計算無法產生正確的結果。 但是,如果我們回頭看看規則 #3,似乎有一個令人期待,但我們沒有。 讓我們直接在時區中使用 加/減 方法的特殊案例,以慶祝日光節約時間。

最佳做法 #3

撰寫程式碼時,如果您需要執行 DateTime 計算, (加/減) 代表練習日光節約時間的時區值,請小心。 可能會產生非預期的計算錯誤。 相反地,將當地時間值轉換成通用時間、執行計算,然後轉換回以達到最大精確度.

修正此中斷的程式碼很簡單:

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.ToUniversalTime().AddHours(3.0).ToLocalTime()
' - displays 10/26/2003 02:00:00 AM – Correct!
MsgBox(d.ToString)
  

可靠地新增時間範圍最簡單的方式,就是將本機時間型值轉換成通用時間、執行計算,然後將值轉換回。

排序日期時間方法

在本文中,會討論不同的 System.DateTime 類別方法。 當基礎實例代表當地時間時,有些則代表通用時間,而有些則完全不需要基礎實例時產生正確的結果。 此外,有些與時區完全無關, (例如 AddYearAddMonth) 。 為了簡化對最常遇到的 DateTime 支援方法背後的假設整體瞭解,下表提供。

若要讀取資料表,請考慮開始 (輸入) 和結束 (傳回的值) 觀點。 在所有情況下,呼叫方法的結束狀態都會由 方法傳回。 不會對基礎資料實例進行轉換。 也會提供描述例外狀況或實用指引的注意事項。

方法名稱 開始觀點 結束點 警示
ToUniversalTime 當地時間 UTC 請勿在已經代表通用時間的 DateTime 實例上呼叫
ToLocalTime UTC 當地時間 請勿在已經代表當地時間的 DateTime 實例上呼叫
ToFileTime 當地時間   方法會傳回 INT64,代表 Win32 檔案時間 (UCT 時間)
FromFileTime   當地時間 靜態方法— 不需要實例。 接受 INT64 UCT 作為輸入時間
ToFileTimeUtc

僅限 (V1.1)

UTC   方法會傳回 INT64,代表 Win32 檔案時間 (UCT 時間)
FromFileTimeUtc

僅限 (V1.1)

  UTC 方法會將 INT64 Win32 檔案時間轉換為 DateTime UCT 實例
Now   當地時間 靜態方法— 不需要實例。 會傳回 DateTime,代表本機電腦時間中的目前時間
UtcNow   UTC 靜態方法— 不需要實例
IsLeapYear 當地時間   會傳回 Boolean,指出本地時間實例的年份部分是否為閏年,則為 true。
今天   當地時間 靜態方法— 不需要實例。 會傳回 DateTime 設定為本機電腦時間目前當天的午夜。

XML 的特殊案例

我最近交談的數個人有透過 Web 服務序列化時間值的設計目標,讓代表 DateTime 的 XML 會格式化為 GMT (例如零位移) 。 雖然我聽過各種原因,包括只將欄位剖析為文字字串,以在用戶端中顯示,以保留伺服器上存在的「儲存在 UCT」假設中,對 Web 服務的呼叫端而言,我從未確信有好的理由可以控制網路上的封送處理格式到這個程度。 原因為何? 只是因為 DateTime 類型的 XML 編碼非常適合用來表示即時,而內建于.NET Framework的 XML 序列化程式會執行與時間值相關聯之序列化和還原序列化問題的精細作業。

此外,它發現強制System.XML。在 .NET 中,無法以 GMT 編碼日期值的序列化程式,至少不能在今天。 身為程式設計人員、設計工具或專案經理,您的工作接著會變成確定正在應用程式中傳遞的資料會以最少的成本正確執行。

我在研究中討論的數個群組已採用定義特殊類別的策略,並撰寫自己的 XML 序列化程式,以便完全控制網路上的 DateTime 值在其 XML 中的外觀。 雖然我讓開發人員在進入此承諾時,不斷努力,但請放心,單獨處理日光節約時間和時區轉換問題的細微差異,應該讓管理員說出「沒有方法」,特別是當.NET Framework中提供的機制已正確序列化時間值時。

您只需要注意一個訣竅,而且身為設計工具,您必須瞭解這一點,並遵守規則 (請參閱規則 #5) 。

無法運作的程式代碼:

讓我們先使用 DateTime 成員變數來定義簡單的 XML 類別。 為了完整起見,此類別是本文稍後所說明建議方法的簡化對等專案。

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable()> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

現在,讓我們使用此類別將一些 XML 寫入檔案。

' write out to the file
Dim t As Xml.XmlTextWriter
Dim ser As XmlSerializer
Dim tt As New timeTest ' a class that has a DateTime variable
' set the fields in your class
tt.timeVal = DateTime.Parse("12/12/2003 12:01:02 PM")
tt.timeVal = tt.TimeVal.ToUniversalTime()

' get a serializer for the root type, and serialize this UTC time
ser = New XmlSerializer(GetType(timeTest))
t = New Xml.XmlTextWriter("c:\timetest.xml", System.Text.Encoding.UTF8)
ser.Serialize(t, tt)
t.Close()
t = Nothing
tt = Nothing

當此程式碼執行時,序列化至輸出檔案的 XML 包含 XML DateTime 標記法,如下所示:

<timeVal>2003-12-12T20:01:02.0000000-08:00</timeVal> 

這是錯誤:以 XML 編碼的值會關閉八小時! 由於這是目前機器的時區位移,因此我們應該可疑。 查看 XML 本身,日期正確,而 20:01:02 日期會對應至倫敦的下午時間,但以倫敦為基礎的時鐘的位移部分不正確。 當 XML 看起來像倫敦時間時,位移也應該代表無法達成此程式碼的倫敦觀點。

XML 序列化程式一律假設要序列化的 DateTime 值代表本機電腦時間,因此它會套用電腦本機時區位移作為編碼 XML 時間的位移部分。 當我們將這個還原序列化到另一部電腦上時,原始位移會從要剖析的值減去,並加入目前電腦的時區位移。

當我們從本機時間開始時,序列化的結果 (編碼為 XML DateTime,後面接著解碼為本機電腦時間) 一律正確,但只有在序列化起始的 DateTime 值代表序列化開始時的本地時間。 在此中斷的程式碼範例中,我們已將 timeVal 成員變數中的 DateTime 值調整為 UCT 時間,因此當我們序列化和還原序列化時,結果會依等於原始電腦的時區位移時數關閉。 這是錯誤的。

最佳做法 #4

測試時,計算您預期在 XML 字串中看到的值,該字串會使用所測試時間點的機器本機時間檢視進行序列化。 如果序列化資料流程中的 XML 不同,請記錄錯誤!

修正此程式碼很簡單。 將呼叫 ToUniversalTime () 的行批註化。

最佳做法 #5

撰寫程式碼以序列化具有 DateTime 成員變數的類別時,這些值必須代表當地時間。 如果它們不包含當地時間,請在任何序列化步驟之前加以調整,包括傳遞或傳回 Web 服務中包含 DateTime 值的型別。

類別編解碼器 Quandary

我們稍早查看了公開 DateTime 屬性的相當未診斷類別。 在該類別中,我們只會序列化在 DateTime 中儲存的內容,而不論值是否代表本機或通用時間觀點。 讓我們看看更複雜的方法,讓程式設計人員選擇過度選擇其所需的時區假設,同時一律會正確序列化。

撰寫具有 DateTime 類型成員變數的類別時,程式設計人員可以選擇將成員變數設為公用或撰寫屬性邏輯,以使用 get/set 作業包裝成員變數。 選擇將類型設為 public 有數個缺點,在 DateTime 類型的情況下,可能會產生不在類別開發人員控制項下的結果。

使用我們到目前為止學到的內容,請考慮改為為每個 DateTime 類型提供兩個屬性。

下列範例說明管理 DateTime 成員變數的建議方法:

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable(), _
    EditorBrowsable(EditorBrowsableState.Advanced)> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal.ToLocalTime()
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value.ToUniversalTime()
            timeValSpecified = True
        End Set
    End Property

    <XmlIgnore()> _
    Public Property timeValUTC() As DateTime
        Get
            timeValUTC = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

此範例已更正,相當於先前的類別序列化範例。 在這兩個類別範例中 (這個範例和先前的) ,類別是使用下列架構描述的實作:

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema id="Timetester" 
     targetNamespace="http://tempuri.org/Timetester.xsd"
     elementFormDefault="qualified"
     xmlns="http://tempuri.org/Timetester.xsd"
     xmlns:mstns="http://tempuri.org/Timetester.xsd"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">

     <xs:element name="timeTest" type="timeTestDef"/>
     <xs:complexType name="timeTestDef">
      <xs:sequence>
         <xs:element name="timeVal" type="xs:dateTime"/>
      </xs:sequence>
     </xs:complexType>
</xs:schema>

在此架構中,以及任何類別實作中,我們會定義代表選擇性時間值的成員變數。 在建議的範例中,我們已提供兩個屬性與 getter 和 setter,一個用於通用時間,另一個用於當地時間。 您在程式碼中看到的角括弧屬性會指示 XML 序列化程式使用本機時間版本進行序列化,而且通常會使類別實作產生架構相容的輸出。 若要讓類別在實例中未設定任何值時,適當地處理無運算式的選擇性缺少,屬性 setter 中的 timeValSpecified 變數和相關聯的邏輯會控制 XML 元素是否在序列化時程表示。 這個選擇性行為會利用序列化子系統中設計來支援選擇性 XML 內容的功能。

使用這個方法來管理 .NET 類別中的 DateTime 值可提供您最佳的兩個世界:您可以根據通用時間取得儲存體存取權,以便計算正確,並取得當地時間檢視的適當序列化。

最佳做法 #6

撰寫程式碼時,請將 DateTime 成員變數設為私用,並提供兩個屬性,以在本機或通用時間中操作您的 DateTime 成員。 藉由控制 getter 和 setter 中的邏輯,將私人成員中的儲存體偏差為 UCT 時間。 將 XML 序列化屬性新增至本機時間屬性宣告,以確保本機時間值是序列化 (請參閱範例) 。

此方法的注意事項

在私人成員變數內以通用時間管理 DateTime 的建議方法是合理的,如同提供雙重屬性來允許程式碼員處理最熟悉的時間版本的建議方法。 開發人員使用這個或任何其他方法向程式公開任何當地時間的問題,會繼續成為日光節約時間的 25 小時問題。 對於使用 CLR 1.0 和 1.1 版的程式,這將會持續發生問題,因此您必須注意程式是否屬於此特殊案例, () 所表示的新增或遺漏時數,並手動調整。 對於無法容忍每年一小時的問題視窗,目前的建議是將您的日期儲存為字串或其他一些自我管理的方法。 (Unix 長整數是不錯的選項。)

針對即將發行的 Visual Studio® 程式碼 「Whidbey」) 中提供的 CLR 2.0 版 (,請注意 DateTime 是否包含當地時間或通用時間值新增至.NET Framework。 此時,建議的模式將會繼續運作,但對於透過 UTC 屬性與成員變數互動的程式,將會消除遺漏/額外小時期間內的這些錯誤。 基於這個理由,目前強烈建議使用雙重屬性撰寫程式碼的最佳做法,讓您的程式完全移轉至 CLR 2.0 版。

處理日光節約時間

當我們準備關閉並離開 DateTime 值的編碼和測試實務主題時,您仍需要瞭解一個特殊案例。 此案例牽涉到圍繞日光節約時間和每年重複一小時問題的模棱兩可。 此問題主要是影響從使用者輸入收集時間值的應用程式。

對於國家/地區計數多數的使用者而言,此案例很簡單,因為在大部分的國家/地區,不會練習日光節約時間。 但是,對於在受影響的程式中的大多數 (,您所有需要處理時間的應用程式都必須在進行日光節約) 的地方表示,或以練習日光節約) 的地方表示的所有應用程式,您必須知道此問題存在並加以考慮。

在練習日光節約時間的世界中,每個月都有一小時的時間,而彈簧看起來有點花花。 在時鐘時間從標準時間移至日光節約時間的晚上,時間會提前一小時。 這會在彈簧中發生。 在年份的下降中,在一天晚上,當地時間時鐘會跳回一小時。

在這幾天,您可能會遇到一天長度為 23 或 25 小時的條件。 因此,如果您要從日期值新增或減去時間範圍,而範圍會跨越時鐘切換的這個奇怪時間點,您的程式碼必須進行手動調整。

對於使用 DateTime.Parse () 方法根據特定日期和時間的使用者輸入來計算 DateTime 值的邏輯,您必須偵測特定值在 23 小時) 上無效 (,而某些值有兩個意義,因為特定小時會在 25 小時) 重複 (。 若要這樣做,您必須知道涉及的日期,並尋找這些小時。 在使用者結束用來輸入日期的欄位時,剖析和重新顯示解譯的日期資訊可能很有用。 根據規則,請避免使用者在其輸入中指定日光節約時間。

我們已涵蓋時間範圍計算的最佳做法。 藉由將當地時間檢視轉換成執行計算前的通用時間,即可超過時間精確度的問題。 較難管理的案例是與剖析在彈簧和落下這個神奇時點期間發生的使用者輸入相關的模棱兩可案例。

目前沒有任何方法可以剖析代表使用者時間檢視的字串,並讓它正確地指派通用時間值。 原因是體驗日光節約時間的人員不會在時區為 Greenwich Mean Time 的位置中生存。 因此,完全有可能有人在美國東部的美國東部輸入值,例如 「Oct 26, 2003 01:10:00 AM」。

在這個特定的上午 2:00,本機時鐘會重設為上午 1:00,建立 25 小時。 由於上午 1:00 到上午 2:00 之間的所有時鐘時間值都會在該特定早上發生兩次,至少在美國和加拿大大部分。 電腦實際上無法知道哪一個上午 1:10—在切換之前發生的,或日光節約時間切換之後 10 分鐘發生的電腦。

同樣地,您的程式必須處理在特定上午 2:10 時,在 springtime 中發生的問題。 原因是在該特定上午 2:00,當地時鐘的時間突然變更為上午 3:00。 整個 2:00 小時永遠不會在此 23 小時發生。

您的程式必須處理這些情況,可能是當您偵測到模棱兩可時提示使用者。 如果您未從使用者收集日期時間字串並加以剖析,則您可能沒有這些問題。 需要判斷特定時間是否落在日光節約時間的程式可以使用下列專案:

Timezone.CurrentTimeZone.IsDaylightSavingTime (DateTimeInstance)

DateTimeInstance.IsDaylightSavingTime

最佳做法 #7

測試時,如果您的程式接受指定日期和時間值的使用者輸入,請務必在 「spring-ahead」、「後援」23 和 25 小時天測試資料遺失。 此外,請務必測試在某個時區的電腦上收集的日期,並儲存在另一個時區的電腦上。

格式化和剖析User-Ready值

對於需要使用者日期和時間資訊且需要將此使用者輸入轉換成 DateTime 值的程式,架構會支援以特定方式格式化的剖析字串。 一般而言, DateTime.ParseParseExact 方法對於將包含日期和時間的字串轉換成 DateTime 值很有用。 相反地,ToStringToLongDateString、ToLongTimeStringToShortDateString 和 ToShortTimeString方法對於將 DateTime 值轉譯為人類可讀取的字串都很有用。

影響剖析的兩個主要問題是文化特性和格式字串。 DateTime 常見問題 (常見問題) 涵蓋文化特性的基本問題,因此我們將著重于影響 DateTime 剖析的格式字串最佳做法。

將 DateTime 轉換為字串的建議格式字串如下:

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'Z' — 適用于 UCT 值

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'zzz' - 針對本機值

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff' — 適用于抽象時間值

如果您想要取得與 XML DateTime 類型規格相容的輸出,這些字串值會傳遞至 DateTime.ToString 方法。 引號可確保電腦上的本機日期時間設定不會覆寫您的格式設定選項。 如果您需要指定不同的版面配置,您可以傳遞其他格式字串以取得相當彈性的日期轉譯功能,但您必須小心只使用 Z 標記法來轉譯 UCT 值的字串,並使用 zzz 標記法進行當地時間值。

剖析字串並將其轉換成 DateTime 值,可以使用 DateTime.Parse 和 ParseExact 方法來完成。 在大部分的情況下,Parse 已足夠,因為 ParseExact 要求您提供自己的 Formatter 物件實例。 剖析相當有功能且有彈性,而且可以精確地轉換包含日期和時間的大部分字串。

最後,請務必只在將執行緒的 CultureInfo.InvariantCulture 設定為CultureInfo之後,才呼叫 Parse 和 ToString 方法。

未來考慮

您目前無法輕鬆地使用 DateTime.ToString 執行的一件事是將 DateTime 值格式化為任意時區。 這項功能會考慮用於.NET Framework的未來實作。 如果您需要能夠判斷字串 「12:00:00 EST」 相當於 「11:00:00 EDT」,您必須自行處理轉換和比較。

DateTime.Now () 方法的問題

處理名為 Now的方法時,有數個問題。 針對閱讀本文的 Visual Basic 開發人員,這也適用于 Visual Basic Now 函式。 經常使用 Now 方法的開發人員知道通常用來取得目前時間。 Now 方法傳回的值位於目前的電腦時區內容中,而且無法視為不可變的值。 常見的做法是在機器之間將儲存或傳送的時間轉換成通用 (UCT) 時間。

當日光節約時間可能時,您應該避免一個程式碼撰寫做法。 請考慮下列程式碼,以引入難以偵測的錯誤:

Dim timeval As DateTime
timeval = DateTime.Now().ToUniversalTime()  

如果在日光節約時間切換期間發生的額外一小時內呼叫,則執行此程式碼所產生的值將會關閉一小時。 (這只適用于練習日光節約時間的時區中的機器。) 因為額外的小時落在該位置,例如上午 1:10:00 發生兩次,所以傳回的值可能不符合您想要的值。

若要修正此問題,最佳做法是呼叫 DateTime.UtcNow () 而不是呼叫 DateTime.Now,然後轉換成通用時間。

Dim timeval As DateTime
timeval = DateTime.UtcNow()  

此程式碼一律會有適當的 24 小時天檢視方塊,然後可以安全地轉換為當地時間。

最佳做法 #8

當您撰寫程式碼並想要儲存以通用時程表示的目前時間時,請避免呼叫 DateTime.Now () ,然後轉換為通用時間。 相反地,直接呼叫 DateTime.UtcNow 函式。

注意:如果您要序列化包含 DateTime 值的類別,請確定序列化的值不代表通用時間。 XML 序列化將不支援 UCT 序列化,直到 Visual Studio 的 Whidbey 版本為止。

幾個已知的小額外專案

有時候,當您開始深入探索 API 的一部分時,您會發現隱藏的 Gem,這可協助您達成目標,但如果您未告知它,則不會在日常旅遊中發現。 .NET 中的 DateTime 數值型別有數個這類 Gem,可協助您達到更一致的通用時間使用。

第一個是在System.Globalization命名空間中找到的DateTimeStyles列舉。 列舉控制 DateTime.Parse () 和 ParseExact 函式的行為,這些函式可用來將使用者指定的輸入和其他形式的輸入字串表示轉換為 DateTime 值。

下表醒目提示 DateTimeStyles 列舉所啟用的一些功能。

列舉常數 目的 警示
AdjustToUniversal 當當做 Parse 或 ParseExact 方法的一部分傳遞時,此旗標會導致傳回的值成為通用時間。 檔模棱兩可,但這適用于 Parse 和 ParseExact。
NoCurrentDateDefault 隱藏以無日期元件剖析字串的假設,將傳回的 DateTime 值是目前日期的時間。 如果使用此選項,傳回的 DateTime 值是 1 年 1 月 1 日指定的時間。
AllowWhiteSpaces

AllowTrailingWhite

AllowLeadingWhite

AllowInnerWhite

這些選項都會在剖析的日期字串前面、後置和中間啟用新增空格的容錯。

System.Timezone類別中找到其他有趣的支援函式。 如果您想要偵測日光節約時間是否會影響 DateTime 值,或您想要以程式設計方式判斷本機電腦的目前時區位移,請務必檢查這些值。

結論

.NET Framework DateTime 類別提供完整的介面,可撰寫處理時間的程式。 瞭解處理 類別的細微差異,超出您可以從 Intellisense® 擷取的內容。 在此,我們涵蓋了處理日期和時間之程式碼撰寫和測試程式的最佳做法。 祝各位編碼程式愉快!