Search code examples
powershelllogicpowershell-4.0powershell-5.0

How does PowerShell handle ((Get-Date).Month)-1 in January?


I wrote a Powershell/Robocopy backup script a few months back. It creates a folder for the backups with the year and month that the backups were taken (ex: 2019 11). The month always has to be one less because the script runs on the first of each new month. Everything has been smooth sailing but I just realized that I'm not really sure how the script will behave come January 1st. Does anyone have any incite as to what the output will be on January 1st, and is there a way for me to test this to confirm?

$month = (Get-Date -UFormat "%Y") + ' ' + ((((Get-Date).Month) - 1)).ToString()
# When run on November 1st, it creates a folder for the October backups called "2019 10".
# When run on December 1st, it creates a folder for the November backups called "2019 11".

When run on January 1st, what will it name the folder for the December backups? Will it be called "2019 12"? "2019 00"? Is there a way for me to easily test the behavior that relies on time, without manually adjusting my PC's calendar?


Solution

  • Get-Date optionally accepts a date to operate on (via -Date or positionally), which defaults to the current point in time.

    Additionally, you can use -Day to modify the day-of-the-month part of the target date (as well as -Month and -Year, analogously); passing -Day 1 returns the date of the first of the month that the target date falls into.

    Calling .AddDays(-1) on the resulting date is then guaranteed to fall in the previous month (it returns the previous month's last day).

    The .ToString() method of System.DateTime allows you to perform custom string formatting of a date, using custom date and time format strings.

    To put it all together:

    # PRODUCTION USE:
    # The reference date - now.
    $now = Get-Date
    
    # OVERRIDE FOR TESTING:
    # Set $now to an arbitrary date, 1 January 2020 in this case.
    # Note: With this syntax, *month comes first*, irrespective or the current culture.
    $now = [datetime] '1/1/2020'
    
    # Get the first day of the month of the date in $now,
    # subtract 1 day to get the last day of the previous month,
    # then use .ToString() to produce the desired format.
    (Get-Date $now -Day 1).AddDays(-1).ToString('yyyy MM')
    

    The above yields:

    2019 12
    

    Note: PowerShell's casts such as [datetime] '1/1/2020' generally use the invariant culture for stability of behavior across different cultures; this virtual culture is associated with the US-English culture and supports its month-first date format (e.g., 12/1/2020 refers to 1 December 2020, not 12 January 2020).

    Surprisingly, by contrast, when you pass arguments to cmdlets, data conversions are culture-sensitive; that is, in the French culture (fr-FR), for instance, calling Get-Date 12/1/2020 would result in 12 January 2020, not 1 December 2020, which is what it returns in the US-English culture (en-US)).

    This problematic discrepancy in behavior is discussed in this GitHub issue - the behavior is unlikely to change, however, in the interest of preserving backward compatibility.