Florian.G
Florian.G

Reputation: 17

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()))

Upvotes: 0

Views: 677

Answers (1)

musicamante
musicamante

Reputation: 48240

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:

  • set the selection mode to NoSelection;
  • install event filters on both the view (to filter key presses) and the view's viewport (to filter mouse events);
  • implement a dateForIndex to retrieve the displayed date at a specific index of the table;
  • set the selection mode to SingleSelection whenever the index at the mouse position is in work days, otherwise set it back to NoSelection;
  • implement proper selection to avoid/skip weekends when using keyboard navigation;
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.

Upvotes: 1

Related Questions