.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
   DateTime メソッドの並べ替え
XML の特殊なケース
   ベスト プラクティス #4
   ベスト プラクティス #5
クラス コーダ Quandary
   ベスト プラクティス #6
夏時間の処理
   ベスト プラクティス #7
User-Ready値の書式設定と解析
   今後の考慮事項
DateTime.Now() メソッドに関する問題
   ベスト プラクティス #8
あまり知られていないエクストラのカップル
まとめ

バックグラウンド

多くのプログラマは、日付と時刻の情報を含むデータを正確に格納して処理する必要がある割り当てに遭遇します。 一見すると、共通言語ランタイム (CLR) の DateTime データ型は、これらのタスクに最適なように見えます。 しかし、プログラマにとっては珍しいことではありませんが、おそらくテスターは、プログラムが正しい時間値を追跡できない場合に遭遇します。 この記事では、DateTime に関連するロジックに関連する問題に焦点を当て、DateTime 情報をキャプチャ、格納、取得、送信するプログラムを作成およびテストするためのベスト プラクティスを明らかにします。

とにかく DateTime とは

NET Framework クラス ライブラリのドキュメントを見ると、"CLR System.DateTime 値型は、0001 年 1 月 1 日午前 1 時から 9999 年 12 月 31 日午後 11 時 59 分 59 分までの日付と時刻を表す" ことがわかります。さらに、驚くべきことに、DateTime 値は特定の時点で瞬時に表され、一般的な方法は、協定世界時 (UCT) (グリニッジ標準時 (GMT) と呼ばれる) にポイントインタイム値を記録することです。

一見すると、プログラマは、DateTime 型が、ビジネス アプリケーションなどの現在のプログラミングの問題で発生する可能性が高い時刻値を格納するのに非常に適していることを発見します。 この自信を持って、多くの疑いのないプログラマーはコーディングを始め、時間を必要なだけ学ぶことができることを確信しています。 この "learn-as-you-go" アプローチを使用すると、いくつかの問題が発生する可能性があるため、それらを特定し始めましょう。 ドキュメントの問題から、プログラムの設計に組み込む必要がある動作まで多岐にまたがっています。

System.DateTime の V1.0 と 1.1 のドキュメントでは、疑いのないプログラマを軌道に乗せることができるいくつかの一般化が行われます。たとえば、現在もドキュメントでは、DateTime クラスで見つかったメソッドとプロパティでは、計算や比較を行うときに、値がローカル コンピューターのローカル タイム ゾーンを表すという前提が常に使用されています。 GMT を前提とする特定の種類の日付と時刻の計算と、ローカル タイム ゾーン ビューを想定する他の種類があるため、この一般化は真実ではないことが判明します。 これらの領域については、この記事の後半で説明します。

そのため、最初にコードを正しく機能させるために役立つ一連のルールとベスト プラクティスを概説して、DateTime 型を調べてみましょう。

