0

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.

Chokehold
  • 67
  • 7

2 Answers2

1

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.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Thank you for quick response! All of the code I have is within a `class Ui(QtWidgets.QMainWindow):` and I have not worked all that much with classes on the whole. I tried putting your code both within the confines of that class and above it but there's still no rolling over when I run it. Am I doing something wrong or is there any customizations I need to do for it to fit my code? because if so I don't see it. – Chokehold Apr 11 '21 at 14:15
  • Sorry, I forgot that you were using a file created in Designer. See the update. – musicamante Apr 11 '21 at 14:27
  • It works! I'm not sure right off the bat _how_ it works just yet, but I will read up and learn from this. Thank you! – Chokehold Apr 11 '21 at 14:55
  • 1
    @Chokehold in practice, `stepEnabled` is the main function that is called both to show which arrows should be drawn as enabled and (most importantly) **if** the spin can actually go up or down, so that's the first required *step* (pun intended) to implement. `stepBy` is then called whenever keyboard navigation (arrow/page keys) is used, when clicking arrows, or using the mouse wheel. In QDateTimeEdit it works by detecting the current section in which the text cursor is and changing that section accordingly, but the default implementation prevents moving beyond the current field "range". – musicamante Apr 11 '21 at 15:01
0

I used the code snippet as shown by @musicamante above, with a few tweaks, maybe because I am using PyQt6. (Basically, adding those .Section and .StepEnabledFlag to the enumerated attributes)

from PyQt6.QtWidgets import QDateTimeEdit

class DateTimeFix(QDateTimeEdit):
    _overrideSteps = (
        QDateTimeEdit.Section.MonthSection, 
        QDateTimeEdit.Section.DaySection, 
        QDateTimeEdit.Section.HourSection, 
        QDateTimeEdit.Section.MinuteSection, 
        QDateTimeEdit.Section.SecondSection, 
    )
    def stepEnabled(self):
        if self.currentSection() in self._overrideSteps:
            return self.StepEnabledFlag.StepUpEnabled | self.StepEnabledFlag.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.Section.MonthSection:
            dt = dt.addMonths(steps)
        elif section == self.Section.DaySection:
            dt = dt.addDays(steps)
        elif section == self.Section.HourSection:
            dt = dt.addSecs(3600 * steps)
        elif section == self.Section.MinuteSection:
            dt = dt.addSecs(60 * steps)
        else:
            dt = dt.addSecs(steps)
        self.setDateTime(dt)

loluengo
  • 76
  • 3