在反序列化来自 Invoke-Restmethod 的响应后禁止转换为 UTC 时区

Disable conversion to UTC timezone after deserialization of a response from Invoke-Restmethod

我正在使用 Invoke-RestMethod 从 REST API 获取数据。响应中的属性之一是日期。当使用 Postman 或其他工具获取数据时,日期会正确返回,但当我使用 PowerShell(版本 5.1.19041.906)及其 Invoke-RestMethod 时,如下所示:

$response = Invoke-RestMethod -Method Get -Uri $url -Headers $requestHeaders

日期属性中的所有值都会自动转换为 UTC。有什么办法可以禁用这种转变吗?我需要从 API.

返回的原始值
$response = Invoke-RestMethod -Method Get -Uri $url -Headers $requestHeaders

$changeddate = $response.fields.'System.ChangedDate'

$datetime  = ([DateTime]$changeddate).ToLocalTime()

Invoke-RestMethod, when given a JSON response, automatically parses it into a [pscustomobject] graph; in a manner of speaking, it has ConvertFrom-Json内置

ConvertFrom-Json 识别出输入 JSON 中日期的不变 字符串 表示形式时,它会将它们转换为 [datetime] 实例。

Windows PowerShell(v5.1,最新和最终版本)和从 PowerShell(核心)开始7.2,您无法控制 [datetime] 个实例中 种类 的构造,如它们的 .Kind property 所示:

  • Windows PowerShell中,需要自定义日期字符串格式(例如"\/Date(1633984531266)\/"),你总是得到Utc个实例。

  • PowerShell (Core) 7+ 中, 还识别字符串值(变体)ISO 8601 日期时间字符串 s(例如 "2021-10-11T13:27:12.3318432-04:00"),.Kind 值取决于 字符串值的具体细节 :

    • 如果字符串以 Z 结尾,表示 UTC,您将获得一个 Utc 实例。
    • 如果字符串以 UTC offset 结尾,例如-04:00 你得到一个 Local 实例(即使偏移值是 00:00
    • 否则你会得到一个 Unspecified 实例。

虽然 Windows PowerShell 不会看到新功能,但 PowerShell(核心) 有希望:GitHub issue #13598建议在ConvertFrom-Json中增加一个-DateTimeKind参数,以允许显式请求兴趣的种类,并交替构造[datetimeoffset]个实例, 哪个更可取。


解决方法

  • 注意:如果您需要访问完全按照定义的原始字符串值,则以下解决方案将不起作用。您必须使用 Invoke-WebRequest and the response's .Content property, as Mathias R. Jessen 注释检索原始 JSON 文本并执行您自己的解析。

以下代码段 遍历 [pscustomobject] 图,从 Invoke-RestMethod 返回并显式 转换任何 [datetime] 实例遇到Local个实例就地Unspecified个实例被视为Local):

# Call Invoke-RestMethod to retrieve and parse a web service's JSON response.
$fromJson = Invoke-RestMethod ... 

# Convert any [datetime] instances in the object graph that aren't already 
# local dates (whose .Kind value isn't already 'Local') to local ones.
& {
  # Helper script block that walks the object graph.
  $sb = {
    foreach ($el in $args[0]) { # iterate over elements (if an array)
      foreach ($prop in $el.psobject.Properties) {
        # iterate over properties
        if ($dt = $prop.Value -as [datetime]) {
          switch ($dt.Kind) {
            'Utc' { $prop.Value = $dt.ToLocalTime() }
            # Note: calling .ToLocalTime() is not an option, because it interprets
            #       an 'Unspecified' [datetime] as UTC.
            'Unspecified' { $prop.Value = [datetime]::new($dt.Ticks, 'Local') }
          }
        }
        elseif ($prop.Value -is [Array] -or $prop.Value -is [System.Management.Automation.PSCustomObject]) { 
          & $sb $prop.Value # recurse
        }
      }
    }
  }
  # Start walking.
  & $sb $args[0]
} $fromJson

# Output the transformed-in-place object graph
# that now contains only Local [datetime] instances.
$fromJson