Get-GuiDate: A PowerShell Calendar Tool

Forms and dialogs can augment PowerShell scripts or even resurface them completely to make them more friendly for people who aren’t familiar with the command line. In some cases, a form can even function as a complete script in and of itself. The Get-GuiDate script, which you can download here, developed from a prototype written with PrimalForms, is an example of a script that is completely centered around a control (the Windows.Forms.MonthCalendar control) embedded in a Windows Form.

 

The Origins of Get-GuiDate

An easily-accessible calendar is a handy tool; unfortunately, Windows never has provided such a tool. (In fact, prior to Vista, you couldn’t even look at the calendar popup from the systray unless you had administrative privileges – and if you did have sufficient privileges, looking at a date would automatically change the system date).

Get-GuiDate started out as a simple calendar that you can quickly call up from PowerShell. This is nothing new, of course. MoW, also known as The PowerShell Guy, wrote a small console calendar script reminiscent of the Unix cal utility for Microsoft’s 2008 Scripting Games. MoW also wrote and blogged about one of the first wrappers for the MonthCalendar control. The Microsoft Scripting Guys wrote yet another wrapper and discussed its implementation in intimate detail in a 2008 PowerShell Tip of the Week about Creating a Graphical Date Picker. Finally – but neither last nor least – Roman Kuzmin wrote and blogged about a Get-Calendar.ps1 script that displays a calendar and returns a date or range of dates.

Although we weren’t aware of Roman’s script until after we wrote Get-GuiDate, Get-Calendar.ps1 is a philosophical precursor of Get-GuiDate. Roman used the same concept we did: make a script flexible so it can be used in other ways (multiple months, displaying week number), and return one or more dates so the script can generate data used elsewhere in PowerShell.

 

Showing the current month and getting back dates

If you run Get-GuiDate without any arguments, it will show the MonthCalendarWithVisualStylescurrent month, similar to the screenshot shown at right.

Note that today’s date will be pre-selected. Today will also be circled and will be shown with the “Today:” legend at the bottom of the form. The script name will be shown in the title bar, and the name of the month and year will be a caption at the top, like the August, 2009 here. You can scroll to prior or future months using the side-pointing arrows (t u) at the top of the form on either side of the month caption.

MultidaySelectionYou can also select multiple days, as shown here, if you either click and drag over a range, or click on a date and then shift-click on another date. (If one end of the range is the first or the last day currently displayed, or belongs to another month, the calendar automatically scrolls to display the adjoining month).

If you either close the form or strike the Enter key, Get-GuiDate returns the selected date; if more than one date was selected, the script transforms the range into an array of dates and returns that. Striking the Escape key is equivalent to cancelling, and returns no dates, whether any were selected or not.

 

Displaying multiple months

The MonthCalendar control can display up to 12 months at a time; just use the DisplayMode parameter with the number of months you would like to display. The script designed to only show 1, 2, 3, 4, 6, 8, 9, or 12 months at a time; if you choose any other number, the script turns the number into an integer and then chooses one of these display geometries using its own algorithm. If you use the command line

Get-GuiDate –DisplayMode 4

you’ll see this calendar:

DisplayMode4

There’s an associated parameter, ScrollBy, that determines how many months will be scrolled when you use the scroll arrows on the Calendar control. This value defaults to the same value as DisplayMode so with 4 months displayed at once, you would also scroll 4 months at a time. If you want to scroll month-by-month even with multiple months displayed simultaneously, use a ScrollBy value of 1, like this:

Get-GuiDate –DisplayMode 4 –ScrollBy 1

 

Adding bolded dates

The Calendar control allows you to specify dates that will appear in bold on the calendar. Those can be literal dates to make bold (using the Bold parameter); dates that should be bold each year (using the YBold parameter); and dates that should be bold each month (using the MBold parameter). Here’s a quick example of how we might do this.

Let’s say that we have the following special dates to mark. We want Western weekend days (Saturdays and Sundays) bolded for the entire current year; August 31 of every year bolded; and the 15th of every month bolded.

First, we need to get the set of weekend days. One way to do this is to simply run through all of the days of the year and check their DayOfWeek property. If this is either 0 (Sunday) or 6 (Saturday), it’s a weekend day. We can collect them into a variable like this:

$WeekendDays = $(for(

    [datetime]$d = “2009-01-01″;

    $d -le [datetime]”2009-12-31”;

    $d = $d.AddDays(1)

)

{

    if(6,0 -contains $d.DayOfWeek){$d}

})

(By the way, it’s important to note that DayOfWeek property of a System.DateTime is NOT the same thing as the WeekDay property we can use to choose the first day of the week shown on the calendar. It’s a confusing inconsistency; just remember that for DateTimes, 0 day in each week is Sunday; we’ll discuss how weeks are displayed in the Tweaking Calendar Display section.)

