Friday Fun: Get-Anniversary

Recently I celebrated a wedding anniversary. Even though I didn’t forget I could have used a little help. So I figured since I’m in PowerShell all the time anyway, it could help. I built a little script to remind me of important dates. Let me walk you through the key steps.

First, I’ll define a variable for the anniversary date.

[datetime]$anndate="5/6/2007"

I picked a date coming up. Next, it isn’t too difficult to calculate the number of days between two dates. But what I needed to do is find May 6th for this year. Here’s how I did it:

[datetime]$thisYear = "$($anndate.month)/$($anndate.day)"

$thisYear is now May 6, 2013. Excellent, because now I can get the number of days until that date.

$when = $thisYear - (Get-Date)

$when is a TimeSpan object.

Days              : 23
Hours             : 11
Minutes           : 25
Seconds           : 42
Milliseconds      : 300
Ticks             : 20283423003384
TotalDays         : 23.4761840316944
TotalHours        : 563.428416760667
TotalMinutes      : 33805.70500564
TotalSeconds      : 2028342.3003384
TotalMilliseconds : 2028342300.3384

I can use the Days property in my message. Although the other item of information I wanted is the number of years for the anniversary. Very important. Again, because we’re working with objects all I need to do is subtract the Year properties between the anniversary date and today.

$NumYears = (Get-Date).year - $anndate.Year

At this point I could simply display a message that tells me how many days are remaining until anniversary #X. But let’s go a step further. How about displaying the number of years as an ordinal? For example, 5th or 23rd anniversary. To do that I did a quick search to see if someone had already figured this out, since there is no .NET specific method to accomplish this. I found some code samples and converted them into a PowerShell function.

Function Get-Ordinal {

Param([int]$i)

Switch ($i %100) {
 #handle special cases
 11 {$sfx = "th" } 
 12 {$sfx = "th" } 
 13 {$sfx = "th" } 
 default {
    Switch ($i % 10) {
        1  { $sfx = "st" }
        2  { $sfx = "nd" }
        3  { $sfx = "rd" }
        default { $sfx = "th" }
    } #inner switch
 } #default
} #outerswitch
 #write the result to the pipeline
 "$i$sfx"
} #end Get-Ordinal

Basically you take the number and perform a modulo operation. Based on the result I know what suffix to use and the function writes the ordinal string to the pipeline, like 1st or 7th.  With that, all that is left to do is display my message.

$msg = "Your {0} anniversary is in {1} days." -f (Get-Ordinal $NumYears),$when.Days

Let me show you the complete script.

Param (
#what is the anniversary date
[datetime]$anndate="5/6/2007"
)

#a function to create ordinal numbers
Function Get-Ordinal {

Param([int]$i)

Switch ($i %100) {
 #handle special cases
 11 { $sfx = "th" } 
 12 { $sfx = "th" } 
 13 { $sfx = "th" } 
 default {
    Switch ($i % 10) {
        1  { $sfx = "st" }
        2  { $sfx = "nd" }
        3  { $sfx = "rd" }
        default { $sfx = "th"}
    } #inner switch
 } #default
} #outerswitch
 #write the result to the pipeline
 "$i$sfx"
} #end Get-Ordinal

#how many years
$NumYears = (Get-Date).year - $anndate.Year

#create the anniversary date for this year
[datetime]$thisYear = "$($anndate.month)/$($anndate.day)"

#is anniversary next year?
if ($thisYear -lt (Get-Date)) {
  #add a year"
  $thisYear=$thisYear.AddYears(1)
  $NumYears++
}

#how soon is the anniversary?
$when = $thisYear - (Get-Date)

#define an empty hashtable for parameters
$phash = @{}

if ($when.Days -gt 0) {
  $msg = "Your {0} anniversary is in {1} days." -f (Get-Ordinal $NumYears),$when.Days
  $phash.Add("Object",$msg)
  $phash.Add("Foregroundcolor","Green")
}
else {
  $msg = "Your {0} anniversary is in {1} hours and {2} minutes!" -f (Get-Ordinal $NumYears),$when.hours,$when.minutes
  $phash.Add("object",$msg)
  $phash.Add("Foregroundcolor","Red")
  #Find a florist!!
  start "http://www.google.com/#hl=en&output=search&q=florist"
}

Write-Host @phash

The only other special feature of this script is that if the number of days is greater than one, the message is display using Write-Host in green.

get-anniversary

BUT, if you are down to 1 day or less you get the message in red AND your browser will open up to a Google search for Florists. I’m trying to help you out as much as I can. You could call this script from your PowerShell profile and (hopefully) never forget another anniversary or important date. Or you might simply take away some tidbits about datetime objects, timespans and splatting. Either way I think you come out ahead.

Enjoy.

UPDATE April 16, 2013
I realized my original code had a problem: if the anniversary date was next year you would get a negative result. For example if the anniversary was yesterday, the script would say your anniversary was in -1 days. That won’t work. So I added some code to test the anniversary date compared to the current date. If it is less than today, meaning it has already passed, then I need to add a year.

if ($thisYear -lt (Get-Date)) {
  #add a year"
  $thisYear=$thisYear.AddYears(1)
  $NumYears++
}

Now when I calculate the difference between $thisYear and today, I’ll get a positive number. I suppose I should rename the variables because $thisYear, in this case, is actually the date for the anniversary next year. I’m also incrementing the value of the number of years is updated.

The other thing to take away from this, and something that I neglected to do (shame on me) is to test based on data that will fail as well as succeed. For this script, I neglected to test for dates that have already passed.

About the Author

PowerShell.org Announcer

This is the official account for PowerShell.org and sponsor announcements.