Expert Solution for 2011 Scripting Games Beginner Event 7: Use PowerShell to Get the Number of Days Between Dates

Expert Solution for 2011 Scripting Games Beginner Event 7: Use PowerShell to Get the Number of Days Between Dates

  • Comments 3
  • Likes

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.

Photo of Michael Frommhold

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

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • 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:\Users\Klaus\SG2011> ./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