1

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?

Evan
  • 334
  • 1
  • 6
  • 18

3 Answers3

3

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Thank you for your fantastic answer! Lots of useful information that I hope will help others as they come across this thread in the future. @hcm had a perfect answer too! – Evan Dec 20 '19 at 14:20
1

You are able to create an arbitrary date like so

$testdate = get-date -year 2020 -month 1 -day 1

Your code however will then generate "2020 0" as an output. You'd be better off with something like this. You would not be bound to running on the first day of the next month either:

$month = $(get-date (get-date $testdate).AddMonths(-1) -format "yyyy MM")

hcm
  • 922
  • 3
  • 10
  • Thank you for your answer! This is perfect! mkement0 offered a very similar answer first and went into great detail so I have to give the answer to him, but your answer is great too and I want to thank you for helping me out! – Evan Dec 20 '19 at 14:21
0

If you can guarantee that you will always run this on the first of the month, then you can use $folderName = ((Get-Date) - (New-TimeSpan -Days 1)).ToString("yyyy MM"). See Microsoft Docs on New-TimeSpan and this StackOverflow question on formatting dates

EDIT: hcm's answer leads to a better response here; instead of using the New-Timespan method above, modify my suggested code above to

$foldername = (Get-Date).AddMonths(-1).ToString("yyyy MM")

This eliminates the requirement that the supporting code be run on the first of the month.

Jeff Zeitlin
  • 9,773
  • 2
  • 21
  • 33
  • Thank you for you answer! This answer would work and definitely answers my question. My script is set to run on the first of each month via Task Scheduler, but the other answers don't require the date to be the first of the month. This makes them a bit more fool-proof if my coworkers decide to adopt my script. Thanks again for your contribution! – Evan Dec 20 '19 at 14:21