pythonpyqt5python-3.6qdatetime

PyQt5 - QDateTimeEdit spinbox rollover


I am building an app to download log files from a specific FTP platform, where I also want the user to be able to select start and stop dates in separate QDateTimeEdit widget. This will let the program choose the appropriate log files.

When users connect to the platform, the minimum and maximum datetimes are set, based on the availability of the log files, which works fine, until the user tries to go a step up with the spinbox button from March 31st to April 1st, for example. The widget does not roll over to the next month.

Is there a property I am missing that enables or disables rollover?

Can I otherwise possibly insert custom python (3.6) code to create the rollover? My googling has yielded nothing but a custom C++ code from 2008 for a stepBy which I can't read all that well.

Here is the XML version of the .ui file:

   <widget class="QDateTimeEdit" name="timestart">
    <property name="geometry">
     <rect>
      <x>510</x>
      <y>155</y>
      <width>126</width>
      <height>22</height>
     </rect>
    </property>
    <property name="alignment">
     <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
    </property>
    <property name="displayFormat">
     <string notr="true">yyyy-MM-dd HH:mm:ss</string>
    </property>
   </widget>
   <widget class="QDateTimeEdit" name="timeend">
    <property name="geometry">
     <rect>
      <x>510</x>
      <y>205</y>
      <width>126</width>
      <height>22</height>
     </rect>
    </property>
    <property name="alignment">
     <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
    </property>
    <property name="displayFormat">
     <string notr="true">yyyy-MM-dd HH:mm:ss</string>
    </property>
   </widget>

I realize enabling calendarPopup is a workaround also but design wise I would rather not if I can find a pythonic way around the rollover issue.


Solution

  • A possible fix is to subclass QDateTimeEdit and override both stepEnabled (which is responsible of showing enabled up/down arrows and allowing actual up/down step action) and then stepBy in order to allow changing the date beyond the limits of the current day/month/year.

    Unfortunately this won't solve the issue of not being able to type "invalid" day numbers until the appropriate month is selected (eg: you can't type 31 on february), but achieving such result is really complex due to the interactions between the spinbox, the cursor and the completer.

    In the following code I've implemented it so that all valid date/time fields can actually go both up and down: months, days, hours, minutes and seconds.

    class DateTimeFix(QtWidgets.QDateTimeEdit):
        _overrideSteps = (
            QtWidgets.QDateTimeEdit.MonthSection, 
            QtWidgets.QDateTimeEdit.DaySection, 
            QtWidgets.QDateTimeEdit.HourSection, 
            QtWidgets.QDateTimeEdit.MinuteSection, 
            QtWidgets.QDateTimeEdit.SecondSection, 
        )
        def stepEnabled(self):
            if self.currentSection() in self._overrideSteps:
                return self.StepUpEnabled | self.StepDownEnabled
            return super().stepEnabled()
    
        def stepBy(self, steps):
            section = self.currentSection()
            if section not in self._overrideSteps:
                super().stepBy(steps)
                return
            dt = self.dateTime()
            section = self.currentSection()
            if section == self.MonthSection:
                dt = dt.addMonths(steps)
            elif section == self.DaySection:
                dt = dt.addDays(steps)
            elif section == self.HourSection:
                dt = dt.addSecs(3600 * steps)
            elif section == self.MinuteSection:
                dt = dt.addSecs(60 * steps)
            else:
                dt = dt.addSecs(steps)
            self.setDateTime(dt)
    

    Since you're creating the UI in Designer, you need to promote the QDateTimeEdit in Designer in order to use the above class.
    The simplified procedure is: right click on the QDateTimeEdit widget in designer and select Promote to..., from the dialog type "DateTimeFix" (the class name) in the "Promoted class name" field, then type the name of the file in which you will put the above class (without the .py extension, just like you'd do with an import), ensure that the base class name is still correct (QDateTimeEdit), click on "Add", and then "Promote". After this procedure you can promote all other QDateTimeEdit you might have in that ui (just right click on them and select it from the new "Promote to" sub menu). See this answer for a more detailed explanation.