ParseExact - FormatException: String was not recognized as a valid DateTime

Viewed 61

Here's my code

# $index is 12 (for exif 'date taken')
$value = $dir.GetDetailsof($file, $index)
$value = $value.Trim()
if ($value -and $value -ne '') {
    return [DateTime]::ParseExact($value, "g", $null)
}
return $null

this always throws the following exception

Exception calling "ParseExact" with "3" argument(s): "String was not recognized as a valid DateTime."
At REDACTEDPATH.ps1:64 char:16
+         return [DateTime]::ParseExact($value, "g", $null)
+                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
   + FullyQualifiedErrorId : FormatException

If I use return [DateTime]::ParseExact("09/11/2019 19:16", "g", $null) the code works as expected

If I go back to using the variable and inspect $value at a breakpoint on the return line, it looks like 09/11/2019 19:16

If I look at the exif data of the file via the Windows property dialogue, the datetime looks like 09/11/2019 19:16

If I change the code to return [DateTime]::ParseExact($value, "dd\MM\yyyy HH:mm", $null) it throws the same exception, but if I try return [DateTime]::ParseExact("09/11/2019 19:16", "dd\MM\yyyy HH:mm", $null) it works. So it seems as though what I'm picking up in $value isn't what I think it is, but I can't see where it differs from my expectation.

Even inserting Write-Host "-$value-" just after the trim line shows exactly what I'd expect to see: -‎09/‎11/‎2019 ‏‎19:16-

EDIT here's an image of variable inspection at the breakpoint

1 Answers

This exif property looks like a normal string, but it has a trailing zero (0x00) character that confuses ParseExact unless we include the zero in the ParseExact pattern

Try

[datetime]::ParseExact($value,"yyyy:MM:dd HH:mm:ss`0", $null)

On further inspection using a Hex editor, I could see your string is loaded with weird (invisible) characters. Preceeding each of the digit parts, there is a sequence of characters E2 80 8E and right after the space, where the time part begins it has E2 80 8F E2 80 8E..

Code point UTF-8 hex Name
U+200E e2 80 8e LEFT-TO-RIGHT MARK
U+200F e2 80 8f RIGHT-TO-LEFT MARK

I have no idea what kind of file you are taking this strange dat from, but this may help parsing out the date from that:

[datetime]::ParseExact($value -replace '[^\d/ :]+', 'dd/MM/yyyy HH:mm', $null)

Of course, given the example date you show, there is no telling whether the pattern is dd/MM/yyyy HH:mm or MM/dd/yyyy HH:mm, so you will have to find that out by trying to parse a date from more files..

The regex pattern:

[^\d/\ :]     Match any single character NOT present in the list below
              A “digit” (any decimal number in any Unicode script)
              A single character from the list “/ :”
   +          Between one and unlimited times, as many times as possible, giving back as needed (greedy)

P.S. To get the 'Date Taken' from an image file's Exif data, I usually use below helper function (simplified thanks to mklement0):

function Get-ExifDate {
    # returns the 'DateTimeOriginal' property from the Exif metadata in an image file if possible
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName', 'FileName')]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf})]
        [string]$Path
    )

    Begin {
        Add-Type -AssemblyName 'System.Drawing'
    }
    Process {
        try {
            $image = [System.Drawing.Image]::FromFile($Path)

            # get the 'DateTimeOriginal' property (ID = 36867) from the image metadata
            # Tag Dec  TagId Hex  TagName           Writable  Group    Notes
            # -------  ---------  -------           --------  -----    -----
            # 36867    0x9003     DateTimeOriginal  string    ExifIFD  (date/time when original image was taken)
            # see: https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
            #      https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.propertyitem.id
            #      https://docs.microsoft.com/nl-nl/windows/win32/gdiplus/-gdiplus-constant-property-item-descriptions#propertytagexifdtorig

            # get the date taken as an array of (20) bytes
            $exifDateBytes = $image.GetPropertyItem(36867).Value
            # transform to string, but beware that this string is Null terminated, so cut off the trailing 0 character
            # or incorporate it in the pattern for ParseExact: "yyyy:MM:dd HH:mm:ss`0"
            $exifDateString = [System.Text.Encoding]::ASCII.GetString($exifDateBytes).TrimEnd("`0")
            # return the parsed date
            [datetime]::ParseExact($exifDateString, "yyyy:MM:dd HH:mm:ss", $null) 
        }
        catch{
            Write-Warning -Message "Could not read Exif data from '$Path': $($_.Exception.Message)"
        }
        finally {
            if ($image) {$image.Dispose()}
        }
    }
}
Related