PowerShellでdatetime型をJSONにしたときの問題

 PowerShell 5.1の環境でdatetime型の日時をJSONに保管して読みだしたときにはまった話題です。
 次のように変数$aに日時を代入します。

$a = [datetime]::ParseExact("2023/11/5 23:00", "yyyy/M/d H:m", $null)

 これをConvertTo-JsonJSONにすると

PS C:\> $a | ConvertTo-Json
{
    "value":  "\/Date(1699192800000)\/",
    "DateTime":  "2023年11月5日 23:00:00"
}

と、valueとDateTime、2つのキーを持つオブジェクトとして保管されます。ではJSONから戻すと以下のようにdatetimeでなくPSCustomObjectとして戻ってきます。まあこれはJSONからの読み込み時の動きなので想定内です。

PS C:\> $b = $a | ConvertTo-Json | ConvertFrom-Json
PS C:\> $b.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object

 ここから日時を得るには、

PS C:\> $b | Get-Member


   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
DateTime    NoteProperty string DateTime=2023年11月5日 23:00:00
value       NoteProperty datetime value=2023/11/05 14:00:00

の結果から、valueキーがそれらしいです。

PS C:\> $b.value

2023年11月5日 14:00:00

 あれ。時刻が9時間遅れてるよ。

PS C:\> $b.value.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     DateTime                                 System.ValueType

と、ちゃんとdatetime型なものの、

"value":  "\/Date(1699192800000)\/"

になった時点でUTCの値になってしまうようです。これは使いづらい。時差分を増減計算するか、$b.DateTimeの文字列からParseExactでdatetime型にするのが近道ですかね。
 しかしこれでは解決できない問題もありまして、実際には$a単体でJSONにすることはなくて、ハッシュテーブルやオブジェクトの値としてあつかうことが多いです。

$x = @{"time" = $a}

$y = [PSCustomObject]@{"time" = $a}

など、ハッシュテーブルやオブジェクト内の変数としてdatetimeがある場合、

PS C:\> $x | ConvertTo-Json
{
    "time":  "\/Date(1699192800000)\/"
}
PS C:\> $y | ConvertTo-Json
{
    "time":  "\/Date(1699192800000)\/"
}

と、UTC時刻のUNIX時間値しか保管されないんですよね。これだとJSONから読み込んだときに9時間遅れているかどうか判別できません。自分の作ったスクリプトのみで動かすなら自己責任で時差を決め打ちできますが、気分的によくないですよね。
 PowerShellでのdatetime型をJSONにするときのFAQみたいで、バージョン6以降、"\/Date(1699192800000)\/"の形ではなく、日時を文字列化するように仕様変更あったようです。
 この方法を採用するとして、あとから新しいバージョンをインストールする手間はなるべくさけて、ある環境でそのまま使う方針ですので、いったん

$a = $a.ToString("o")

などと"2023-11-05T23:00:00.0000000"のような文字列に変更してからJSONに保管、呼び出し後

$a = [datetime]::ParseExact($a, "o", $null)

にてdatetime型に戻すようにしました。
 ただしこれだとタイムゾーン情報が入っていないので、念には念を入れてdatetimeoffset型で

$a = [datetimeoffset]::ParseExact("2023/11/5 23:00", "yyyy/M/d H:m", $null)
$a = $a.ToString("o")

と文字列"2023-11-05T23:00:00.0000000+09:00"してからConvertTo-Jsonして、

$b = $a | ConvertTo-Json | ConvertFrom-Json
$b = [datetimeoffset]::ParseExact($b,"o",$null)

のように文字列からdatetimeoffset型に戻してあつかっています。