2

I'm currently writing a PowerShell script for backup tapes and trying to calculate return dates of the tapes. Tapes are picked up and returned by our provider on Fridays. Retention periods are 4 weeks, 12 months and 3 years. Monthly tapesets fall on the first Monday of the month so I'm currently working with a monthly tapeset, which will return 12 months from now. How would I calculate 12 months from Friday 10/08/2021, but make sure the return date is also a Friday?

#Calculate Friday - Vaulting Date
$Friday = (Get-Date).AddDays(5-((Get-Date).DayOfWeek.value__)).ToString("MM/dd/yyyy")

#Calculate Return Dates
$Weekly = (Get-Date).AddDays(5-((Get-Date).DayOfWeek.value__)).AddDays(28).ToString("MM/dd/yyyy")
$Monthly = (Get-Date).AddDays(5-((Get-Date).DayOfWeek.value__)).AddMonths(12).ToString("MM/dd/yyyy")
$Yearly = (Get-Date).AddDays(5-((Get-Date).DayOfWeek.value__)).AddYears(3).ToString("MM/dd/yyyy")

$Monthly currently returns 10/08/2022 which is the Saturday after the date I want. I presume I'd run into the same problem with $Yearly as well, and what if it's a leap year? Any assistance would greatly be appreciated.

TechFA
  • 129
  • 1
  • 9
  • As an aside: you don't need the `.value__` property in your method calls, because PowerShell _automatically_ converts enumeration values such as `Sunday` (from enumeration [`System.DayOfWeek`](https://learn.microsoft.com/en-US/dotnet/api/System.DayOfWeek)) to their underlying numbers (integers). – mklement0 Oct 05 '21 at 16:35
  • Another aside: It's best to lock in the starting date with a _single_ `Get-Date` call, whose result is stored in a variable, which can then be used in later calculations. Aside from being more efficient, it also avoids problems - even though largely hypothetical - of unwittingly crossing calendar-day or DST-transition boundaries between `Get-Date` calls. – mklement0 Oct 05 '21 at 16:50

2 Answers2

3

First add one year:

$date = Get-Date 10/08/2021
$date = $date.AddYears(1)

Then keep adding one day at a time until you reach a Friday:

while($date.DayOfWeek -ne 'Friday'){
  $date = $date.AddDays(1)
}

Finally, convert to the desired string format (don't convert to a string until you're done calculating your date, you won't be able to do datetime math with it :) ):

$date.ToString('MM/dd/yyyy')
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
2

You were on the right track, except:

  • Your $someDate.AddDays(5-($someDate.DayOfWeek)) method of finding the Friday that falls into the same week as $someDate must be applied after the .AddMonths(), ... calls, i.e. to the results of the future-date-by-offset values.

    • Note: Due to basing your calculation on the System.DayOfWeek enumeration, which ranges from value 0 (Sunday) to value 6 (Saturday), the implication is that, relative to the given date:

      • For Sunday through Thursday, you'll find the following Friday.
      • For Friday, you'll find that day itself.
      • For Saturday, you'll find the previous Friday, i.e. the day before.
  • Generally, as Mathias points out in his answer, you should work with [datetime] instances while performing date calculations and apply formatting (conversion to string representations) only to the calculation results.

    • Note: [datetimeoffset] would even be better, due to unambiguously denoting absolute points in time;[1] while you can work with this .NET type directly, PowerShell itself unfortunately doesn't offer first-class support for it (yet), such as via Get-Date; several feature requests are pending as of PowerShell 7.2 - see this GitHub search.

To put it all together:

# Get today's date without a time-of-day component (via .Date)
# Note: Not strictly necessary, if you use the results only as
#       formatted date-only strings.
$today = (Get-Date).Date

# Find this week's Friday.
$thisFriday = ($today).AddDays(5-($today.DayOfWeek))

# Add an offset.
$futureDate = $thisFriday.AddYears(1)

# Find the future date's adjacent Friday.
$futureFriday = $futureDate.AddDays(5-$futureDate.DayOfWeek)

# Output the result via an aux. object that results in tabular display.
[pscustomobject] @{
  ThisFriday = $thisFriday.ToString('MM/dd/yyyy')
  FutureFriday = $futureFriday.ToString('MM/dd/yyyy')
}

You'll see output such as the following:

ThisFriday FutureFriday
---------- ------------
10/08/2021 10/07/2022

[1] [datetimeoffset] is also DST-aware (aware of daylight-saving time) when performing calculations, whereas [datetime] appears not to be. This means that even when adding full days to [datetimeoffset] instances, a calendar-day boundary may be crossed if there is an intervening DST transition.

mklement0
  • 382,024
  • 64
  • 607
  • 775