PowerShell 5.1の環境でdatetime型の日時をJSONに保管して読みだしたときにはまった話題です。
次のように変数$aに日時を代入します。
$a = [datetime]::ParseExact("2023/11/5 23:00", "yyyy/M/d H:m", $null)
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.PSCustomObjectName 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型に戻してあつかっています。