1

I have code to find files containing specific words that were written to the folder today. The following code works as expected:

##Define the directory to search
$Crmrpts = '\\1.1.1\crmrpts'
##Define the report names to search for. Full names not wildcard
$Values = 
@(
    'Customer',
    'Product',
    'Sales',
    'Returns',
    'Inventory'
)

##Gci the folder defined above looking for file names matching the reports to search for above
$Files = gci $Crmrpts '*.csv' | Where-Object {
    
    $v = $_.Name
    $t = $_.LastWriteTime.Date

    $Values | Where-Object { $v.Contains($_) -and $t.Equals((Get-Date).Date) }

}

The important variable here is $t and the section $t.Equals((Get-Date).Date)

Since I am looking for LastWriteTime.Date specifically, I assume I can change my search criteria to be a date, like so:

$Values | Where-Object { $v.Contains($_) -and $t.Equals("12/13/2020")

But this doesn't work. However, if I run another piece of independent code using the same components, it does work:

gci '\\1.1.1\crmrpts' | ? {$_.Name -like '*Customers*' -and $_.LastWriteTime.Date -eq "12/13/2020"}

Why does the above line work, containing $_.LastWriteTime.Date -eq "12/13/2020" but that same piece of logic does not work in my original script above?

The only way I can get my original script to work for a date other than current date is like this:

$Values | Where-Object { $v.Contains($_) -and $t.Equals((Get-Date).Date.AddDays(-1))
sleven
  • 95
  • 2
  • 8

2 Answers2

4

The .Net DateTime.Equals() method can only compare a datetime object with another datetime object.

That is why $t.Equals("12/13/2020") won't work, because you are telling the method to compare a datetime object to a string.

$_.LastWriteTime.Date -eq "12/13/2020" does work, because PowerShell conveniently converts the given date string to a datetime object before comparing.

Here, the conversion to a DateTime object works culture invariant as mklement0 commented.

However, if you have a different locale, like me (NL Dutch), you cannot rely on creating dates using the invariant "MM/dd/yyy" format all the time.. Especially, something like Get-Date '12/13/2020' will give me an exception because here the current locale settings (for me "dd/MM/yyyy") are used.

In short:

  • always try and compare a datetime object to another datetime object
  • don't assume that every machine in the world accepts the same date string format
Theo
  • 57,719
  • 8
  • 24
  • 41
  • Thanks for the info. I suppose using the .Net Equals() method bypasses or does not allow for the convenient implicit string conversion to a date that I am familiar with when simply piping Where-Object to Get-ChildItem. If I wanted to put the date to search in a variable, how would I construct that? – sleven Dec 14 '20 at 14:32
  • 1
    @sleven Well, there's lots of ways, like `$refDate = (Get-Date -Year 2020 -Month 12 -Day 13).Date` or `$refDate = [DateTime]::new(2020,12,13)` or if it is for yesterday `$refDate = (Get-Date).AddDays(-1).Date` or `$refDate = [datetime]::Today.AddDays(-1)` – Theo Dec 14 '20 at 14:37
1

tl;dr

Use a [datetime] cast to force the method argument to be of that type:

t.Equals([datetime] "12/13/2020")

$t.Equals("12/13/2020")

As Theo's helpful answer explains, the [datetime] type's .Equals() method only works as you intended if you pass a [datetime] instance - passing a string representation of a date is not automatically converted to [datetime].[1]

By contrast, PowerShell's -eq operator does perform implicit conversion of the RHS, namely to the type of the LHS operand.

Therefore, $_.LastWriteTime.Date -eq "12/13/2020" is implicitly the same as:
$_.LastWriteTime.Date -eq [datetime] "12/13/2020" and that's why it worked.

Note that this implicit conversion is culture-invariant; that is, it works the same irrespective of what culture is in effect in your session (as reflected in $PSCulture and by Get-Culture).

PowerShell casts and automatic type conversions, including stringification in expandable strings ("..."), are generally culture-invariant (based on System.Globalization.CultureInfo.InvariantCulture, which is based on the US-English culture).

Notable exceptions:

Also note that PowerShell's for-display output formatting and the use of the -f operator (e.g., '{0:N2}' -f 1.2) are culture-sensitive by design.

For a comprehensive discussion of culture invariance vs. sensitivity in PowerShell, see this answer.


[1] Note that while PowerShell generally performs implicit type conversions also when calling .NET methods, in this case PowerShell's method-overload resolution algorithm chooses the [object] based .ToEquals() overload (bool Equals(System.Object value)), which therefore passes the string argument as-is - and [datetime] itself makes no attempt to perform automatic conversions, as is typical. It is PowerShell that provides automatic conversions in many cases, which is generally a great benefit, but has its pitfalls.

mklement0
  • 382,024
  • 64
  • 607
  • 775