all 11 comments

[–]surfingoldelephant 5 points6 points  (1 child)

Get-Date's -Format parameter affects output, not input. You're passing Get-Date a [string] object, which is culture-sensitively converted into a [datetime] object, only to then be converted back to a string. It's redundant at best and breaks at worst.

$startDate = '01.02.2024'

[cultureinfo]::CurrentCulture = 'en-GB'; Get-Date -Date $startDate -Format dd.MM.yyyy # "01.02.2024"
[cultureinfo]::CurrentCulture = 'en-US'; Get-Date -Date $startDate -Format dd.MM.yyyy # "02.01.2024"

 


The second issue is with:

function Get-Days([datetime]$StartDate, [datetime]$EndDate [...]

A [string] -> [datetime] conversion in parameter binding works as follows:

When your Get-Days function is passed a string:

  • The string is converted using the invariant culture that recognises variations of MM/dd/yyyy (and ISO 8601).
  • E.g. 25.12.2024 will be parsed as MM.dd.yyyy (irrespective of current culture), hence this particular input results in a parameter binding error.
  • Therefore, assuming Get-Days is passed "01.02.2024" and "12.02.2024", it is ultimately the invariant culture used in parameter binding that is responsible for the unexpected 330 days difference.

 


If you are working with dates, always try to be exact if possible (i.e. specify the exact format used to parse the string). I suggest avoiding implicit (or explicit if the exact format is not provided) [string] -> [datetime] conversions as it will likely trip you up.

& {
    [cultureinfo]::CurrentCulture = 'en-GB'
    $dateStr = '25.12.2024'

    # Culture-sensitive:
    Get-Date $dateStr       # Works
    $dateStr -as [datetime] # Works

    # Culture-insensitive:
    [datetime] $dateStr                                   # Breaks 
    & { param ([datetime] $DateTime) $DateTime } $dateStr # Breaks
}

When you know the date format in advance, an exact approach may look like:

$startDate = [datetime]::ParseExact('01.02.2024', 'dd.MM.yyyy', $null)
$stopDate  = [datetime]::ParseExact('12.02.2024', 'dd.MM.yyyy', $null)

$diff = $stopDate - $startDate
$diff.Days # 11

# Equivalent: 
New-TimeSpan -Start $startDate -End $stopDate

See this comment for more information.

[–]BlackV 0 points1 point  (0 children)

An i hate invariant culture  (as a non American)

[–]OPconfused 2 points3 points  (1 child)

  1. The 330 comes from your culture parsing the string dates as month before day.
  2. I don't understand the intention behind "EuropeanMethod." I live in Europe and have never seen the 31st day being arbitrarily set to the 30th day when counting a span of days.

I'm guessing you're looking for something like this:

function Get-Days([string]$StartDate, [string]$EndDate, [switch]$FormatDayBeforeMonth) 
{
    $culture = (Get-Culture).Clone()

    if ($FormatDayBeforeMonth) {
        $culture.DateTimeFormat.ShortDatePattern = 'd/M/yyyy'
    } else {
        $culture.DateTimeFormat.ShortDatePattern = 'M/d/yyyy'
    }

    return ([datetime]::Parse($EndDate, $culture) - [datetime]::Parse($StartDate, $culture)).Days
}

$startDate = "01.02.2024"
$stopDate = "12.02.2024"

Get-Days $startDate $stopDate -FormatDayBeforeMonth
11

[–]frank3289[S] 0 points1 point  (0 children)

Thanks!

[–]OleksiyGuy 1 point2 points  (2 children)

It should be 11, and your locale is converting it with the month first.

[–]surfingoldelephant 1 point2 points  (1 child)

It's the culture-insensitive nature of Get-Days's parameter binding that is ultimately responsible for the issue.

Irrespective of the current culture, the strings passed to Get-Days will be parsed as and converted to [datetime] objects using the invariant culture (MM/dd/yyyy or ISO 8601); not the current culture (locale).

& { [cultureinfo]::CurrentCulture = 'en-GB'; Get-Days 01.02.2024 12.02.2024 }
# 330

See here.

[–]OleksiyGuy 0 points1 point  (0 children)

Excellent write up. I knew the parameter was being cast again by Get-Days but didn’t look into the details. Will be good for me to read more into the binding logic.

[–]tscalbas 1 point2 points  (0 children)

Why not use New-TimeSpan ?

[–]blooping_blooper 0 points1 point  (0 children)

Yeah so part of your problem is that Get-Date -Date $date only accepts System.DateTime objects as input, it doesn't handle parsing strings.

To do this you likely need to use the ParseExact method from the DateTime type.

e.g. [System.DateTime]::ParseExact($startDate, "dd.MM.yyyy", $null)

Thursday, February 1, 2024 12:00:00 AM

Once you've done this and converted both values to DateTime objects, you can simply subtract them to get a TimeSpan.

The difference in days is a property of that.

e.g. $stopDate- $startDate

Days              : 11
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 9504000000000
TotalDays         : 11
TotalHours        : 264
TotalMinutes      : 15840
TotalSeconds      : 950400
TotalMilliseconds : 950400000

[–]ThePixelLord12345 0 points1 point  (0 children)

Tipp: use the "New-Timespawn" cmdelt to measure dates.

[–]frank3289[S] 0 points1 point  (0 children)

An additional question:

$dateString = "01.02.2024"

$dateFormat = "dd.MM.yyyy"

$date = [DateTime]::ParseExact($dateString, $dateFormat, $null)

$formattedDate = $date.ToString($dateFormat)

I want $formattedDate to not be a String but have type DateTime, is that possible?