Summary: Microsoft PFE, Michael Frommhold, uses Windows PowerShell to get the number of days between two dates as he solves Beginner Event 7 in one line of code.
Microsoft Scripting Guy, Ed Wilson, here. Michael Frommhold is the author for the expert commentary on Beginner Event 7. Michael has been a PFE in Germany since 2007 with expertise in these technologies: Active Directory, development, and platform.
Let’s see how we can solve Beginner Event 7.
Worked solution
First, it looks like we have to deal with dates—so let’s inspect dates in Windows Powershell. Handling with dates in Windows Powershell implements the usage of DateTime objects. Why? When using the Get-Date cmdlet, we receive a DateTime object.
Test:
Get-Date("7/31/11") | Get-Member
Output: TypeName: System.DateTime
Name MemberType Definition
---- ---------- ----------
…
Subtract Method System.TimeSpan Subtract(System.DateTime value)
…
As you can see, we found a method called Subtract. It seems to be very useful for our task, so let’s try to do the date math with this method of the DateTime object. We declare two DateTime objects $DeadLine and $Today and fill them with appropriate data.
$DeadLine -> As defined by our boss, we set the DateTime to July 31.
$Today -> The Get-Date cmdlet, called without any parameters, will return the actual date.
[DateTime] $DeadLine = Get-Date("7/31/11")
[DateTime] $Today = Get-Date
$DeadLine.Subtract($Today) | Get-Member
Output: TypeName: System.TimeSpan
Name MemberType Definition
---- ---------- ----------
…
Days Property System.Int32 Days {get;}
…
Using the Substract method of a DateTime object returns a TimeSpan object with the useful property Days. What do we get when we call this property today (3/29/11)?
Write-Host $DeadLine.Subtract($Today).Days
Output: 123
It already looks like we are close to the final solution. We just pass the info to the Windows PowerShell command line and we are finished.
Write-Host "There are $DeadLine.Subtract($Today).Days days until the end of the fiscal year"
Output: There are 07/31/2011 00:00:00.Subtract(03/29/2011 10:03:58).Days days until the end of the fiscal year
Unfortunately, it does not look like what were expecting. The problem is that Write-Host doesn’t know that $DeadLine.Subtract($Today).Days should be treated as a variable, so we must set the variable identifier $ first.
Write-Host "There are $($DeadLine.Subtract($Today).Days) days until the end of the fiscal year"
Output: There are 123 days until the end of the fiscal year
Because we have seen that the Substract method of the DateTime object returns a TimeSpan object, we can shorten our code by directly instantiating a TimeSpan object with the desired end date. Remember to add the variable identifier $ to our DateTime object Get-Date.
[TimeSpan] $Delta = New-TimeSpan -End $(Get-Date("7/31/11"))
Write-Host "There are $($Delta.Days) days until the end of the fiscal year"
Output: There are 123 days until the end of the fiscal year
Wouldn’t it be nice to place the code in a ps-file and pass the deadline and the milestone as command line arguments? There are two ways to accomplish this: a quick n’ dirty one and an elegant one. I will show the quick n’ dirty method first.
Quick n’ dirty solution
Use the numbered items of the command-line arguments array $args, and hope the arguments will be given in the right order.
[DateTime] $End = Get-Date $args[0]
[String] $MileStone = $args[1]
Write-Host "There are $((New-TimeSpan -End $End).Days) days until $MileStone"
Call: deadlinereminder.ps1 7/31/11 "the end of the fiscal year"
Output: There are 123 days until the end of the fiscal year
Because we need a one-liner, we can shorten this to the following code.
Write-Host("There are {0} days until {1}” –f (New-TimeSpan -End $(Get-Date $args[0])).Days, $($args[1]))
Call: deadlinereminder.ps1 7/31/11 "the end of the fiscal year"
Output: There are 123 days until the end of the fiscal year
To be honest, the string that passed into Write-Host cmdlet doesn’t look really readable. Let’s use the f switch (f = Format) to make this more readable. The numbers in curly brackets ({0} and {1}) are placeholders for the variables that follow the f:
Write-Host("There are {0} days until {1}” –f (New-TimeSpan -End $(Get-Date $args[0])).Days, $($args[1]))
Call: deadlinereminder.ps1 7/31/11 "the end of the fiscal year"
Output: There are 123 days until the end of the fiscal year
Elegant solution
Define named command line arguments—we need not care about the correct order of the passed arguments. For the sake of reusability, we put the date math code in a function and call the function with the passed and properly translated values.
Name the command line arguments (EndDate and MileStone).
Param([String] $EndDate = "EndDate", [String] $MileStone = "MileStone")
Declare the date math function DeadLine.
Function DeadLine([DateTime] $End, [String] $MileStone)
{
Write-Host("There are {0} days until {1}" -f (New-TimeSpan -End $End).Days, $MileStone)
}
Translate the command line argument EndDate to a DateTime object, and call date math function.
DeadLine (Get-Date $EndDate) $MileStone
Put it all together.
--------------------------------------------------------------------------------------
# Command line parsing using named arguments
Param([String] $EndDate = "EndDate", [String] $MileStone = "MileStone")
# Function doing the date math
Function DeadLine([DateTime] $End, [String] $MileStone)
{
Write-Host("There are {0} days until {1}" -f (New-TimeSpan -End $End).Days, $MileStone)
}
# Main code
DeadLine (Get-Date $EndDate) $MileStone
--------------------------------------------------------------------------------------
Call: deadlinereminder.ps1 –EndDate 7/31/11 –MileStone "the end of the fiscal year"
Output: There are 123 days until the end of the fiscal year
Thank you Michael. I really appreciate you taking the time to write this solution.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
Hello Michael,
I probably could have written in german 🙂
Well, as everybody might know in between, I should rename to Mr.Ed … oh, no! This one's already taken … Mr.Error 🙂
And really I just copied the solution and saved it to disk and called it, like you proposed:
PS F:UsersKlausSG2011> ./expert_be7.ps1 –EndDate 7/31/11 –MileStone "the end of the fiscal year"
Get-Date : Der Parameter "Date" kann nicht gebunden werden. Der Wert "7/31/11" kann nicht in den Typ "System.DateTime" konvertiert wer
den. Fehler: "Die Zeichenfolge wurde nicht als gültiges DateTime erkannt."
The judges might have said: This is not a working solution … no stars!
Of course, the solution does work in the USA and other countries using american date formats!
So nobody might have seen anything bad in it.
I really tried to make my solution work with any date format and used the approach to pass strings in
and try to convert them to localized DateTime values, like that:
if (!($firstDate = $firstDay -as [DateTime]))
{
return "can't convert firstDay: '$firstDay' to a DateTime"
}
This works like charm, but it might have even been better to allow DateTimes to be passed as parameter, too!
Well besides the localization problem the solution doesn't provide any error handling and I know, that the first
thing that our users would enter into the pretty new function might be something like:
./expert_be7.ps1 –EndDate Friday –MileStone "WOW! Only few days til the end of this week!"
which will produce the same error as above of course!
Another nice feature might have been to provide an additional parameter: -StartDate that could be used to
provide an alternative to today's date at nearly no cost!
BUT: The worst aspect of the solution is, in my opinion, the fact that you do provide default values but these values
will cause the same error as above on themselves!
Param([String] $EndDate = "EndDate", [String] $MileStone = "MileStone")
Setting $EndDate to a default of "EndDate" is really a bad idea! You might have used "12/31/2011" or any other
reasonable default but not the string "EndDate"!
Providing default values that will raise errors is really a "no go" imho.
kind regards, Klaus
Yeah, but what happens when it ends up only being 1 day? I think mine was the only one that corrected for grammar with it changing the noun and verb to "There is 1 day left" instead of the usual "There are 1 days left". Your one-liner is much more elegant than mine. I saw the subtract but didn't investigate any farther on it to find the timespan.
I can see that many people used the -f switch to format the output. My question is how they discovered the switch. I have looked at the help for write-host and I do not see that anywhere. Perhaps an article about finding help would clear things up for me.
Thanks