[.NET 8] DateTime.Deconstruct を使ってみる

## はじめに
今さらですが、.NET8 から DateTime 構造体（DateOnly, TimeOnly, DateTimeOffset にも）に `Deconstruct`メソッドが追加されています。

https://learn.microsoft.com/ja-jp/dotnet/api/system.datetime.deconstruct?view=net-8.0#definition

同等の機能は簡単に実装できるので特に必要性を感じませんが、関連記事をほとんど見つけられなかったので、スクリプトで動作確認した結果をメモ程度に載せておきます。
## PowerShell（7.4 以降）
環境は次のとおりです。
```
PowerShell 7.4.5
PS C:\Users\User> dotnet --version
8.0.401
```
引数を参照で渡す必要があるため、先に変数を用意します。
```powershell:PowerShell 7.4
$year = $month = $day = 0
(date).Deconstruct([ref]$year, [ref]$month, [ref]$day) # 今日の日付を分解する
$year, $month, $day
# 2024
# 9
# 25
```
DateOnly と TimeOnly への分解も同様です。
```powershell:PowerShell 7.4
$dateonly, $timeonly = [dateonly]::new(1, 1, 1), [timeonly]::new(0)
(date).Deconstruct([ref]$dateonly, [ref]$timeonly)
$dateonly, $timeonly

# Year      : 2024
# Month     : 9
# Day       : 25
# DayOfWeek : Wednesday
# DayOfYear : 269
# DayNumber : 739153

# Hour        : 5
# Minute      : 34
# Second      : 51
# Millisecond : 307
# Microsecond : 908
# Nanosecond  : 500
# Ticks       : 200913079085
```

## IronPython 3.4 (on .NET8)
環境は次のとおりです。（`--roll-forward LatestMajor`オプションが必要です。）
```
PS C:\Users\User> dotnet --roll-forward LatestMajor "C:\Users\User\IronPython\3.4\net6.0\ipy.dll"
IronPython 3.4.1 (3.4.1.1000)
[.NETCoreApp,Version=v6.0 on .NET 8.0.8 (64-bit)] on win32
```
### 引数なしの場合
IronPython は融通が利く(？)ので、とりあえず直球を投げます。
```python:IronPython 3.4 on .NET8
import System
from System import DateTime

DateTime.Now.Deconstruct()
# (<System.DateOnly object at 0x000000000000002B [2024/09/25]>, <System.TimeOnly object at 0x000000000000002C [5:46]>)
```
DateOnly と TimeOnly に分解されます。
代入した上で、さらに分解してみます。
```python:IronPython 3.4 on .NET8
dateonly, timeonly = DateTime.Now.Deconstruct()
dateonly.Deconstruct()
# (2024, 9, 25)
timeonly.Deconstruct()
# (5, 55)
```
DateOnly と TimeOnly は、それぞれ (年, 月, 日) と (時, 分) に分解されます。
また、タプルの各要素は int です。

日付を DateOnly で扱えば使い勝手がよさそうです。

### 引数ありの場合
オーバーロードを使い分ける場合は引数を参照で渡す必要があり、少々手間がかかります。

```python:IronPython 3.4 on .NET8
# (System と DateTime は import 済みとします)
import clr
from System import Int32

year_ref = clr.Reference[Int32]()
month_ref = clr.Reference[Int32]()
day_ref = clr.Reference[Int32]()
DateTime.Now.Deconstruct(year_ref, month_ref, day_ref)

year_ref.Value
# 2024
month_ref.Value
# 9
day_ref.Value
# 25
```
実用性は乏しいのですが、参考まで。

## F# (on .NET8)
環境は次のとおりです。
```
PS C:\Users\User> dotnet fsi --version
Microsoft (R) F# インタラクティブ バージョン F# 8.0 のための 12.8.400.0
```
本命の F# です。
左辺から推論して、想定した結果を返します。
```fsharp:F# 8
open System
let year, month, day = DateTime.Now.Deconstruct();;
// val year: int = 2024
// val month: int = 9
// val day: int = 25

let dateonly, timeonly = DateTime.Now.Deconstruct();;
// val timeonly: TimeOnly = 6:24
// val dateonly: DateOnly = 2024/09/25
```
もちろん結果の一部破棄もできます。
```fsharp:F# 8
let _, month, day = DateTime.Now.Deconstruct();;
// val month: int = 9
// val day: int = 25
```
日付データのコレクション処理でも簡潔に記述できる可能性があります。
```fsharp:F# 8
// とりあえず DateTime 100日分のシーケンスを用意
let seqDate: DateTime seq =
    seq { for d in 0..99 -> DateTime(2024, 9, 1).AddDays(d) };;
// val seqDate: DateTime seq

// 左辺に型を明示しておけば オーバーロードに対応できる
let seqYMD: (int * int * int) seq = seqDate |> Seq.map _.Deconstruct()
printfn "%A" seqYMD;;
// seq [(2024, 9, 1); (2024, 9, 2); (2024, 9, 3); (2024, 9, 4); ...]
// val seqYMD: (int * int * int) seq

let seqDateAndTime: (DateOnly * TimeOnly) seq = seqDate |> Seq.map _.Deconstruct()
printfn "%A" seqDateAndTime;;
// seq
//   [(2024/09/01, 0:00); (2024/09/02, 0:00); (2024/09/03, 0:00);
//    (2024/09/04, 0:00); ...]
// val seqDateAndTime: (DateOnly * TimeOnly) seq

```
## おわりに
冒頭で「特に必要性を感じない」と書きましたが、意外と便利かもしれない...