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

3

6

I'm using the Invoke-RestMethod to get the data from REST API. One of the attributes in response is the date. When using Postman or other tools to get the data the date is returned correctly but when I'm using PowerShell (version 5.1.19041.906) and its Invoke-RestMethod like this:

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

All values from the date attribute are automatically converted to UTC. Is there any way how to disable this shift? I need the original values returned from the API.

Calcify answered 11/10, 2021 at 14:5 Comment(1)
All values returned by Invoke-RestMethod have already been deserialized for you. If you want the raw JSON response, use Invoke-WebRequest insteadAhoufe
E
7

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

When ConvertFrom-Json does recognize what are invariably string representation of dates in the input JSON, it converts them to [datetime] instances.

In Windows PowerShell (v5.1, the latest and final version) and up to PowerShell (Core) 7.4.x, you get NO control over what kind of [datetime] instances are constructed, as reflected in their .Kind property:

  • In Windows PowerShell, which requires a custom date-string format (e.g. "\/Date(1633984531266)\/"), you invariably get Utc instances.

  • In PowerShell (Core) 7+, which additionally recognizes string values that are (variations of) ISO 8601 date-time strings (e.g. "2021-10-11T13:27:12.3318432-04:00"), the .Kind value depends on the specifics of the string value:

    • If the string ends in Z, denoting UTC, you get a Utc instance.
    • If the string ends in a UTC offset, e.g. -04:00 you get a Local instance (even if the offset value is 00:00)
      • Note that this means that the timestamp is translated to the caller's local time zone, so the original offset information is lost (unless the caller's time zone's offset happens to match).
    • Otherwise you get an Unspecified instance.

Update:

While Windows PowerShell will see no new features, in PowerShell 7.5+,[1] based on the feature request in GitHub issue #13598, ConvertFrom-Json will have a -DateKind parameter, so as to allow explicitly requesting the date kind of interest, and to alternatively construct [datetimeoffset] instances, which are generally superior to [datetime].
In the absence of a -DateKind argument in a given call, the (PowerShell (Core)) behavior described above will continue to apply.


Workaround:

  • Note: In the event that you need access to the raw string values, exactly as defined, the solution below wont' work. You'll have to retrieve the raw JSON text and perform your own parsing, using Invoke-WebRequest and the response's .Content property, as Mathias R. Jessen notes.

The following snippet walks a [pscustomobject] graph, as returned from Invoke-RestMethod and explicitly converts any [datetime] instances encountered to Local instances in place (Unspecified instances are treated as 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

[1] See GitHub PR #20925. It should first become available in PowerShell 7.5.0-preview.3, the next 7.5 preview version to be released as of this writing.

Expatriate answered 11/10, 2021 at 21:29 Comment(0)
A
0
$response = Invoke-RestMethod -Method Get -Uri $url -Headers $requestHeaders

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

$datetime  = ([DateTime]$changeddate).ToLocalTime()
Arjan answered 11/10, 2021 at 14:53 Comment(0)
S
0

As already mentioned by others, invoke-restmethod is basically invoke-webrequest + convertfrom-json. So what is needed is to prevent convertfrom-json from recognizing datetime strings in the json response. So my method is to alter the datetime strings to make them unrecognizable to convertfrom-json. It would be easiest if you know the datetime format in the raw response. In my example below, the datetime text is in iso8601 format (eg: 2022-09-27T18:00:00.000Z).

Before:

$response = Invoke-RestMethod @params

After:

$response_raw = invoke-webrequest @params
$response_raw_content = $response_raw.content #look at this to see original datetime format

# obfuscate datetime text
$response_raw_content_fixdates = $response_raw_content -replace '(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)\.(\d\d\d)Z','$1d$2d$3T$4t$5t$6p$7Z'
$response = $response_raw_content_fixdates | ConvertFrom-Json

# restore datetime text
$response_names = $response[0].psobject.Properties.name
for($i=0; $i -lt $response.count; $i++) {
    $response_names | foreach-object {
        $response_name = $_
        if($response[$i].$response_name -match '(\d\d\d\d)d(\d\d)d(\d\d)T(\d\d)t(\d\d)t(\d\d)p(\d\d\d)Z'){
            [string]$response[$i].$response_name = $response[$i].$response_name -replace '(\d\d\d\d)d(\d\d)d(\d\d)T(\d\d)t(\d\d)t(\d\d)p(\d\d\d)Z','$1-$2-$3T$4:$5:$6.$7Z'
        }
    }
}
Sonde answered 16/10 at 17:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.