ルール

  1. DateTime インスタンスの計算と比較は、比較または使用されるインスタンスが同じタイム ゾーンの観点からのポイントの表現である場合にのみ意味があります。
  2. 開発者は、何らかの外部メカニズムを使用して、DateTime 値に関連付けられているタイム ゾーン情報を追跡する責任があります。 通常、これは、DateTime 値型を格納するときにタイム ゾーン情報を記録するために使用する別のフィールドまたは変数を定義することによって実現されます。 このアプローチ (DateTime 値と共にタイム ゾーンの感覚を格納する) は最も正確であり、プログラムのライフサイクルのさまざまな時点で異なる開発者が常に DateTime 値の意味を明確に理解できるようにします。 もう 1 つの一般的な方法は、すべての時間値が特定のタイム ゾーン コンテキストに格納されることを設計の "ルール" にすることです。 この方法では、タイム ゾーン コンテキストのユーザーのビューを保存するために追加のストレージは必要ありませんが、時間値が誤って解釈されたり、ルールを認識していない開発者によって誤って保存されたりするリスクが生じます。
  3. コンピューターのローカル時刻を表す値に対して日付と時刻の計算を実行すると、常に正しい結果が得られるとは限りません。 夏時間を練習するタイム ゾーン コンテキストで時間値に対して計算を実行する場合は、日付の算術計算を実行する前に、値を汎用時刻表現に変換する必要があります。 操作の特定の一覧と適切なタイム ゾーン コンテキストについては、「DateTime メソッドの並べ替え」セクションの表を参照してください。
  4. DateTime 値のインスタンスに対する計算では、インスタンスの値は変更されないため、 MyDateTime.ToLocalTime() の呼び出しでは DateTime のインスタンスの値は変更されません。 Date (Visual Basic® では) クラスと DateTime クラス (.NET CLR 内) に関連付けられているメソッドは、計算または操作の結果を表す新しいインスタンスを返します。
  5. .NET Framework バージョン 1.0 および 1.1 を使用する場合は、UCT 時間を表す DateTime 値を送信しないでくださいSystem.XML。シリアル化。 これは、Date、Time、および DateTime の値に対して行われます。 System.DateTime を含む XML への Web サービスおよびその他の形式のシリアル化の場合は、常に DateTime 値の値が現在のコンピューターのローカル時刻を表していることを確認してください。 シリアライザーは、GMT でエンコードされた XML スキーマ定義の DateTime 値 (オフセット値 = 0) を適切にデコードしますが、ローカル コンピューターの時刻ビューポイントにデコードします。
  6. 一般に、タイムアウトの測定、算術演算の実行、異なる DateTime 値の比較など、絶対経過時間を処理する場合は、可能な限りユニバーサル時間値を使用して、タイム ゾーンや夏時間の影響を受けることなく、可能な限り最高の精度を得るようにする必要があります。
  7. スケジュールなどのユーザー向け概念の概要を扱う場合、ユーザーの観点から見ると毎日 24 時間あると安全に想定できます。ローカル時刻に算術演算 et cetera を実行して Rule #6 に対抗しても問題ありません。

この記事全体を通して、この単純なルールの一覧は、日付を処理するアプリケーションを作成およびテストするための一連のベスト プラクティスの基礎として機能します。

今では、何人かのユーザーが既にコードを調べ、この記事の目的である "Oh darn, it's not do what i expected to do"(私が期待したことをやっていない)" と言っています。 ここまで読んでからエピファニーを持っていない人のために、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 メソッド ファミリの省略可能な引数として使用されない限り、 Parse メソッドが結果をローカル時刻でレンダリングすることを認識します。

前に示したコードでは、実際には DateTime 変数 d の値を 3 行目の汎用時刻に変換できません。これは、サンプルが Rule #4 に違反しているためです (DateTime クラスのメソッドは基になる値を変換しません)。 注: このコードは、テストされた実際のアプリケーションで確認されました。

どのように成功しましたか? 関係するアプリケーションは、テスト中に、すべてのデータが同じタイム ゾーンに設定されたマシンから送信されたため、ルール #1 が満たされた (比較および計算されるすべての日付が同じタイム ゾーンの観点にローカライズされているため) 格納された日付を正常に比較できました。 このコードのバグは、見つけにくい種類です。実行されるが何も行わないステートメントです (ヒント: この例の最後のステートメントは、書かれているように no-op です)。

ベスト プラクティス #2

テスト時にチェック、格納されている値が、目的のタイム ゾーンで意図したポイントインタイム値を表していることを確認します

コード サンプルの修正は簡単です。

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

DateTime 値型に関連付けられている計算メソッドは基になる値に影響を与えることはなく、代わりに計算結果を返すので、プログラムは変換された値を格納することを忘れないようにする必要があります (もちろん、これが必要な場合)。 次に、この一見適切な計算でも、夏時間を含む特定の状況で期待される結果を達成できない可能性がある方法を調べます。

計算の実行

