Note: For convenience, PowerShell is used to demonstrate the behavior, but the question is about surprising behavior of the System.DateTime .NET type, contrasted with type System.DateTimeOffset.
There may be a good conceptual reason for this behavior, but it escapes me. If there is, it would be helpful to understand why and how to avoid this pitfall.
The following PowerShell snippet demonstrates round-trip conversion of a DateTime instance expressed in local time via its Unix time equivalent:
# Get midnight 1 Jul 2018 in local time.
$date = Get-Date '2018-07-01'
# Convert to Unix time (seconds since midnight 1 Jan 1970 UTC)
# Note: In PowerShell Core this command could be simplified to: Get-Date -Uformat %s $date
$unixTime = [int] (Get-Date -Uformat %s $date.ToUniversalTime())
# Reconvert the Unix time stamp to a local [datetime] instance.
# Note that even though the input string is a UTC time, the cast creates
# a *local* System.DateTime instance (.Kind equals Local)
$dateFromUnixTime1 = ([datetime] '1970-01-01Z').AddSeconds($unixTime)
# Reconvert the Unix time stamp to a local [datetime] instance via
# a [System.DateTimeOffset] instance:
$dateFromUnixTime2 = ([datetimeoffset ] '1970-01-01Z').AddSeconds($unixTime).LocalDateTime
# Output the results
@"
original: $date
Unix time: $unixTime
reconstructed via [datetime]: $dateFromUnixTime1
reconstructed via [datetimeoffset]: $dateFromUnixTime2
"@
The above yields (on my US-English system in the Eastern Timezone):
original: 07/01/2018 00:00:00
Unix time: 1530417600
reconstructed via [datetime]: 06/30/2018 23:00:00
reconstructed via [datetimeoffset]: 07/01/2018 00:00:00
As you can see, the [datetime] instance obtained via the ([datetime] '1970-01-01Z') instance - whose .Kind value is Local, i.e., a local date - is off by 1 hour, whereas the [datetimeoffset]-based calculation (which is UTC-based) works as expected.
I suspect that this is related to DST (daylight-saving time) - it wouldn't happen with 2018-12-01, for instance - but I'm unclear why.
Ultimately the problem was due to
AddSecondsbeing called on a local-time basedDateTime. The .net docs say (emphasis mine):I'm not a heavy PowerShell expert, but it appears that
[datetime] 'somestring'is equivalent to callingDateTime.Parse("somestring"). With that API, the default behavior is to return values in terms of the local time zone. Since you passed aZ, the input is treated as UTC, and then the value is converted to local time. That's the cause of the discrepancy.In C# (staying with
DateTime) one would pass arguments to control the parsing and output behavior:The
RoundTripKindstyle says (in part) that the output kind should be determined by the information in the input string. SinceZmeans UTC, you'll get a UTC-basedDateTimein the output.I'm not sure offhand how to pass these parameters into the shorthand (type accelerator?) in powershell, but the longhand is like this:
Also, you could just make things easier by using the built-in method instead of parsing:
You can get a
DateTimeoff that if you like (though keep in mind thatDateTimeOffset.UtcDateTimeretains the UTC kind whileDateTimeOffset.DateTimewill always have unspecified kind, whereasDateTimeOffset.LocalDateTimereturns the local kind).The powershell I think would be like this: