
Windows PowerShell solution to Event 4 in the 2008 Winter Scripting Games.
Solutions are also available for Windows PowerShell and Perl.
As far as the Scripting Guys were concerned, this turned out to be a fairly easy script to write. Granted there were a few false starts until we got the spacing just right; in fact, our first few tries tended to produce calendars that looked like this:
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 30 31
Interesting, maybe even a little avant-garde. But not exactly in the spirit of the event.
Fortunately, however, once we got the spacing down the rest was easy.
Or so we thought. It was only after this solution had been written (and was almost ready to be published) that they realized something: they forgot to include the month and the year at the top of the calendar. That necessitated adding a couple lines of code to the script, and required a few minor changes to this solution.
Note. Does that mean that the Scripting Guys should read the event instructions before they try to write a solution to that event? Heck no. After all, we had to write the event instructions; it hardly seems fair that we should have to read those instructions as well. |
That was a little bit of a hassle but, compared to the way some of these scripts went (see Event 1 for a prime example), this was pretty minor.
Here’s a script that displays a formatted calendar onscreen:
intMonth = InputBox ("Please enter the month: ")
intYear = InputBox ("Please enter the year: ")
intDay = 1
dtmStartDate = CDate(intMonth & "/" & intDay & "/" & intYear)
strMonthName = MonthName(intMonth)
strCalendar = strMonthName & " " & intYear & vbCrlf & vbCrLf
strCalendar = strCalendar & "Sun Mon Tue Wed Thu Fri Sat" & vbCrlf
strSunday = " "
strMonday = " "
strTuesday = " "
strWednesday = " "
strThursday = " "
strFriday = " "
strSaturday = " "
intFirstDay = Weekday(dtmStartDate)
Select Case intFirstDay
Case 1 strSpacer = strSunday
Case 2 strSpacer = strMonday
Case 3 strSpacer = strTuesday
Case 4 strSpacer = strWednesday
Case 5 strSpacer = strThursday
Case 6 strSpacer = strFriday
Case 7 strSpacer = strSaturday
End Select
strCalendar = strCalendar & strSpacer & intDay
If intFirstDay = 7 Then
strCalendar = strCalendar & vbCrLf
End If
Do While True
dtmStartDate = dtmStartDate + 1
intDay = intDay + 1
intMonthCheck = Month(dtmStartDate)
If intMonthCheck <> CInt(intMonth) Then
Wscript.Echo strCalendar
Exit Do
End If
intNextDay = Weekday(dtmStartDate)
If intNextDay = 1 Then
strSpacer = " "
Else
strSpacer = " "
End If
If intDay < 10 Then
strCalendar = strCalendar & " " & strSpacer & intDay
Else
strCalendar = strCalendar & strSpacer & intDay
End If
If intNextDay = 7 Then
strCalendar = strCalendar & vbCrLf
End If
Loop
As you can see, the script starts off in a pretty simple and straightforward fashion. We begin by using the InputBox function to prompt the user to enter the month and the year; per the event instructions, that’s going to make the variable intMonth equal to, say, 1 (for January), and make the variable intYear equal to, say, 2008 (for, well, 2008). After prompting the user for the month and year we then set the variable intDay to 1 (representing the first day of the month). Once we do that we can then construct a date-time string representing the first day of, in our example here, January, 2008. That’s what this line of code is for:
dtmStartDate = CDate(intMonth & "/" & intDay & "/" & intYear)
That’s not too bad, is it? We’re simply combining our values into a string that looks like this – 1/1/2008 – and then using the CDate function to make sure that this string value gets treated as a date-time value.
Like we said, simple and straightforward.
After we have our date-time value the next step is to set up the “header” for a calendar; that’s simply the month and year followed by the row across the top that lists the days of the week. That’s what these lines of code are for:
strMonthName = MonthName(intMonth) strCalendar = strMonthName & " " & intYear & vbCrlf & vbCrLf strCalendar = strCalendar & "Sun Mon Tue Wed Thu Fri Sat" & vbCrlf
There’s nothing too fancy going on here; we call the MonthName function to retrieve the name of the month, follow it up with a couple of carriage return-linefeeds (the VBScript constant vbCrLf), then we list the days of the week (with a few blank spaces between each day) and tack another carriage return-linefeed on the end. If we were to echo back the value of the variable strCalendar right now our calendar would look like this:
January 2008 Sun Mon Tue Wed Thu Fri Sat
Granted, that’s not much of a calendar, at least not at the moment. But it’s a start.
After defining the header row, our next task is to use the Weekday function to determine which day of the week our starting date (January 1, 2008) fell on; that’s what this line of code is for:
intFirstDay = Weekday(dtmStartDate)
In case you’re wondering, January 1, 2008 was a Tuesday.
That brings us to this block of code:
strSunday = " " strMonday = " " strTuesday = " " strWednesday = " " strThursday = " " strFriday = " " strSaturday = " "
Good question: what are we doing here? Well, the key to getting our calendar to turn out right is to make sure that we get the first day of the month in the proper spot; if that first day is off then the rest of the calendar is going to be off as well. All we’re doing here is adding in the proper amount of blank spaces to ensure that the first day of the month gets slotted correctly. For example, the variable strTuesday consists of 18 blank spaces. Why? Because, if the first day of the month is a Tuesday (which, in this case, it is), we need to start our calendar off with 18 blank spaces before inserting the 1. In other words:
January 2008
Sun Mon Tue Wed Thu Fri Sat
1
See how that works? We define these starting strings for each day of the week, then use the following Select Case statement to determine which variable we need to use:
Select Case intFirstDay
Case 1 strSpacer = strSunday
Case 2 strSpacer = strMonday
Case 3 strSpacer = strTuesday
Case 4 strSpacer = strWednesday
Case 5 strSpacer = strThursday
Case 6 strSpacer = strFriday
Case 7 strSpacer = strSaturday
End Select
When we called the Weekday function for January 1, 2008 we should have gotten back a value of 3; that’s because January 1, 2008 fell on a Tuesday, and – in the world of VBScript – Tuesday is equal to 3. That means that our Select Case statement is going to set the variable strSpacer to strTuesday; it also means that we can use this line of code to add the required number of blank spaces (and day 1 of the month) to the variable strCalendar. That’s what we do here:
strCalendar = strCalendar & strSpacer & intDay
We then need to do one last thing before we start putting together the rest of the calendar; we need to execute this block of code:
If intFirstDay = 7 Then
strCalendar = strCalendar & vbCrLf
End If
What we’re doing here is simply checking to see if the first day of our month happens to be a Saturday. If it is, we need to immediately tack a carriage return-linefeed on to the end of our calendar. Why? Well, if the first day of month is a Saturday the first date row in our calendar will look like this:
January 2008
Sun Mon Tue Wed Thu Fri Sat
1
As you can see, we can’t put any more dates on that first row; instead, we need to add a carriage return-linefeed and put days 2, 3, 4, 5, 6, 7, and 8 on the next row.
Now we’re ready to make a calendar. To begin with, we set up a Do While loop designed to run forever (that is, designed to run as long as True is equal to True).
Note. Will this loop really run forever? No. Like we said, it’s designed to run forever. But when was the last time you saw a piece of software do exactly what it was designed to do? Besides, we included some code that enables us to break out of the loop after we’ve finished adding the last day of the month to the calendar. |
Inside this loop we first increment the values of the variables dtmStartDate and intDay:
dtmStartDate = dtmStartDate + 1 intDay = intDay + 1
That makes dtmStartDate equal to January 2, 2008 (the second day in the month), as well as making intDay equal to 2 (which corresponds quite nicely to the second day of the month). We then use this line of code to check the value of the Month property for dtmStartDate:
intMonthCheck = Month(dtmStartDate)
Why do we do this? Well, suppose dtmStartDate was equal to January 31, 2008. When we add 1 (that is, 1 day) to this value dtmStartDate will be equal to February 1, 2008. That means that we’re done: we’ve taken care of all the days in January and are now into February. In turn, that means that it’s time to display our calendar and then exit both the Do loop and the script. This block of code checks to see if the original starting month and the “current” month are the same:
If intMonthCheck <> CInt(intMonth) Then
Wscript.Echo strCalendar
Exit Do
End If
If they are, great; then we just continue on; if they aren’t, then we echo back the value of strCalendar and then call the Exit Do statement to break out of our endless loop.
So what’s next? Well, after checking the month we then use the Weekday function to determine the day of the week for (the first time through the loop) the second day in the month:
intNextDay = Weekday(dtmStartDate)
Why does that matter? Well, it makes a difference how many blank spaces we need to put between day 1 and 2. For example, suppose day 1 falls on a Tuesday and day 2 falls on a Wednesday. That means the calendar will look like this:
January 2008
Sun Mon Tue Wed Thu Fri Sat
1 2
Notice that we have seven blank spaces between the 1 and the 2. Now, compare that with a month where the first day falls on a Saturday:
January 2008
Sun Mon Tue Wed Thu Fri Sat
1
2
Because day 2 is a Sunday, and represents the beginning of a new row in the calendar, we want only two blank spaces (preceded by a carriage return-linefeed) between the 1 and the 2. With that in mind, we use the Weekday function to determine the day of the week, then use this block of code to determine the correct number of spaces to put between the dates:
If intNextDay = 1 Then
strSpacer = " "
Else
strSpacer = " "
End If
Note. Yes, if you take the trouble to count the blank spaces in the Else statement you’ll see that we have only six blank spaces. Didn’t we say we needed seven blank spaces between the 1 and the 2? Yes, we did. And we’ll explain that seeming-discrepancy in just a moment. |
At this point, we’re ready to add day 2 onto the calendar:
If intDay < 10 Then
strCalendar = strCalendar & " " & strSpacer & intDay
Else
strCalendar = strCalendar & strSpacer & intDay
End If
As you probably noticed, the first thing we do here is check to see if the day of month is less than 10. Why? Well, we want our calendar to line up nicely, like so:
January 2008
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5
6 7 8 9 10 11 12
To get that nice alignment we have to add a blank space before any single-digit numbers; that means that day 2 must really be _2, with the underscore (_) representing a blank space. In the preceding block of code, we check to see if the day of the month is less than 10. If it is, we add the day to the calendar by combining:
| • | The current value of strCalendar. |
| • | An blank space (“ “). |
| • | The variable strSpacer. |
| • | The day of the month (intDay). |
That, by the way, is how we get our seven blank spaces: six blank spaces in the variable strSpacer, plus an extra blank space. If the day of the month is equal to or greater than 10 that means the day already has two digits; in that case, there’s no need to add an extra blank space.
The only thing we have left to do is check to see if the day we just added to the calendar was a Saturday; if it was, then we need to add a carriage return-linefeed to the calendar. (Why? So that the next day – a Sunday – appears in a new row.) That’s what we do here:
If intNextDay = 7 Then
strCalendar = strCalendar & vbCrLf
End If
From there we simply zip back to the top of the loop and repeat this whole process with the next day in the month. When all is said and done, our calendar for January 1, 2008 should look something like this:
January 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 30 31
It’s not the Sports Illustrated Swimsuit Calendar. But it’s the next-best thing.