Private/Convert-Timestamp.ps1
# Requires -Version 5.1 # Precompile regex for log parsing $LogLineRegex = [regex]::new( '^(?<Timestamp>(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?|' + '[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}|' + '(?:\d{2}/\d{2}/\d{4}|\d{4}/\d{2}/\d{2})\s+\d{2}:\d{2}:\d{2}(?:\s*[AP]M)?|' + '(?:\d{2}/\d{2}/\d{4}|\d{4}/\d{2}/\d{2})\s+\d{2}:\d{2}:\d{2}|' + '(?:\d{2}/\d{2}/\d{4}|\d{4}/\d{2}/\d{2})))' + '(?:\s*\[(?<Level>[A-Z]+)\])?\s*(?<Message>.*)$', [System.Text.RegularExpressions.RegexOptions]::Compiled ) function Convert-Timestamp { <# .SYNOPSIS Converts a string timestamp into a System.DateTime object. .DESCRIPTION Attempts to parse a given timestamp string into a DateTime object using common formats, syslog-style fallback, and flexible parsing. .PARAMETER TimestampString The timestamp string to parse. .RETURNS [datetime] if parsing succeeds, otherwise $null. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] [string]$TimestampString, [Parameter()] [System.Globalization.CultureInfo]$Culture = [System.Globalization.CultureInfo]::GetCultureInfo('en-US') ) process { if ([string]::IsNullOrWhiteSpace($TimestampString)) { return $null } $formats = @( @{ Format = "o"; Style = [System.Globalization.DateTimeStyles]::RoundtripKind }, @{ Format = "yyyy-MM-ddTHH:mm:ss"; Style = [System.Globalization.DateTimeStyles]::AssumeUniversal }, @{ Format = "yyyy-MM-ddTHH:mm:ss'Z'"; Style = [System.Globalization.DateTimeStyles]::AssumeUniversal }, @{ Format = "yyyy-MM-ddTHH:mm:ss.fff"; Style = [System.Globalization.DateTimeStyles]::AssumeUniversal }, @{ Format = "yyyy-MM-ddTHH:mm:ss.fff'Z'"; Style = [System.Globalization.DateTimeStyles]::AssumeUniversal }, @{ Format = "yyyy-MM-dd HH:mm:ss"; Style = [System.Globalization.DateTimeStyles]::None }, @{ Format = "MM/dd/yyyy HH:mm:ss"; Style = [System.Globalization.DateTimeStyles]::None }, @{ Format = "dd/MM/yyyy HH:mm:ss"; Style = [System.Globalization.DateTimeStyles]::None }, @{ Format = "MM/dd/yyyy hh:mm:ss tt"; Style = [System.Globalization.DateTimeStyles]::None }, @{ Format = "dd/MM/yyyy hh:mm:ss tt"; Style = [System.Globalization.DateTimeStyles]::None } ) foreach ($entry in $formats) { [datetime]$parsed = $null if ([datetime]::TryParseExact($TimestampString, $entry.Format, $Culture, $entry.Style, [ref]$parsed)) { return $parsed } } # Syslog-style fallback (no year) $syslogFormats = @("MMM d HH:mm:ss", "MMM dd HH:mm:ss") $currentYear = (Get-Date).Year foreach ($fmt in $syslogFormats) { [datetime]$syslogResult = $null if ([datetime]::TryParseExact($TimestampString, $fmt, $Culture, [System.Globalization.DateTimeStyles]::AllowWhiteSpaces, [ref]$syslogResult)) { try { $adjusted = Get-Date -Year $currentYear -Month $syslogResult.Month -Day $syslogResult.Day ` -Hour $syslogResult.Hour -Minute $syslogResult.Minute -Second $syslogResult.Second return $adjusted } catch { continue } } } # Generic flexible fallback [datetime]$fallback = $null if ([datetime]::TryParse($TimestampString, $Culture, [System.Globalization.DateTimeStyles]::AllowWhiteSpaces, [ref]$fallback)) { return $fallback } return $null } } function ConvertFrom-LogEntry { <# .SYNOPSIS Converts a log line string into a structured object with Timestamp, Level, Message. .PARAMETER LogLine Single log line string to parse. .RETURNS PSCustomObject with properties: Timestamp ([datetime]), Level (string), Message (string), RawLine (string). #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$LogLine ) process { try { $match = $LogLineRegex.Match($LogLine) if ($match.Success) { $timestampString = $match.Groups["Timestamp"].Value.Trim() $level = $match.Groups["Level"].Value $message = $match.Groups["Message"].Value.Trim() $parsedTimestamp = Convert-Timestamp -TimestampString $timestampString [PSCustomObject]@{ Timestamp = $parsedTimestamp Level = if ([string]::IsNullOrWhiteSpace($level)) { "UNKNOWN" } else { $level } Message = $message RawLine = $LogLine } } else { [PSCustomObject]@{ Timestamp = $null Level = "UNPARSEABLE" Message = $LogLine RawLine = $LogLine } } } catch { return $null } } } |