一見すると、System.DateTime クラスに付属する計算関数は非常に便利です。 時間値に間隔を追加したり、時間値に対して算術演算を実行したり、.NET 時刻値を Win32® API 呼び出しに適した対応する値型に変換したり、OLE オートメーション呼び出しを行ったりするためにもサポートされています。 DateTime 型を囲むサポート メソッドを見ると、MS-DOS® と Windows® が長年にわたって時間とタイムスタンプを処理するために進化してきたさまざまな方法を懐かしく思い出します。

これらのコンポーネントがすべてオペレーティング システムのさまざまな部分にまだ存在するという事実は、Microsoft が維持する下位互換性要件に関連しています。 プログラマにとっては、ファイル、ディレクトリのタイムスタンプを表すデータを移動する場合、または date 値と DateTime 値を含む COM/OLE 相互運用を実行する場合は、Windows に存在するさまざまな世代間の変換を処理する際に習熟する必要があることを意味します。

二度とだまされない

たとえば、タイム ゾーン オフセットを格納する必要があるオーバーヘッドを回避するために 、"UCT 時間にすべてを格納する" 戦略を採用したとします (太平洋標準時や PST など、ユーザーが目で見たタイム ゾーンのビュー)。 UCT 時間を使用して計算を実行するには、いくつかの利点があります。 その中で最も長い点は、世界時で表される場合、毎日の長さが固定され、対処するタイム ゾーン オフセットが存在しないという事実です。

