.NETの基本クラスライブラリの長年の問題は、日付と時刻の値を別々に表すことができないことである。.NET 6の一部として、新しいDateOnly
クラスとTimeOnly
クラスは、この過失を修正しようとするものである。
90年代以降、Windowsプログラマは、日付と時刻の値に関する最適とは言えない言い分に対処してきた。Visual Basicには、日付、時刻、日付と時刻、期間の値に使われるDate
クラスのみがあった。これにより、日付コンポーネントしか保持するつもりがなかった値に対して、時間コンポーネントも取得しようとする場合によく問題が発生した。その逆も同じである。
.NET 1(VB 7)では、Date構造の名前がDateTime
に変更された。より適切な名前であるが、日付のみの値と日付+時刻の値の両方を表す必要があるという同じ問題があった。これは、タイムゾーン変換を実行するときに特に問題であった。DateTime
構造に格納されている日付のみの値は、午前1時あるいは午後11時の時間コンポーネントを簡単に取得できる。後者の場合、1時間のオフセットにより、日付コンポーネントが前日に変更される。
また、.NET 1には、TimeSpan
構造の導入があった。これは期間を保存するように設計された。しかし、多くの場合、その場しのぎの時間のみの構造として活用されていた。繰り返すが、この手法には問題があった。たとえば、午後10時に3時間を追加すると、予想される午前1時ではなく「1日と2時間」になる。多くの場合、これは、値がデータベースの時間のみの列に挿入されるまで気付かれず、その結果、オーバーフローエラーが発生する。
.NET 2では、DateTimeOffset
構造が追加された。UTCからのオフセットを保持するフィールドを含めることで、タイムゾーンのシナリオに対応するためである。しかし、他の問題は未解決のままであった。
DateOnlyとTimeOnly
.NET 6まで進むと、DateOnly
とTimeOnly
の提案があった。名前が示すように、これらの構造は、日付のみを保持するか、時刻のみを保持するように設計されている。単一責任の原則を正しく適用する数少ない例では、すでに説明した構造のような、複数の役割を果たすように求められるものはない。
前述のように、DateTime
構造の元の名前は単にDate
であった。これはVBでも継続され、Date
キーワードは引き続きDateTime
を参照する。そのため、混乱を避けるために、DateOnly
という名前が選択された。
DateOnly
という名前が選択されたもう1つの理由は、DateTime.Date
がすでにDateTime
値を返していることである。これは変更できないが、新たなプログラマはDate
を返すことを期待するだろう。DateOnly
型の呼び出しについて、新規の一致するプロパティをDateTime.DateOnly
で呼ぶことができる。
同様に、TimeOfDay
に問題がある。多くのプロパティとメソッドがDateTime
値あるいはTimeSpan
値の参照に使用するためである。
シリアル化
DateOnly
とTimeOnly
はSerializable属性を実装していない。.NET Core以降では、この属性に依存するシリアル化ライブラリと同じように、この属性は非推奨となる。
IXmlSerializable
属性とそれと同等のJSONに対するリクエストがあった。ただし、DateOnly
とTimeOnly
は、そのような属性が使用できないNETの最も低レベルのライブラリの1つに配置される。したがって、シリアライザーは、これらの新しいタイプに対して独自のカスタム処理を含める必要がある。そして、考慮すべきデータベースドライバーのようなものに組み込まれた完全にカスタムのシリアライザーがある。
このため、.NET 6がリリースされた後、シリアライザーが追いつくまで、一部のシナリオでDateOnly
とTimeOnly
を使えない期間が発生する可能性がある。
DateTimeOnly
タイムゾーンの問題を解決するために、.NET 2ではDateTime.Kind
プロパティが導入された。これは、開発者が日付+時刻の値に対して、その値がローカル、UTC、タイムゾーン未指定のいずれかを表すフラグでアノテーションを付ける選択肢を提供することを意味する。実際に起こったことは、開発者がこの情報を指定することを余儀なくされたか、予期しないバグに直面したことである。
さらに悪いことに、Kind
プロパティは一貫して尊重されることはなかった。たとえば、多くのシリアライザーはそれを認識し、値をエクスポートするときにオフセットを含める。これにより、一部の日付(DateTime.Now
からの日付など)のみがオフセットでシリアル化されるため、バグの診断が困難になる。ただし、比較演算子はKind
を完全に無視する。開発者は、Kindプロパティを自分でチェックし、必要に応じてタイムゾーン変換を実行する必要がある。
これらの問題を解消するために、基本的に.NET 1のDateTime
と同じように機能する新しいDateTimeOnly
構造が要求された。
高度なシナリオ
現時点では、実際のタイムゾーンの処理のような高度なシナリオに対処する予定はない。そして、開始と終了から成る期間と長さを表す期間についても同じである。この役割は、主にNoda Timeライブラリによって処理される。JavaのJoda Timeに基づいて、「1月3日」や「2021年3月」など、年、日、月が意図的に欠落している部分的な日付を表すこともできる。