MonthCalendar Control and datetime

From IronPython Cookbook

Why MonthCalendar?

Image:monthcalstart.jpg

Where I work we do much of our production accounting based on production by day or shift. Generating reports and querying information based on a date lends itself to this kind of control (widget). Instead of entering a date at the keyboard, the user can visually spot the day she wants on the calendar and click on it.

What does this have to do with the datetime module?

In order to make use of the date selected on the MonthCalendar control, I have chosen to convert the date to text and parse it as a(n) (Iron)Python datetime object. There are some differences between IronPython's datetime and CPython's datetime which I will briefly cover below.

The dot Net Framework has it's own date processing capabilities, but I am familiar with CPython's and have chosen to use them.

What the script does

This is purely a demo script in that it produces nothing useful.

Still, it does illustrate date parsing and date differences. As you click on dates on the MonthCalendar control, the Label control displays the date chosen in text. Clicking on the Button will show you how many days passed between the first time available to IronPython to the date you selected (usually about 2000 years worth of days - somewhere on the order of hundreds of thousands).

Image:monthcalend.jpg

Why 2000 years instead of the 40 or so since 1970 (Unix timestamp)?

  • IronPython datetime versus CPython datetime

CPython Interpreter:

 >>> mydate = datetime.datetime.fromtimestamp(0)
 >>> mydate
 datetime.datetime(1969, 12, 31, 17, 0)

(I'm in the Arizona Mountain Time Zone; that's why I'm 7 hours behind London.)

IronPython Interpreter:

 >>> mydate = datetime.datetime.fromtimestamp(0)
 >>> mydate
 datetime.datetime(1, 1, 1, 0, 0)

Note that there's no correction for GMT, even though I'm still in Arizona.

The IronPython implementation handles the start date differently.

For my uses, the IronPython datetime object is similar enough to CPython's. To get around the difference of the start date, I use a reference date relative to the problem I'm working on (a date 5 years ago or the start of the millenium) to get datetime date differences and time and date strings. I also need to remember to account for the 7 hour time difference when I'm working in IronPython (if you're in Britain, I guess it's not an issue).

The script below focuses in part on showing IronPython's datetime implementation. There is a print statement to the console that yields a representation of the IronPython start date for its datetime module. This is good to be aware of before you begin parsing dates, but it won't keep you from doing what you need to do - even if you work frequently with Unix timestamps.

Back to the MonthCalendar Control

  • ToString method with the lowercase o as a parameter
 (self.cal.SelectionRange.Start).ToString('o')[:10]

The o is a dot Net Framework constant (see Microsoft link below) that ensures a culture neutral representation of the date. I only care about the first 10 digits of the format yyyy-mm-dd. No matter what the localization settings for my computer are (German, Argentinian, English U. S.), this date will always appear in the same format. Unlike the MonthCalendar control itself, which will display "Heute:" versus "Today:" when the computer local is switched to Germany, this date format will not change.

MonthCalendar - Is that all there is to it?

Certainly not! Sushila Patel's link below shows more functionality in C# - translating to IronPython is easy though. The ability to select a range of dates and extract a start and end (which I have disabled) opens up more possibilities for reporting (report over a series of dates that don't start with the week or month).

Conclusions

MonthCalendar is a handy control for making a user friendly GUI.

Although IronPython's implementation of the datetime module differs from CPython's, it does not differ enough to prevent you from using it in an application with Windows Forms.

Notes:

  • This was done on a Windows XP platform. I had trouble getting the MonthCalendar widget to show up under Mono on Linux, although it was definitely "there" (the Label was reporting the date).
  • The base form for this project was taken from Michael Foord's forms at the beginning of the Windows Forms section of this site. Ms. Patel's linked article on the MonthCalendar widget helped considerably in its deployment (thanks, Sushila!).

Links:

Date format constants: http://msdn2.microsoft.com/en-us/library/az4se3k1.aspx

Sushila Patel MonthCalendar control example for C#: http://weblogs.asp.net/sushilasb/archive/2006/08/15/How-To_3A00_-Get-date-from.aspx

The Script:

 import clr
 clr.AddReference("System.Windows.Forms")
 clr.AddReference("System.Drawing")
 from System.Windows.Forms import (Form, MonthCalendar, Label,
                                   Button, Application)
 from System.Drawing import Point
 import datetime
 inthebeginning = datetime.datetime.fromtimestamp(0)
 print inthebeginning
 class calendarform(Form):
     def __init__(self):
         self.Text = 'MonthCalendar Control Demo'
         self.label = Label()
         self.cal = MonthCalendar()
         # user can only select one day
         # could have a range of days, if desired
         self.cal.MaxSelectionCount = 1
         self.cal.DateChanged+=self.dateselect
         self.label.Width = 200
         self.label.Height = 50
         self.label.Location = Point(5, 170)
         self.label.Text = ('Program set to run for:\n' + 
                            (self.cal.SelectionRange.Start).ToString('o')[:10])
         self.Controls.Add(self.cal)
         self.Controls.Add(self.label)
         self.button = Button()
         self.button.Text = 'Get days from the beginning'
         self.button.Width = 200
         self.button.Location = Point(5, 225)
         self.button.Click+=self.reportdays
         self.Controls.Add(self.button)
     def dateselect(self, sender, args):
         self.label.Text = ('Program set to run for:\n' + 
                            (self.cal.SelectionRange.Start).ToString('o')[:10])
     def reportdays(self, sender, args):
         datestring = self.label.Text[24:]
         dateparts = datestring.split('-')
         chosendate = datetime.datetime(int(dateparts[0]), # year
                                        int(dateparts[1]), # month
                                        int(dateparts[2])) # day
         timediff = chosendate - inthebeginning
         self.label.Text = ('There are %d days between the\n' % timediff.days +
                            'beginning of the epoch and\n' +
                            chosendate.strftime('%Y-%m-%d')) + '.'
 form = calendarform()
 Application.Run(form)


Back to Contents.

TOOLBOX
LANGUAGES