1 日の長さが異なる可能性があることを読んで驚いた場合は、夏時間が可能なタイム ゾーンでは、1 年の 2 日間 (通常は) 日数の長さが異なっていることに注意してください。 そのため、太平洋標準時 (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 分後に、夏時間の変更が有効になりました。 正解は 2003 年 10 月 26 日午前 2:00:00 であるため、現地時間の値に基づくこの計算で正しい結果が得られなかった。 しかし、ルール #3 を振り返ると、矛盾しているように見えますが、矛盾はありません。 夏時間を祝うタイム ゾーンで Add/Subtract メソッドを使用する特別なケースと呼びましょう。

ベスト プラクティス #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)
  

スパンを確実に追加する最も簡単な方法は、ローカル時刻ベースの値をユニバーサルタイムに変換し、計算を実行してから、値を元に戻す方法です。

DateTime メソッドの並べ替え

この記事では、さまざまな System.DateTime クラス メソッドについて説明します。 基になるインスタンスが現地時刻を表す場合は正しい結果が得られるものもあれば、ユニバーサル時刻を表すものもあれば、基になるインスタンスをまったく必要としないものもあります。 さらに、タイム ゾーン ( AddYearAddMonth など) に完全に依存しないものもあります。 最も一般的に見られる DateTime サポート メソッドの背後にある前提の全体的な理解を簡略化するために、次の表を示します。

テーブルを読み取る場合は、開始 (入力) と終了 (戻り値) の観点を検討してください。 いずれの場合も、メソッドを呼び出す終了状態は メソッドによって返されます。 データの基になるインスタンスへの変換は行われません。 例外や役に立つガイダンスを説明する注意事項も示されています。

メソッド名 開始ビューポイント 終了ビューポイント 注意事項
ToUniversalTime ローカル時刻 UTC 既にユニバーサル時刻を表す DateTime インスタンスで を呼び出さないでください
ToLocalTime UTC ローカル時刻 ローカル時刻を既に表す DateTime インスタンスで を呼び出さないでください
ToFileTime ローカル時刻   メソッドは、Win32 ファイル時刻 (UCT 時間) を表す INT64 を返します
FromFileTime   ローカル時刻 静的メソッド- インスタンスは必要ありません。 入力として INT64 UCT 時間を取ります
ToFileTimeUtc

(V1.1 のみ)

UTC   メソッドは、Win32 ファイル時刻 (UCT 時刻) を表す INT64 を返します
FromFileTimeUtc

(V1.1 のみ)

  UTC メソッドは INT64 Win32 ファイル時刻を DateTime UCT インスタンスに変換します
Now   ローカル時刻 静的メソッド- インスタンスは必要ありません。 ローカル コンピューター時刻の現在の時刻を表す DateTime を返します。
UtcNow   UTC 静的メソッド - インスタンスは必要ありません
IsLeapYear ローカル時刻   現地時刻インスタンスの year 部分が閏年の場合に true を示すブール値を返します。
本日   ローカル時刻 静的メソッド- インスタンスは必要ありません。 ローカル コンピューター時刻の現在の日の午前 0 時に設定された DateTime を返します。

XML の特殊なケース

私が最近話した何人かの人々は、DateTimeを表すXMLがGMT(ゼロオフセットなど)でフォーマットされるように、Webサービス上で時間値をシリアル化するという設計目標を持っていました。 クライアントに表示するためのテキスト文字列としてフィールドを解析するだけの希望から、サーバー上に存在する "UCT に格納されている" 前提を Web サービスの呼び出し元に保持したいなど、さまざまな理由を聞いたことがありますが、ワイヤー上のマーシャリング形式をこの程度に制御する正当な理由があるとは確信していません。 なぜですか? 単純に、DateTime 型の XML エンコードはインスタント イン タイムを表すのに完全に適しており、.NET Frameworkに組み込まれている XML シリアライザーは、時間値に関連するシリアル化と逆シリアル化の問題を管理する細かいジョブを実行するためです。

また、System.XMLを強制することが判明した。ネットワーク上の GMT で日付値をエンコードするシリアル化シリアライザーは、少なくとも今日は .NET では使用できません。 プログラマ、デザイナー、またはプロジェクト マネージャーは、アプリケーションで渡されるデータが最小限のコストで正確に実行されるようにします。

この論文で取り上げてきたいくつかのグループでは、特別なクラスを定義し、独自の XML シリアライザーを記述して、ワイヤー上の DateTime 値が XML 内でどのような外観であるかを完全に制御できるようにする戦略を採用しました。 私は開発者がこの勇敢な取り組みへの飛び込みを行うときに持っている引っ越しに感心しますが、夏時間とタイム ゾーン変換の問題だけに対処する微妙な違いは、特に.NET Frameworkで提供されるメカニズムが既に時間値をシリアル化する完全に正確な仕事をする場合に、良いマネージャーが「まさか」と言うべきであることを確信しています。

注意する必要があるトリックは 1 つだけであり、デザイナーとしてこれを理解し、ルールに従う必要があります (「ルール #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 でエンコードされた値が 8 時間オフになっています。 これは現在のマシンのタイム ゾーン オフセットであるため、疑わしいはずです。 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 操作でラップするか選択できます。 型をパブリックにすることを選択すると、DateTime 型の場合、クラス開発者の制御下にない結果が生じる可能性があるいくつかの欠点があります。

これまでに学習したことを使用して、DateTime 型ごとに 2 つのプロパティを提供することを検討してください。

次の例は、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

この例は、前のクラスシリアル化の例と同等の修正です。 両方のクラス例 (この 1 つ前の例) では、クラスは次のスキーマで説明されている実装です。

<?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 の両方で 2 つのプロパティを提供しました。1 つはユニバーサル時刻用、1 つは現地時刻用です。 コードに表示される山かっこで囲まれた属性は、シリアル化にローカル時刻バージョンを使用するように XML シリアライザーに指示し、通常、クラス実装の結果をスキーマ準拠の出力にします。 インスタンスに値が設定されていない場合に、クラスが式の省略可能な不足を適切に処理できるように、 timeValSpecified 変数とプロパティ セッターの関連ロジックは、XML 要素がシリアル化時に表現されるかどうかを制御します。 この省略可能な動作は、オプションの XML コンテンツをサポートするように設計されたシリアル化サブシステムの機能を利用します。

この方法を使用して .NET クラスの DateTime 値を管理すると、両方の世界で最高の結果が得られます。つまり、計算が正確になるようにユニバーサルタイムに基づいてストレージ アクセスを取得し、ローカル時刻ビューを適切にシリアル化できます。

ベスト プラクティス #6

コーディングする場合は、DateTime メンバー変数をプライベートにし、ローカル時刻またはユニバーサル時刻で DateTime メンバーを操作するための 2 つのプロパティを指定します。 ゲッターとセッターのロジックを制御して、プライベート メンバー内のストレージを UCT 時間としてバイアスします。 ローカル時刻のプロパティ宣言に XML シリアル化属性を追加して、ローカル時刻の値がシリアル化されていることを確認します (例を参照)。

このアプローチに関する注意事項

プライベート メンバー変数内でユニバーサル時間で DateTime を管理する推奨されるアプローチは健全です。また、コーデラーが最も快適な時間のバージョンを処理できるように、デュアル プロパティを提供することをお勧めします。 プログラムにローカル時刻を公開するこの方法またはその他の方法を使用している開発者が、夏時間に関する 25 時間の問題であり続ける問題の 1 つ。 これは引き続き CLR バージョン 1.0 と 1.1 を使用するプログラムの問題であるため、プログラムがこの特別なケース (表される時間の追加時間または不足時間) に該当するかどうかに注意し、手動で調整する必要があります。 1 年に 1 時間の問題期間を許容できない場合、現在の推奨事項は、日付を文字列または他の自己管理アプローチとして格納することです。 (Unix の長整数は適切なオプションです)。

CLR バージョン 2.0 ("Whidbey" という名前の Visual Studio® コードの今後のリリースで利用可能) の場合、DateTime にローカル時刻が含まれているか、ユニバーサル時刻値が.NET Frameworkに追加されているかが認識されます。 その時点で、推奨されるパターンは引き続き機能しますが、UTC プロパティを介してメンバー変数と対話するプログラムの場合、欠落/余分な時間期間内のこれらのエラーは排除されます。 このため、プログラムが CLR バージョン 2.0 にクリーンに移行されるように、デュアル プロパティを使用したコーディングのベスト プラクティスが現在強く推奨されています。

夏時間の処理

DateTime 値のコーディングとテストのプラクティスに関するトピックを閉じて残す準備を進める中で、理解する必要がある特別なケースが 1 つ残っています。 このケースには、夏時間を囲むあいまいさと、1 年に 1 時間の繰り返しの問題が含まれます。 この問題は、主に、ユーザー入力から時間値を収集するアプリケーションにのみ影響を与える問題です。

ほとんどの国では夏時間が実施されていないため、このケースは簡単ではありません。 しかし、影響を受けるプログラムの大半に参加しているユーザー (つまり、夏時間を実践する場所で表される、または提供される可能性のある時間に対処する必要があるアプリケーションを持っているすべてのユーザー) は、この問題が存在することを知り、それを考慮する必要があります。

夏時間を実践する世界の地域では、秋と春ごとに1時間があり、時間は一見ヘイワイヤーに行きます。 時刻が標準時から夏時間にシフトする夜、時間は 1 時間先にジャンプします。 これは春に発生します。 1年の秋、ある夜、現地時刻時計は1時間に戻ります。

これらの日には、その日の長さが 23 時間または 25 時間の条件が発生する可能性があります。 したがって、日付値からスパンのスパンを加算または減算していて、そのスパンがクロックが切り替わるこの奇妙な時点を越える場合は、コードで手動で調整する必要があります。

DateTime.Parse() メソッドを使用して特定の日付と時刻のユーザー入力に基づいて DateTime 値を計算するロジックの場合は、特定の値が有効でないことを検出する必要があります (23 時間の日) には、特定の時間が (25 時間の日に) 繰り返されるため、特定の値には 2 つの意味があります。 これを行うには、関連する日付を把握し、これらの時間を探す必要があります。 ユーザーが日付の入力に使用するフィールドを終了すると、解釈された日付情報を解析して再表示すると便利な場合があります。 原則として、ユーザーが入力に夏時間を指定することは避けてください。

スパン計算のベスト プラクティスについては、既に説明しました。 計算を実行する前にローカル時刻ビューをユニバーサルタイムに変換することで、時間の精度の問題を乗り越えます。 管理が難しいケースは、春と秋のこの魔法の時間に発生するユーザー入力の解析に関連するあいまいさのケースです。

現在、ユーザーの時間ビューを表す文字列を解析し、ユニバーサル時刻値を正確に割り当てる方法はありません。 その理由は、夏時間を経験する人は、タイム ゾーンがグリニッジ標準時である場所に住まないからです。 したがって、米国の東海岸に住んでいる人が、"2003 年 10 月 26 日午前 1 時 10 分 00 分" のような値で型指定する可能性があります。

この特定の朝の午前 2 時に、ローカル クロックは午前 1 時にリセットされ、25 時間の日が作成されます。 1:00 AM から 2:00 AM までのクロック時間のすべての値は、少なくとも米国とカナダのほとんどの特定の朝に 2 回発生するためです。 コンピューターには、実際には、スイッチの前に発生した午前 1 時 10 分、または夏時間スイッチの 10 分後に発生する午前 1 時 10 分を知る方法がありません。

同様に、プログラムは、特定の朝の午前 2 時 10 分のような時間がない春に発生する問題に対処する必要があります。 その理由は、その特定の朝の2:00に、ローカルクロックの時刻が突然午前3時に変わるということです。 この 23 時間の日は、2:00 時間全体が発生することはありません。

プログラムは、あいまいさを検出したときにユーザーにプロンプトを表示することによって、このようなケースに対処する必要があります。 ユーザーから日付/時刻文字列を収集して解析していない場合は、おそらくこれらの問題はありません。 特定の時刻が夏時間に含まれるかどうかを判断する必要があるプログラムでは、次の機能を使用できます。

Timezone.CurrentTimeZone.IsDaylightSavingTime(DateTimeInstance)

または

DateTimeInstance.IsDaylightSavingTime

ベスト プラクティス #7

テスト時に、プログラムで日付と時刻の値を指定するユーザー入力を受け入れる場合は、"spring-ahead"、"fall-back" 23- and 25 時間日のデータ損失をテストしてください。 また、あるタイム ゾーンのマシンで収集され、別のタイム ゾーンのマシンに格納されている日付をテストします。

User-Ready値の書式設定と解析

ユーザーから日付と時刻の情報を取得し、このユーザー入力を DateTime 値に変換する必要があるプログラムの場合、フレームワークでは、特定の方法で書式設定された文字列の解析がサポートされます。 一般に、 DateTime.Parse メソッドと ParseExact メソッドは、日付と時刻を含む文字列を DateTime 値に変換する場合に便利です。 逆に、 ToStringToLongDateStringToLongTimeStringToShortDateStringおよび ToShortTimeString の各メソッドは、DateTime 値を人間が判読できる文字列にレンダリングする場合に役立ちます。

解析に影響を与える 2 つのメインの問題は、カルチャと書式指定文字列です。 DateTime のよく寄せられる質問 (FAQ) では、カルチャに関する基本的な問題について説明します。そのため、ここでは 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 メソッドを使用します。 ParseExact では独自の Formatter オブジェクト インスタンスを指定する必要があるため、ほとんどの場合、Parse で十分です。 Parse は非常に優れた柔軟性を備え、日付と時刻を含むほとんどの文字列を正確に変換できます。

最後に、スレッドの CultureInfo を CultureInfo.InvariantCulture に設定した後にのみ、Parse メソッドと ToString メソッドを常に呼び出す必要があります。

今後の考慮事項

現在、DateTime.ToString では簡単にできないことの 1 つは、DateTime 値を任意のタイム ゾーンに書式設定することです。 この機能は、.NET Frameworkの今後の実装で検討されています。 文字列 "12:00:00 EST" が "11:00:00 EDT" と等しいかどうかを判断できる必要がある場合は、変換と比較を自分で処理する必要があります。

DateTime.Now() メソッドに関する問題

Now という名前のメソッドを扱うときには、いくつかの問題があります。 これを読む Visual Basic 開発者の場合、これは Visual Basic Now 関数にも適用されます。 Now メソッドを定期的に使用する開発者は、現在の時刻を取得するために一般的に使用されることを知ります。 Now メソッドによって返される値は、現在のコンピューターのタイム ゾーン コンテキストにあり、変更できない値として扱うことはできません。 一般的な方法は、格納またはマシン間で送信される時刻をユニバーサル (UCT) 時間に変換することです。

夏時間が発生する可能性がある場合は、避ける必要があるコーディングプラクティスが 1 つあります。 検出が難しいバグを導入できる次のコードを検討してください。

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

このコードの実行結果の値は、秋の夏時間切り替え中に発生する余分な時間に呼び出された場合、1 時間ごとにオフになります。 (これは、夏時間を練習するタイム ゾーンにあるマシンにのみ適用されます)。その朝 1:10:00 AM など、同じ値が 2 回発生する場所に余分な時間が含まれるため、返される値が必要な値と一致しない可能性があります。

これを解決するには、DateTime.Now を呼び出す代わりに DateTime.UtcNow() を呼び出し、ユニバーサル時刻に変換することをお勧めします。

Dim timeval As DateTime
timeval = DateTime.UtcNow()  

このコードは常に適切な 24 時間 1 日のパースペクティブを持ち、安全に現地時刻に変換できます。

ベスト プラクティス #8

コーディングを行い、現在の時刻をユニバーサル時刻として格納する場合は、DateTime.Now() を呼び出し、その後にユニバーサル時刻への変換を行わないでください。 代わりに、DateTime.UtcNow 関数を直接呼び出します。

注意: DateTime 値を含むクラスをシリアル化する場合は、シリアル化される値がユニバーサル時刻を表していないことを確認してください。 XML シリアル化では、Visual Studio の Whidbey リリースまで UCT シリアル化はサポートされません。

あまり知られていないエクストラのカップル

API の一部に飛び込み始めると、隠された宝石が見つかる場合があります。これは目標を達成するのに役立ちますが、それについて話されなければ、日々の旅行では明らかにされません。 .NET の DateTime 値型には、ユニバーサル時刻のより一貫した使用を実現するのに役立つ可能性のある、このような宝石がいくつかあります。

1 つ目は、System.Globalization 名前空間にある DateTimeStyles 列挙です。 列挙は、ユーザー指定の入力やその他の形式の入力文字列表現を DateTime 値に変換するために使用される DateTime.Parse() 関数と ParseExact 関数の動作を制御します。

次の表では、DateTimeStyles 列挙が有効にする機能の一部を示します。

列挙定数 目的 注意事項
AdjustToUniversal Parse メソッドまたは ParseExact メソッドの一部として渡された場合、このフラグを指定すると、返される値はユニバーサル時刻になります。 ドキュメントはあいまいですが、これは Parse と ParseExact の両方で機能します。
NoCurrentDateDefault 日付コンポーネントなしで解析される文字列には、現在の日付の時刻である DateTime 値が返されるという前提を抑制します。 このオプションを使用する場合、返される DateTime 値は、1 年のグレゴリオ暦の日付 1 月 1 日に指定された時刻です。
AllowWhiteSpaces

AllowTrailingWhite

AllowLeadingWhite

AllowInnerWhite

これらのオプションはすべて、解析される日付文字列の前、後ろ、および中央に追加された空白の許容範囲を有効にします。 なし

その他の興味深いサポート関数は 、System.Timezone クラスにあります。 夏時間が DateTime 値に影響を与えるかどうかを検出する場合、またはローカル コンピューターの現在のタイム ゾーン オフセットをプログラムで決定する場合は、それらの出力を必ずチェックしてください。

まとめ

.NET Framework DateTime クラスには、時間を処理するプログラムを記述するためのフル機能を備えたインターフェイスが用意されています。 クラスを扱う際の微妙な違いを理解することは、Intellisense® から収集できる内容を超えています。 ここでは、日付と時刻を扱うプログラムのコーディングとテストのベスト プラクティスについて説明しました。 コーディングをお楽しみください!