
Windows PowerShell solution to Event 5 in the 2008 Winter Scripting Games.
To successfully complete this event you needed to learn a little bit about working with dates in Windows PowerShell, including using some of the date functions found in .NET. You also needed to be able to read a command-line argument into your script. There were probably several different ways to do this; here’s what we came up with:
$dtoday = Get-Date -format d
$ddate = [datetime]$args[0]
$dtoday = Get-Date $dtoday
if ($dtoday -gt $ddate)
{
"Date entered is earlier than today."
}
else
{
$a = $ddate.subtract($dtoday)
"Days: " + $a.days
$years = $ddate.year - $dtoday.year
$months = $ddate.month - $dtoday.month
if ($months -lt 0)
{
$months = 12 + $months
$years--
}
if ($years -gt 0)
{
$months = $months + (12 * $years)
}
"Months: " + $months
if ($ddate.Day -lt $dtoday.Day)
{
$months--
}
$dTotal = $dtoday.AddMonths($months)
"Months/Days: " + ($months) + "/" + ($ddate.subtract($dTotal)).Days
}
The object of this event was to compare two dates: today’s date and a date input as a command-line parameter to the script. So we start by calling the Get-Date cmdlet to retrieving today’s date:
$dtoday = Get-Date -format d
Normally Get-Date will return the date and time, like this:
Thursday, January 31, 2008 11:42:06 AM
We’re interested in only the date, so we use the –format parameter with a value of “d”, which give us the date in short date format. In the US the preceding date would look like this:
1/31/2008
For more on the options to the –format parameter check MSDN or download the Windows PowerShell Graphical Help.
Now that we have today’s date, we need to retrieve the date input on the command line. We do that with this line:
$ddate = [datetime]$args[0]
In Windows PowerShell, command-line arguments are stored in an array named $args. The first item in that array, the item at index number 0, is the first argument. In our case that will be the date entered. By default arguments are read in as strings. We want PowerShell to consider this string a date, so we “cast” the value to a datetime data type by preceding it with [datetime].
Now we do something that might seem a little odd:
$dtoday = Get-Date $dtoday
There’s probably a better way to do this, but this is what we came up with. When we start calling functions to calculate dates, those functions actually use the date and the time. If we pass the date in the short date format, the functions don’t work properly. Now, our first thought was to not format the date as a short date and simply leave the full date and time. Here’s the problem we ran into with that. The date that is entered from the command line doesn’t include a time, so when we cast that string to a datetime data type a time of midnight (12:00:00 AM) is automatically added. If we were to compare that to a date with a time of anything past 12:00 AM, the day calculations will be off by one. For example, if today is February 20, 2008 at 2:00:00 PM and we enter a date of February 21, 2008 at the command line, the difference in days between these two dates will be zero. Why? Because there isn’t a full day between 2:00 PM on the 20th and midnight on the 21st. In order to get output of a full day, we need to set the time for today’s date to midnight. We do that by turning our short date back into a full date. The easiest way to do that is to simply call Get-Date again, passing it the variable ($dtoday) holding our short date.
Like we said, that was sort of a roundabout way of doing things, but it worked. Chances are other people found other ways, maybe more straightforward ways, to make that work. But this is what we did.
Now we can get down to the business of comparing dates. We said in the instructions that when we tested the entries to this event the date we used at the command line would always be later than today’s date. Because our script doesn’t handle dates earlier than today, we put in this if statement:
if ($dtoday -gt $ddate)
{
"Date entered is earlier than today."
}
Here we’re checking to see if today’s date ($dtoday) is greater than (-gt) the input date ($ddate). If it is, we display an error message and end the script. You didn’t need to do this to get credit for this event; we added it just to be thorough.
The first bit of information we wanted was the number of days between our two dates. To find this, we used the DateTime object’s Subtract method:
$a = $ddate.subtract($dtoday)
We’re calling Subtract on the input date and passing today’s date, meaning we’re subtracting today’s date from the input date. The Subtract method returns a TimeSpan object. We use the Days property of this object to display the number of days between the two dates:
"Days: " + $a.days
Note: As an alternative, we could have done this using the New-TimeSpan cmdlet, like this: $a = New-TimeSpan -start $dtoday -end $ddate "Days: " + $a.days Here we’re passing New-TimeSpan the starting date (specified with the –start parameter) and the ending date (in the –end parameter) of our time span and placing the results in the variable $a. This gives us a TimeSpan object, and we can once again display the number of days within that timespan by reading the Days property. |
Next we find the difference in years between the two dates:
$years = $ddate.year - $dtoday.year
We’ll use this value in a moment when we calculate the number of months between the two dates.
And speaking of months… We were able to easily retrieve the difference in days because the TimeSpan object has a Days property. Unfortunately the TimeSpan object doesn’t have a Months property. Because of that we need to figure it out ourselves. We start by subtracting the month of today’s date from the month of the input date:
$months = $ddate.month - $dtoday.month
Keep in mind we’re simply subtracting month numbers; years aren’t included. That means that if today is February (month 2) and the input date is January (month 1), the result of this equation is -1. So even if we input a year of 2010, the difference between the months will still be -1. Now, we know there are more than -1 months between February 2008 and January 2010. This is how we figure out just how many:
if ($months -lt 0)
We first see if the difference between the months is less than (-lt) 0. If it is we add 12 to the number of months:
$months = 12 + $months
What does this do? Well, it counts forward in time rather than backwards. If it’s February now, the next January will be 11 months away. And that’s what the result of our equation is (12 + -1). Having done this, we’ve essentially moved into the next year. Because of that, we need to subtract one from the difference in years between the two years:
$years--
Note that if the input month is greater than or equal to today’s month we don’t need to do anything because we’re already counting forward in time.
Note: In Windows PowerShell, two minus signs (--) is shorthand for “subtract 1.” So this: $years-- is the same as this: $years = $years - 1 |
We’ve now solved part of our problem. However, in our example our input date was 2010, and January 2010 is more than 11 months away from February 2008. That means we need to check the year:
if ($years -gt 0)
If the difference in years between our input date and today is greater than 0, we need to add 12 months for each year, like this:
$months = $months + (12 * $years)
We’ve multiplied 12 by the number of years (12 months for each year) and added that to the number of months. Let’s take a look at our example once more. Today is February 20, 2008; the input date is January 15, 2010:
$months = $ddate.month - $dtoday.month
# $months = -1
if ($months -lt 0)
{
$months = 12 + $months
# $months = 11
$years—
# $years = 1
}
if ($years -gt 0)
{
$months = $months + (12 * $years)
# $months = 11 + (12 * 1)
# $months = 23
}
We can now display the number of months between the two dates (in this example, 23):
"Months: " + $months
We now have the number of days between two dates, and the number of months. Believe it or not, we haven’t reached the tricky part yet: now we get to figure out how many months plus days there are between the two dates.
Remember when we worked with months we had to make sure we went forward in time rather than back? Well, we need to do the same thing with days:
if ($ddate.Day -lt $dtoday.Day)
{
$months--
}
If the day of the input date is less than the day of today’s date, we need to subtract 1 from the number of months. Why? Because if today is February 20 and the input date is March 3, there isn’t an entire month between those two dates.
Next we add the number of months to today’s date:
$dTotal = $dtoday.AddMonths($months)
This puts today’s date within less than one month of the input date. For example, if today is February 20, 2008 and the input date is April 3, 2008, $months will be equal to 1. Using the AddMonths function we add 1 to today’s date to get March 20, 2008. Now all we need to do is call the Subtract function again to find the difference between the dates, and call the Days function again to retrieve the number of days in the resulting TimeSpan:
($ddate.subtract($dTotal)).Days
Notice that last time we did this we used two lines:
$a = $ddate.subtract($dtoday) "Days: " + $a.days
This time we combined to function call and the property retrieval in one. Kind of a handy way to do things sometimes.
And that’s it. All we have to do is display the Months/Days information:
"Months/Days: " + ($months) + "/" + ($ddate.subtract($dTotal)).Days
If today is February 20, 2008 and we input February 21, 2009, here are our results:
Days: 367 Months: 12 Months/Days: 12/1