
Windows PowerShell solution to Event 4 in the 2008 Winter Scripting Games.
You know, if the Scripting Guys could follow their own instructions they would have been able to complete their solutions for the Scripting Games much faster and much easier. For example, the instructions for Event 4 clearly show that the output is supposed to include the month and year:
February 2008
Sun Mon Tue Wed Thu Fri Sat
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29
So did the solution the Scripting Guys come up with include the month and the year in its output? Do you even need to ask?
Fortunately, though, that was a minor problem. Even more fortunate, the Scripting Guys had once written an article on formatting dates in Windows PowerShell. Because of that, they knew exactly how to retrieve the name of the month for a given date. For example, this line of code retrieves the value January:
$strMonth = Get-Date("1/1/2008") -uformat %B
And once they figured that out they could easily add code to print the month and year as part of the displayed calendar.
Best of all, no one will ever know that the Scripting Guys came this close to screwing up.
Here’s the solution we came up with:
$intMonth = read-host "Please enter the month"
$intYear = read-host "Please enter the year"
$intDay = 1
$strDate = "$intMonth/1/$intYear"
$dtmStartDate = Get-Date($strDate)
$strCalendar = "Sun Mon Tue Wed Thu Fri Sat`n"
$strSunday = "`n "
$strMonday = " "
$strTuesday = " "
$strWednesday = " "
$strThursday = " "
$strFriday = " "
$strSaturday = " "
$strFirstDay = $dtmStartDate.DayOfWeek
switch ($strFirstDay){
"Sunday" {$strSpacer = $strSunday}
"Monday" {$strSpacer = $strMonday}
"Tuesday" {$strSpacer = $strTuesday}
"Wednesday" {$strSpacer = $strWednesday}
"Thursday" {$strSpacer = $strThursday}
"Friday" {$strSpacer = $strFriday}
"Saturday" {$strSpacer = $strSaturday}
}
$strCalendar = [string] $strCalendar + $strSpacer + $intDay
If ($strFirstDay -eq "Saturday")
{$strCalendar = $strCalendar + "`n"}
do
{
$dtmStartDate = $dtmStartDate.AddDays(1)
$intDay = $intDay + 1
$intMonthCheck = $dtmStartDate.Month
if ($intMonthCheck -ne $intMonth)
{$strCalendar;break}
$intNextDay = $dtmStartDate.DayOfWeek
if ($intNextDay -eq "Sunday")
{$strSpacer = " "}
else
{$strSpacer = " "}
If ($intDay -lt 10)
{$strCalendar = $strCalendar + " " + $strSpacer + $intDay}
Else
{$strCalendar = $strCalendar + $strSpacer + $intDay}
if ($intNextDay -eq "Saturday")
{$strCalendar = $strCalendar + "`n"}
}
until ($x -eq 1000)
So how do you create a nicely-formatted calendar in a console window? To tell you the truth, when we first tried our hand at this event we had absolutely no idea. Were we able to come up with something? We’re about to find out.
To begin with, we use the Read-Host cmdlet to prompt the user to enter the month (using a number between 1 and 12) followed by a year; that’s what these two lines of code are for:
intMonth = read-host "Please enter the month" $intYear = read-host "Please enter the year"
Because all months (as far as we know, anyway) start with day 1, we next set the value of a variable named $intDay to 1. Once we’ve done that, we can create a date string by combining the variables $intMonth, $intDay, and $intYear, along with a couple of well-placed /’s:
$strDate = "$intMonth/$intDay/$intYear"
With $strDate equal to a value like 2/1/2008, we can then use the Get-Date cmdlet to retrieve a date-time object corresponding to February 1, 2008:
$dtmStartDate = Get-Date($strDate)
Once we have a date-time object corresponding to the first day of the month, well, at that point we’re ready to get down to work.
According to the event instructions, our calendar must include a header row (the month and year) followed by a row that lists the days of the week. We set up the header rows by using these lines of code:
$strMonth = Get-Date($strDate) -uformat %B $strCalendar = $strMonth + " " + $intYear + "`n`n" $strCalendar = $strCalendar + "Sun Mon Tue Wed Thu Fri Sat`n"
Now comes the tricky part. (Or at least one of the tricky parts.) If we want our calendar to be formatted correctly we have to make sure that the first day of the month gets put in the proper spot; after all, if the first day is wrong then everything else is going to be wrong. That’s where this block of code comes in:
$strSunday = "`n " $strMonday = " " $strTuesday = " " $strWednesday = " " $strThursday = " " $strFriday = " " $strSaturday = " "
All we’re doing here is assigning a bunch of blank spaces to a set of variables. As you can probably guess, each variable contains just enough blank spaces to make sure that the first day of the month ends up in the proper spot. For example, suppose the first day of the month falls on a Wednesday. In that case, we’ll use the variable $strWednesday, and by combining $strWednesday and the value 1 (for day 1) our calendar will start off looking like this:
October 2008
Sun Mon Tue Wed Thu Fri Sat
1
As you can see, day 1 is just exactly where it should be.
Of course, that leads to another problem: how do we know which of these variables to use? Actually that’s easy: the variable we use depends on the day of the week for the first day in the month. And that’s something we can determine from the DayOfWeek property:
$strFirstDay = $dtmStartDate.DayOfWeek
After we store the day of the week in the variable $strFirstDay we use the following switch block to assign the correct value to the variable $strSpacer:
switch ($strFirstDay){
"Sunday" {$strSpacer = $strSunday}
"Monday" {$strSpacer = $strMonday}
"Tuesday" {$strSpacer = $strTuesday}
"Wednesday" {$strSpacer = $strSWednesday}
"Thursday" {$strSpacer = $strThursday}
"Friday" {$strSpacer = $strFriday}
"Saturday" {$strSpacer = $strSaturday}
}
And once we’ve done that we can add the required blank spaces and day 1 to the calendar using this line of code:
$strCalendar = [string] $strCalendar + $strSpacer + $intDay
If we’re putting together a calendar for February, 2008 that means $strCalendar will be equal to this:
February 2008
Sun Mon Tue Wed Thu Fri Sat
1
And yes, that’s exactly what it should be equal to.
Next we need to check to see if the first day of the month happens to fall on a Saturday:
If ($strFirstDay -eq "Saturday")
{$strCalendar = $strCalendar + "`n"}
Why do we need to do this? Well, if day 1 falls on a Saturday, that means that day 2 falls on a Sunday; that also means that day 2 must be put on the next row in our calendar. Therefore, if day 1 is a Saturday we use this line of code to add a carriage return-linefeed to the calendar string:
{$strCalendar = $strCalendar + "`n"}
Now it’s time to add the remaining days of the month. To do that, we first set up a do loop designed to run until $x is equal to 1000; because $x will never be equal to 1000 this causes the loop to continue running until we tell it to stop. Inside the loop, the first thing we do is use the AddDays method to add a day to the date-time value $dtmStartDate:
$dtmStartDate = $dtmStartDate.AddDays(1)
After incrementing the counter variable $intDay by 1 (because we’re now on day 2 of the month) we use this line of code to see if we’re still in the same month as the month we began with:
$intMonthCheck = $dtmStartDate.Month
if ($intMonthCheck -ne $intMonth)
{$strCalendar;break}
For day 2 this will obviously be true. As we near the end of the month this might not be true. For example, if we add one day to February 29, 2008 we don’t get February 30, 2008; instead we get March 1, 2008. At that point the months differ. That means we’ve completed the calendar for February, 2008; that’s why we echo back the value of $strCalendar and then use the break command to exit the do loop.
Note. Yes, we know. It was only after we finished this solution that we remembered that the System.Globalization.Calendar class includes a nifty little function named GetDaysInMonth. That would have been a much more elegant way of solving this problem. But, then again, when was the last time you saw “Scripting Guys” and “elegant” in the same sentence? |
Like we said, this isn’t an issue the first time through the loop: day 2 will always be in the same month as day 1. Consequently, we use this line of code to determine the day of the week for day 2:
$intNextDay = $dtmStartDate.DayOfWeek
Why do we need to know that? Well, take a look at a sample calendar:
Sun Mon Tue Wed Thu Fri Sat 1 2 3 4 5 6 7
As you can see, we have an equal number of spaces between Sunday’s date (1) and Monday’s date (2), the same number of spaces we have between Monday’s date and Tuesday’s date, and Tuesday’s date and Wednesday’s date and – well, you get the idea. There’s one exception, however: Sunday’s date is indented only 2 spaces. (There’s just two spaces from the edge of the calendar to the 1.) Because of that we need to determine whether the current day of the month (day 2) falls on a Sunday. If it does, we need to use a different number of blank spaces when we go to separate dates. That’s what this block of code does for us:
if ($intNextDay -eq "Sunday")
{$strSpacer = " "}
else
{$strSpacer = " "}
That takes care of the “Sunday problem.” Now we have another problem. After you get to the 10th of the month each day requires two spaces in the calendar; days 1 through 9 require only one space:
February 2008
Sun Mon Tue Wed Thu Fri Sat
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
That means we need to add a blank space to the front of each single digit day; if we don’t, we’ll end up with a calendar that looks something like this:
February 2008
Sun Mon Tue Wed Thu Fri Sat
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
That’s not exactly what we had in mind. Therefore, we use this block of code to check the value of the current day in our calendar:
if ($intDay -lt 10)
{$strCalendar = $strCalendar + " " + $strSpacer + $intDay}
else
{$strCalendar = $strCalendar + $strSpacer + $intDay}
As you can see, if the value of $intDay is less than 10 we add an extra blank space to the front of the day; that’s what " " + $strSpacer does. Otherwise, we just add blank spaces using the variable $strSpacer.
The net result? Our calendar for February, 2008 will now look like this:
February 2008
Sun Mon Tue Wed Thu Fri Sat
1 2
And you’re right: we do have another issue to contend with, don’t we? Because February 2nd falls on a Saturday, we need to place February 3rd on the next line in the calendar. How are we going to do that? Well, first we check to see if the current day happens to fall on a Saturday:
if ($intNextDay -eq "Saturday")
If it does, we simply add a carriage return-linefeed to the calendar string:
{$strCalendar = $strCalendar + "`n"}
And then it’s back to the top of the loop, where we repeat this process with the next day in the month.
When all is said and done, our calendar for February, 2008 should look like this:
February 2008
Sun Mon Tue Wed Thu Fri Sat
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29
Just think: thanks to Event 4 in the 2008 Winter Scripting Games you’ll never have to buy another calendar. Ever!