CalendarCtrl and DateTime problems

I'm using the CalendarCtrl and find it to have problems in 2.6.2.1 on Mac OS X.

The EVT_CALENDAR_SEL_CHANGED will be sent with the date set to 2038 (GetTick() == 0xffffffff)
after clicking the up spin arrow on the year. My work around is to ignore this as the correct date arrives
in the next event.

I've been able to get the year to change to 0 and -1 when clicking on the year spinners, not sure of
the exact sequence that did this.

Enable( False ) disabled the year and month controls but the day picker still allows changes.
I work around this by forcing the date back.

It seems that the CalendarCtrl keeps a reference to the DateTime object I pass in.
And that object is returned from GetDate. This was a surprise I would expect the ctrl
to have its own independent DataTime object inside itself.

The code I ended up with is below.

Barry

class HistoryDialog(wx.Dialog):
     def __init__( self, parent ):
         wx.Dialog.__init__( self, parent, -1, 'Log History' )

         self.g_sizer = wx.FlexGridSizer( 0, 2, 0, 0 )
         self.g_sizer.AddGrowableCol( 1 )

         self.all_radio = wx.RadioButton( self, -1, 'Show all entries' )
         self.all_radio.SetValue( True )
         self.g_sizer.Add( self.all_radio, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )
         self.g_sizer.Add( (0,0), 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )

         self.limit_radio = wx.RadioButton( self, -1, 'Show only:' )
         self.limit_text = wx.TextCtrl( self, -1, '20', style=wx.TE_RIGHT )
         self.limit_text.Enable( False )
         self.g_sizer.Add( self.limit_radio, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )
         self.g_sizer.Add( self.limit_text, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )

         self.date = wx.DateTime_Now()
         self.date.SubtractDS( wx.DateSpan( weeks=1 ) )

         self.since_radio = wx.RadioButton( self, -1, 'Show since:' )
         self.since_date = wx.calendar.CalendarCtrl( self, -1,
                                 self.copyDataTime( self.date ),
                                 style=wx.calendar.CAL_MONDAY_FIRST )
         self.since_date.Enable( False )
         self.g_sizer.Add( self.since_radio, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_TOP, 3 )
         self.g_sizer.Add( self.since_date, 1, wx.EXPAND|wx.ALL|wx.ALIGN_RIGHT, 3 )

         self.button_ok = wx.Button( self, wx.ID_OK, ' OK ' )
         self.button_ok.SetDefault()
         self.button_cancel = wx.Button( self, wx.ID_CANCEL, ' Cancel ' )

         self.h_sizer_buttons = wx.BoxSizer( wx.HORIZONTAL )
         self.h_sizer_buttons.Add( (150, 20), 1, wx.EXPAND )
         self.h_sizer_buttons.Add( self.button_ok, 0, wx.EXPAND|wx.EAST, 15 )
         self.h_sizer_buttons.Add( self.button_cancel, 0, wx.EXPAND|wx.EAST, 2 )

         self.v_sizer = wx.BoxSizer( wx.VERTICAL )
         self.v_sizer.Add( self.g_sizer, 0, wx.EXPAND|wx.ALL, 5 )
         self.v_sizer.Add( self.h_sizer_buttons, 0, wx.EXPAND|wx.ALL, 5 )

         self.SetAutoLayout( True )
         self.SetSizer( self.v_sizer )
         self.v_sizer.Fit( self )
         self.Layout()

         self.CentreOnParent()

         wx.EVT_BUTTON( self, wx.ID_OK, self.OnOk )
         wx.EVT_BUTTON( self, wx.ID_CANCEL, self.OnCancel )

         wx.EVT_RADIOBUTTON( self, self.all_radio.GetId(), self.OnRadio )
         wx.EVT_RADIOBUTTON( self, self.limit_radio.GetId(), self.OnRadio )
         wx.EVT_RADIOBUTTON( self, self.since_radio.GetId(), self.OnRadio )

         wx.calendar.EVT_CALENDAR_SEL_CHANGED( self, self.since_date.GetId(), self.OnCalendarSelChanged )

     # ----------------------------------------
     def OnOk( self, event ):
         self.EndModal( wx.ID_OK )

     def OnCancel( self, event ):
         self.EndModal( wx.ID_CANCEL )

     # ----------------------------------------
     def OnRadio( self, event ):
         self.since_date.Enable( self.since_radio.GetValue() )
         self.limit_text.Enable( self.limit_radio.GetValue() )

     def OnCalendarSelChanged( self, event ):
         if self.since_radio.GetValue():
             # ensure that the date stays in the past
             date = self.since_date.GetDate()
             # sometimes the event is sent with a bogus value for the date
             # just ignore these events
             if date.GetTicks() == ((2**32)-1):
                 return
             if date.IsLaterThan( wx.DateTime_Now() ):
                 self.since_date.SetDate( self.copyDataTime( self.date ) )
             else:
                 self.date = self.copyDataTime( date )
         else:
             # CalendarCtrl does not disable day changes
             # force date back to self.date
             self.since_date.SetDate( self.copyDataTime( self.date ) )

     def copyDataTime( self, date ):
         t = date.GetTicks()
         return wx.DateTimeFromTimeT( t )

Barry Scott wrote:

I'm using the CalendarCtrl and find it to have problems in 2.6.2.1 on Mac OS X.

The EVT_CALENDAR_SEL_CHANGED will be sent with the date set to 2038 (GetTick() == 0xffffffff)
after clicking the up spin arrow on the year. My work around is to ignore this as the correct date arrives
in the next event.

This appears to be the result of multiple EVT_TEXT and EVT_SPINCTRL events being sent as one control updates the other. This is definitely a bug, please enter a bug report about it with a "wxMac specific" category.

I've been able to get the year to change to 0 and -1 when clicking on the year spinners, not sure of
the exact sequence that did this.

I think that the zero is wrong (IIRC there wasn't a year zero, just 1 B.C. followed by 1 A.D., right?) but the calendar control is supposed to be able to handle the full range of the wx.DateTime, or at least the max range of the wx.SpinCtrl, so the negative years are probably correct. You may want to ask about it on wx-users to be sure though.

Enable( False ) disabled the year and month controls but the day picker still allows changes.

Please enter a bug report about this too, use a category of "Generic".

I work around this by forcing the date back.

It seems that the CalendarCtrl keeps a reference to the DateTime object I pass in.
And that object is returned from GetDate. This was a surprise I would expect the ctrl
to have its own independent DataTime object inside itself.

What makes it seem this way to you? I looked at the code and although a reference is passed into the constructor it is assigned (copied) to a local wxDateTime value.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!