Problem description
in the Microsoft Powershell Documentation ConvertFrom-Json (Microsoft.PowerShell.Utility) – PowerShell – Microsoft Learn is an example using Get-Date
Get-Date | Select-Object -Property * | ConvertTo-Json | ConvertFrom-Json
Running this example in a simply modified way the double conversion shows a weird output:
PS T:> $DateObj = Get-Date | Select-Object -Property *
PS T:> $DateObj | ConvertTo-Json | ConvertFrom-Json
DisplayHint : 2
DateTime : Freitag, 19. April 2024 23:06:39
Date : 18.04.2024 22:00:00
Day : 19
DayOfWeek : 5
DayOfYear : 110
Hour : 23
Kind : 2
Millisecond : 54
Minute : 6
Month : 4
Second : 39
Ticks : 638491647990542546
TimeOfDay : @{Ticks=831990542546; Days=0; Hours=23; Milliseconds=54; Minutes=6; Seconds=39; TotalDays=0.96295201683564813; TotalHours=23.110848404055556; TotalMilliseconds=83199054.2546; TotalMinutes=1386.6509042433333;
TotalSeconds=83199.054254599992}
Year : 2024
Note the output of the Date
field, which is off by one day for whatever reason.
Is this a bug in the conversion?
DateTime : Freitag, 19. April 2024 23:06:39 – correct local time
Date : 18.04.2024 22:00:00 – time offset of 25 hours – like UTC +25
Looking at the time of the query and language date settings, it could be related to to those settings:
Get-Date
issued after 23:00 (11PM)- local Timezone set is CEST – Central European Summer Time (German MESZ)
- as summer time is active, CEST is UTC +2 at the moment
- CET (German MEZ) is 1 hour behind UTC (or GMT) – UTC +1
- summer time is genrally 1 hour earlier than local time UTC – UTC +1
In a second test after midnight, the difference can be at least explained, as detailed below
But none of the differences explain, why the Date
field is off by 25 hours (earlier) – UTC +25
PS T:> $unixTime = 1713477600000 / 1000
PS T:> $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
PS T:> $whatIWant = $origin.AddSeconds($unixTime)
PS T:> $whatIWant
Donnerstag, 18. April 2024 22:00:00
It seems somehow in the conversion to JSON with ConvertTo-Json
the timestamp seems to get messed up.
Test 1 – onestep conversion at 23:04 CEST – weird offset like UTC +25 hours
PS T:> Get-Date | Select-Object -Property * | ConvertTo-Json | ConvertFrom-Json
DisplayHint : 2
DateTime : Freitag, 19. April 2024 23:04:54
Date : 18.04.2024 22:00:00
Day : 19
DayOfWeek : 5
DayOfYear : 110
Hour : 23
Kind : 2
Millisecond : 435
Minute : 4
Month : 4
Second : 54
Ticks : 638491646944355931
TotalSeconds=83094.4355931}
Year : 2024
The double conversion to JSON and back ConvertTo-Json
and then ConvertFrom-Json
shows the weird behaviour between the Datetime
(correct local time) and Date
property field, which shows a difference of roughly 25 hours less than local time (UTC +25)
Test 2 – at 23:05 CEST – properties in powershell are off by 23 hourslike UTC +23
PS T:> Get-Date | Select-Object -Property *
DisplayHint : DateTime
DateTime : Freitag, 19. April 2024 23:05:00
Date : 19.04.2024 00:00:00
Day : 19
DayOfWeek : Friday
DayOfYear : 110
Hour : 23
Kind : Local
Millisecond : 487
Minute : 5
Month : 4
Second : 0
Ticks : 638491647004876481
Test 3 – at 23:06 CEST – 2 steps: create $DateObj
with all properties – time offset +23 h
PS T:> $DateObj = Get-Date | Select-Object -Property *
PS T:> $DateObj
DisplayHint : DateTime
DateTime : Freitag, 19. April 2024 23:06:39
Date : 19.04.2024 00:00:00
Day : 19
DayOfWeek : Friday
DayOfYear : 110
Hour : 23
Kind : Local
Millisecond : 54
Minute : 6
Month : 4
Second : 39
Ticks : 638491647990542546
TimeOfDay : 23:06:39.0542546
Year : 2024
First we create a meta object $DateObj
with all properties and display it.
We see that Get-Date
is using local time settings and it seems the Date
property field should be UTC, but the difference between the Date
(UTCish) and Datetime
(correct local time) property fields is 25 hours later (UTC +25), a time offset which doesn’t exist.
PS T:> $DateObj | ConvertTo-Json | ConvertFrom-Json
DisplayHint : 2
DateTime : Freitag, 19. April 2024 23:06:39
Date : 18.04.2024 22:00:00
Day : 19
DayOfWeek : 5
DayOfYear : 110
Hour : 23
Kind : 2
Millisecond : 54
Minute : 6
Month : 4
Second : 39
Ticks : 638491647990542546
TimeOfDay : @{Ticks=831990542546; Days=0; Hours=23; Milliseconds=54; Minutes=6; Seconds=39; TotalDays=0.96295201683564813; TotalHours=23.110848404055556; TotalMilliseconds=83199054.2546; TotalMinutes=1386.6509042433333; TotalSeconds=83199.054254599992}
Year : 2024
The double conversion from the PS object $DateObj
with ConvertTo-Json
to JSON and then back with ConvertFrom-Json
shows the difference between DateTime
and Date
of 23h in the past (UTC +25)
PS T:> $DateObj | ConvertTo-Json
{
"DisplayHint": 2,
"DateTime": "Freitag, 19. April 2024 23:06:39",
"Date": "/Date(1713477600000)/",
"Day": 19,
"DayOfWeek": 5,
"DayOfYear": 110,
"Hour": 23,
"Kind": 2,
"Millisecond": 54,
"Minute": 6,
"Month": 4,
"Second": 39,
"Ticks": 638491647990542546,
"TimeOfDay": {
"Ticks": 831990542546,
"Days": 0,
"Hours": 23,
"Milliseconds": 54,
"Minutes": 6,
"Seconds": 39,
"TotalDays": 0.96295201683564813,
"TotalHours": 23.110848404055556,
"TotalMilliseconds": 83199054.2546,
"TotalMinutes": 1386.6509042433333,
"TotalSeconds": 83199.054254599992
},
"Year": 2024
}
In the conversion to JSON with ConvertTo-Json
we see the Date
field with the wrong timestamp 1713477600000
.
It’s the Unix timestamp with milliseconds added somehow rounded, but the offset of UTC/GMT and local time are wrong.
PS T:> $unixTime = 1713477600000 / 1000
PS T:> $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
PS T:> $whatIWant = $origin.AddSeconds($unixTime)
PS T:> $whatIWant
Donnerstag, 18. April 2024 22:00:00
Test 4 – at 00:07 CEST – offset is in the same hour
PS T:> $DateObj2 = Get-Date | Select-Object -Property *
PS T:> $DateObj2
DisplayHint : DateTime
DateTime : Samstag, 20. April 2024 00:07:32
Date : 20.04.2024 00:00:00
Day : 20
DayOfWeek : Saturday
DayOfYear : 111
Hour : 0
Kind : Local
Millisecond : 18
Minute : 7
Month : 4
Second : 32
Ticks : 638491684520183150
TimeOfDay : 00:07:32.0183150
Year : 2024
Here the offset between the DateTime
and Date
fields is merely 7 minutes.
PS T:> $DateObj2 | ConvertTo-Json | ConvertFrom-Json
DisplayHint : 2
DateTime : Samstag, 20. April 2024 00:07:32
Date : 19.04.2024 22:00:00
Day : 20
DayOfWeek : 6
DayOfYear : 111
Hour : 0
Kind : 2
Millisecond : 18
Minute : 7
Month : 4
Second : 32
Ticks : 638491684520183150
TimeOfDay : @{Ticks=4520183150; Days=0; Hours=0; Milliseconds=18; Minutes=7; Seconds=32; TotalDays=0.005231693460648148; TotalHours=0.12556064305555556; TotalMilliseconds=452018.315; TotalMinutes=7.5336385833333335; TotalSeconds=452.018315}
Year : 2024
Using the double conversion to JSON and back to a PS object, the time difference between DateTime
and Date
is now two hours (UTC +2)
PS T:> $DateObj2 | ConvertTo-Json
{
"DisplayHint": 2,
"DateTime": "Samstag, 20. April 2024 00:07:32",
"Date": "/Date(1713564000000)/",
"Day": 20,
"DayOfWeek": 6,
"DayOfYear": 111,
"Hour": 0,
"Kind": 2,
"Millisecond": 18,
"Minute": 7,
"Month": 4,
"Second": 32,
"Ticks": 638491684520183150,
"TimeOfDay": {
"Ticks": 4520183150,
"Days": 0,
"Hours": 0,
"Milliseconds": 18,
"Minutes": 7,
"Seconds": 32,
"TotalDays": 0.005231693460648148,
"TotalHours": 0.12556064305555556,
"TotalMilliseconds": 452018.315,
"TotalMinutes": 7.5336385833333335,
"TotalSeconds": 452.018315
},
"Year": 2024
}
The conversion of the Date field to JSON gets a different timestamp (looks again like unix timestamp * 1000 ms)
PS T:> $unixTime = 1713477600000 / 1000
PS T:> $origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
PS T:> $whatIWant = $origin.AddSeconds($unixTime)
PS T:> $whatIWant
Donnerstag, 18. April 2024 22:00:00
Using the JSON timestamp in the Date
field an converting it to a timedate string, it shows the same time as when the date
field gets converted back to a Powershell Object.
Here the time difference to local time is UTC +2
Summary: Powershell datetime values and or conversion to and from JSON seem to be wrong
It seems the Date
Property of the Powershell seems to be returning really odd and impossible values.
PS T:> Get-TimeZone
Id : W. Europe Standard Time
DisplayName : (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
StandardName : W. Europe Standard Time
DaylightName : W. Europe Daylight Time
BaseUtcOffset : 01:00:00
SupportsDaylightSavingTime : True
PS T:> (Get-Date).IsDaylightSavingTime()
True
Above is the information about the timezone and summer time setting of the local host, where the commands were issued.
2
Answers
Yeah, it’s a bug. It has been fixed in 7.x, not sure if they are ever going to fix it in 5.1.x.
Explanation:
A
[DateTime]
object has aDate
property which is a[DateTime]
object its value with time set to Midnight.This is WANTED as it’s supposed to be the value of JUST the date without the time but there was no way to have only the day-month-year values.
So they made a "convert to midnight and let the dev know it’s supposed to check only the date part"
Ex:
ConvertTo-Json
takes the propertyDate
, which is set to Midnight, CONVERTS IT TO UTC and then converts it to to UnixTime.Which means it applies the TimeZone Offset to MIDNIGHT.
Which, in your specific case, means it sets it back by 2 hour, thus turning it into 19 April 2024 22:00:00
Then it’s converted into UnixTime and then to JSON
Then ConvertFrom-Json gets it… but it has no way to know its supposed to apply some offset, so it converts it straight back to to 19 April 2024 22:00:00
Powewrshell 7.x fixed it by having
ConvertTo-Json
converting[Datetime]
properties in a full date string with offset included :It’s not so much a bug as it is a quirk of how the .NET Framework < 7 handles JSON dates.
Most applications follow the ISO 8601 datetime format. When converting date objects to JSON, Microsoft must have decided that it was better to be able to identify where a value is a date – possibly for purposes of simplicity when parsing/deserialising JSON – by using a
/date(epoch_timestamp)/
format.The issue with double conversion for a date object is that it does not contain the timezone, or time offset; ISO 8601 does.
When converting to JSON, the Unix time generated is the number of seconds elapsed since 1970 according to your local time, including your time offset. When it is converted back to datetime, there is no way to determine an offset or timezone; It’s been lost. So, when It’s converted back it is assumed that the JSON value was generated inside the UTC timezone. And therefore, in order to display the correct time as intended by the party who generated it, your local time offset is applied.
In your case, you are West Europe (UTC+1) and daylight saving offset gives you another (+1), so that’s (UTC+2). To get back to UTC time (the time .NET thinks is represented by the JSON value), 2 hours must be deducted.
You can verify this behaviour by changing your timezone to UTC. You will find that the date is always the correct date.
You can actually instruct
Get-Date
to output the date in ISO 8601 format, by using the o round-trip format parameter, if it helps.Otherwise, you could negate the offset behaviour by adding your time offset to the datetime object after conversion. Just make sure the conversion to and back from JSON are being done on the same machine, or on machines in the same time zone.