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.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
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.
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.
The biggest problem with this code (and it seems a common flaw in date code you see on the Internet) is that it’s date format specific. I’d guess you’re North American, so you use m/d/y, but if you were Australian like me, you’d use d/m/y – and most of the world uses some format other than m/d/y.
For the first line, you’re better off using $anndate=(Get-Date “28/03/2004”), as it will then use your computer’s date format specifier (using [datetime] annoyingly forces m/d/y, which much of the world doesn’t use). Get-Date uses the culture settings you have set.
Similarly, to get the date of the anniversary for this year, without using a format specific date, you could use something like $thisyear=$anndate.AddYears(((Get-Date).year – $anndate.year))
Presto, your code is now date format agnostic. Other than putting in the original date in a format that’s correct for the culture settings on the computer.
Thanks for the comment. Whenever I write code with date time formats I am thinking that it has a North American bias, but I assume people will adjust accordingly. I tried your suggestion but Get-Date “28/03/2004” gives me an error that the string is not recognized as a valid datetime. But it should work for you. You should be able to use Get-Date X where X is a culture-appropriate datetime format. Definitely something I should continue to bear in mind.
Actually, better again for getting this year’s anniversary: $thisyear = Get-Date $anndate -year (Get-Date).year