Private/ConvertTo-TripwireUtc.ps1
|
<# .SYNOPSIS Normalise any timestamp to a UTC DateTime. .DESCRIPTION Every time comparison in this module goes through here, and the reason is a bug this function exists to make impossible. Timestamps reach the engine from three places with three different .NET Kinds: a touch normalised in-process carries Kind=Utc, a decoy's plantedAt read back from JSON is a STRING, and a cast like [DateTime]'2026-06-15T09:00:00Z' produces Kind=Local -- it shifts the value into the machine's timezone. Subtract one from the other and the difference is wrong by the UTC offset, silently. On a machine at UTC-4 the 15-minute provisioning-grace window becomes a window that can never match, so the first run of every deployment pages somebody about its own planting; on a machine at UTC+0 the same code is correct and the bug is invisible. Kind=Unspecified is treated as UTC, which is what Microsoft Graph returns. #> function ConvertTo-TripwireUtc { [CmdletBinding()] [OutputType([DateTime])] param([Parameter(Mandatory)] [AllowNull()] $Value) if ($null -eq $Value) { return $null } if ($Value -is [DateTime]) { switch ($Value.Kind) { 'Utc' { return $Value } 'Local' { return $Value.ToUniversalTime() } default { return [DateTime]::SpecifyKind($Value, [DateTimeKind]::Utc) } } } if ($Value -is [DateTimeOffset]) { return $Value.UtcDateTime } $parsed = [DateTimeOffset]::Parse( [string]$Value, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal) return $parsed.UtcDateTime } |