python-3.xpyqt5qdateedit

PyQt5 - How can I disable weekend on a QDateEdit


I'm using a QDateEdit to choose a specific date, but I would like to disbale weekends, I only want to choose week days.

self.date =  QDateEdit(calendarPopup = True)
self.date.setDisplayFormat("dd-MM-yyyy")
self.date.setMinimumDate(QDate(2021,10,1))    
self.date.setDate(QDate(datetime.today()))

Solution

  • QCalendarWidget only allows a single range of accepted dates, and all dates in that range can be selected.

    The only solution I could think of (except for creating your own calendar from scratch) is to subclass QCalendarWidget, access the underlying QTableView (which is what shows the calendar) and do the following:

    class CalendarWidget(QtWidgets.QCalendarWidget):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.setSelectionMode(self.NoSelection)
            self.view = self.findChild(QtWidgets.QAbstractItemView, 'qt_calendar_calendarview')
            self.view.installEventFilter(self)
            self.view.viewport().installEventFilter(self)
    
        def dateForIndex(self, index):
            row = index.row()
            column = index.column()
            if self.horizontalHeaderFormat():
                row -= 1
            if self.verticalHeaderFormat():
                column -= 1
            if not 0 <= row <= 5 or not 0 <= column <= 6:
                return QtCore.QDate()
    
            day = index.data()
            month = self.monthShown()
            year = self.yearShown()
    
            # day numbers bigger than 21 cannot be shown in the first 3 rows
            if row <= 2 and day > 21:
                month -= 1
                if month <= 0:
                    month = 12
                    year -= 1
            # day numbers smaller than 15 cannot be shown in the last 3 rows
            elif row >= 3 and day < 15:
                month += 1
                if month >= 13:
                    month = 1
                    year += 1
    
            date = QtCore.QDate(year, month, day)
            if self.minimumDate() <= date <= self.maximumDate():
               return date
            return QtCore.QDate()
    
        def moveCursor(self, key):
            currentDate = self.dateForIndex(self.view.currentIndex())
            delta = 1
    
            if key == QtCore.Qt.Key_Up:
                newDate = currentDate.addDays(-7)
            elif key == QtCore.Qt.Key_Down:
                newDate = currentDate.addDays(7)
            elif key == QtCore.Qt.Key_Left:
                newDate = currentDate.addDays(-1)
            elif key == QtCore.Qt.Key_Right:
                newDate = currentDate.addDays(1)
            elif key == QtCore.Qt.Key_Home:
                newDate = QtCore.QDate(currentDate.year(), currentDate.month(), 1)
                delta = -1
            elif key == QtCore.Qt.Key_End:
                newDate = QtCore.QDate(currentDate.year(), currentDate.month(), 
                    currentDate.daysInMonth())
                delta = -1
            elif key == QtCore.Qt.Key_PageUp:
                newDate = currentDate.addMonths(-1)
                delta = -1
            elif key == QtCore.Qt.Key_PageDown:
                newDate = currentDate.addMonths(1)
                delta = -1
            else:
                return
    
            newDate = max(self.minimumDate(), min(newDate, self.maximumDate()))
            if currentDate != newDate:
                # if it's a day of the weekend, add the delta until a work day is
                # found; for Home/End/Page keys the delta is inverted, as we need to
                # ensure that we stay in the days of the selected month, and if the
                # function reaches a weekend it could skip a month
                while newDate.dayOfWeek() > 5:
                    if newDate > currentDate:
                        newDate = newDate.addDays(delta)
                    else:
                        newDate = newDate.addDays(-delta)
                if self.minimumDate() <= newDate <= self.maximumDate():
                    return newDate
    
        def eventFilter(self, obj, event):
            if (event.type() in (event.MouseButtonPress, event.MouseButtonRelease, event.MouseButtonDblClick) 
                and event.button() == QtCore.Qt.LeftButton):
                    index = self.view.indexAt(event.pos())
                    if index.isValid():
                        date = self.dateForIndex(index)
                        if date.dayOfWeek() <= 5:
                            self.setSelectionMode(self.SingleSelection)
                        else:
                            self.setSelectionMode(self.NoSelection)
    
            elif event.type() == event.MouseMove and event.buttons() == QtCore.Qt.LeftButton:
                index = self.view.indexAt(event.pos())
                if index.isValid():
                    date = self.dateForIndex(index)
                    if not date.isValid() or date.dayOfWeek() > 5:
                        # ignore mouse move events for weekends
                        return True
    
            elif event.type() == event.KeyPress:
                newDate = self.moveCursor(event.key())
                if newDate:
                    self.setSelectedDate(newDate)
                    return True
            return super().eventFilter(obj, event)
    

    The only issue with this implementation is that if the dateEditEnabled is set (which is the default), there's no way to prevent selecting weekend days, except from connecting to the activated and selectionChanged signals and eventually reset the selected date to a valid day.