The $WeekendDays variable now contains every weekend day of 2009. For the annually bolded dates, we can provide a date with any valid year and for the dates bolded every month, we can use any value for the year or the month. The following works perfectly:

Get-GuiDate -Bold $WeekendDays -YBold:1980-08-31   -MBold:1752-01-13

and will show you the following calendar:

BoldedDates

As you can see, all of the weekend days are bold (including ones in adjacent months); August 31 is bold; and the 13th is bold. If you need more than a single annual or monthly bolded date, you can give YBold and MBold arrays of dates as well.

 

Manipulating today and the pre-selected day: Today and DateSelected parameters

By default, the calendar control starts up with the local date preselected. This date is also shown at the bottom of the Calendar control preceded by a colored outline and the text “Today:”.  You can control the date selected at startup with the DateSelected parameter, and you can control the date referred to as “Today” with the Today parameter. What this should tell you is that Today doesn’t really mean Today; it means “some date that we’re pretending is today and we want to make easily accessible”.

This is easiest to explain with demonstration. By default, DateSelected will be set to the same value you specify as Today. Let’s set both for a calendar. We’ll set Today to the first day in the year 2525 (anyone remember the old Zager and Evans song?) and will therefore use 2525-01-01 for Today. We want to start out with the true current date preselected. So we do this:

Get-GuiDate -Today 2525-01-01 -DateSelected (Get-Date)

This will give us the calendar shown below:

 InTheYear2525

The “true” today is pre-selected, but the Calendar is calling January 1, 2525 Today. The practical effect is that from any date on the calendar, you can quickly jump to 2525-01-01 by clicking on the Today value. So Today serves as an anchor if you need to work with dates surrounding some day other than the current date.

 

Tweaking calendar display: FirstDayofWeek, WeekNumbers, Title, and NoTodayCircle

There are a few remaining ways you can tweak your calendar’s appearance. We can choose the weekday that each calendar week begins on with the FirstDayofWeek parameter. We can change the title bar text with the Title parameter. The WeekNumbers switch will turn on display of the numeric week of the year at the beginning of each week. Finally, the NoTodayCircle parameter will turn off automatic circling of “Today” on the calendar.

Here’s an example of using all of these parameters, showing weeks starting on Monday and using the title “Vacation”:

Get-GuiDate.ps1 -FirstDayofWeek Monday –WeekNumbers   -Title Vacation -NoTodayCircle

DisplayTweaks

One very important point to remember if you’ve worked with the DateTime object’s DayOfWeek property: the FirstDayOfWeek used by the Calendar control can be specified as a number in the range 0-6, just like the DateTime’s DayOfWeek. However, FirstDayOfWeek uses 0 to represent Monday, whereas the DateTime uses 0 to represent Sunday. It’s safest to use the localized string for the day of week you want; this will be parsed into the day of week for you.

Restricting the calendar: SelectionCount, MinDate, and MaxDate

For flexibility, the only limits on date display and return for your calendar are the ones the Calendar control itself has. This means you can select as large a range of dates as the Calendar control is willing to let you choose (10 years worth) over any date range between January 1, 1752 and December 31, 9998. You can choose to limit these values with some of Get-GuiDate’s parameters.

To restrict the number of days which can be selected, specify a numeric value for the SelectionCount property. The number 0 disables limits, so the smallest real value you can use is simply 1. You can specify the earliest date displayed with the MinDate property and the latest date displayed with MaxDate. You can take this to ridiculous extremes if you choose, displaying only one day on the calendar:

$d = Get-Date;Get-GuiDate -SelectionCount 1 -MinDate $d -MaxDate $d

This gives you the calendar shown below, empty except for today and completely unscrollable.

RestrictedDisplay 

Zoom Capabilities

One feature of the calendar control that is only available if visual styles are enabled on your system is calendar zoom. If your calendar control onscreen looks very similar to the screen shots we’ve shown, visual styles are available.

If you click on the caption, you can zoom out to get a bird’s eye view of the calendar by clicking the month caption (below left); if you keep clicking on the caption, you can back out until you see an overview of the entire displayable range of calendar dates (below right).

BirdsEyeCalendar BirdseyeCentury

If you used a DisplayMode value other than the default 1 – for example, if you use the command

Get-GuiDate -DisplayMode 4

you’ll get a super-grid similar to the following:

 ZoomedDisplayMode4

Be careful about returning dates after zooming. Whatever zoom level you’re at, selection works at the level of blocks shown on-screen. So in the previous picture, if you hit the Enter key or close the dialog, Get-GuiDate will return every single date from the beginning of year 2000 to the end of 2009 – a whopping 3,653 dates!

This is about the worst you can do; the MonthCalendar itself will return a maximum of 10 years of dates. If you want to make sure this can’t happen, you can try restricting the size of the selection range using the SelectionCount parameter. Users can still select a larger range of dates, but the control reduces the range to the number of dates specified for SelectionCount, beginning with the first selected date.

Get-GuiDate download