Private/ConvertFrom-MailDate.ps1
|
# RFC 5322 date-time including obsolete zone abbreviations. Returns a UTC [datetime] or $null. $script:ObsoleteZones = @{ UT = '+0000'; GMT = '+0000'; EST = '-0500'; EDT = '-0400'; CST = '-0600' CDT = '-0500'; MST = '-0700'; MDT = '-0600'; PST = '-0800'; PDT = '-0700' } function ConvertFrom-MailDate { [CmdletBinding()] [OutputType([datetime])] param([AllowNull()][AllowEmptyString()][string]$Text) if ([string]::IsNullOrWhiteSpace($Text)) { return $null } $t = [regex]::Replace($Text, '\([^()]*\)', ' ') $t = [regex]::Replace($t, '\([^()]*\)', ' ') $t = ([regex]::Replace($t, '\s+', ' ')).Trim() $zoneEvaluator = [System.Text.RegularExpressions.MatchEvaluator] { param($m) $zone = $script:ObsoleteZones[$m.Groups[1].Value.ToUpperInvariant()] if ($null -eq $zone) { return '+0000' } return $zone } $t = [regex]::Replace($t, '\b(UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT)\b\s*$', $zoneEvaluator, 'IgnoreCase') # A numeric offset without colon ("+0200") is not accepted by every .NET parser: normalize it. $t = [regex]::Replace($t, '([+-])(\d{2})(\d{2})\s*$', '$1$2:$3') $parsed = [DateTimeOffset]::MinValue $styles = [System.Globalization.DateTimeStyles]::AllowWhiteSpaces -bor [System.Globalization.DateTimeStyles]::AssumeUniversal if ([DateTimeOffset]::TryParse($t, [cultureinfo]::InvariantCulture, $styles, [ref]$parsed)) { return $parsed.UtcDateTime } $m = [regex]::Match($t, '(\d{1,2})\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+(\d{2,4})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*([+-]\d{2}:?\d{2})?', 'IgnoreCase') if (-not $m.Success) { return $null } $months = @('jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec') $year = [int]$m.Groups[3].Value if ($year -lt 100) { if ($year -ge 70) { $year += 1900 } else { $year += 2000 } } $month = [array]::IndexOf($months, $m.Groups[2].Value.ToLowerInvariant()) + 1 $second = 0 if ($m.Groups[6].Success) { $second = [int]$m.Groups[6].Value } $offset = '+00:00' if ($m.Groups[7].Success) { $offset = $m.Groups[7].Value -replace ':', '' } $offset = $offset -replace ':', '' $sign = 1 if ($offset[0] -eq '-') { $sign = -1 } $offsetMinutes = $sign * ([int]$offset.Substring(1, 2) * 60 + [int]$offset.Substring(3, 2)) try { $utc = New-Object DateTime ($year, $month, [int]$m.Groups[1].Value, [int]$m.Groups[4].Value, [int]$m.Groups[5].Value, $second, [DateTimeKind]::Utc) return $utc.AddMinutes(-$offsetMinutes) } catch { return $null } } function Format-Duration { <# .SYNOPSIS Human-readable duration ("3 s", "2 min 10 s", "1 h 05 min"). #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)][int64]$Seconds) $s = [math]::Abs($Seconds) if ($s -lt 1) { $out = '< 1 s' } elseif ($s -lt 90) { $out = '{0} s' -f $s } elseif ($s -lt 3600) { $out = '{0} min {1} s' -f [math]::Floor($s / 60), ($s % 60) } elseif ($s -lt 86400) { $out = '{0} h {1:00} min' -f [math]::Floor($s / 3600), [math]::Floor(($s % 3600) / 60) } else { $out = '{0} d {1} h' -f [math]::Floor($s / 86400), [math]::Floor(($s % 86400) / 3600) } if ($Seconds -lt 0) { return '-' + $out } return $out } |