Beispiel #1
0
class HistWindowSelect(QGroupBox):
    """An extension of the QGroupBox widget. It allows the user to select a
    start date and end date for the historical window in the Monte Carlo VaR
    calculation application.
    
    The end date of the historical window is set to 22 days from the start date
    as a minimum. This restriction is implemented through the QDateEdit's set
    minimum date function.
    """
    end_signal = QtCore.pyqtSignal(dt.datetime)

    def __init__(self, parent=None):
        super(HistWindowSelect, self).__init__(parent)

        self.setTitle('Historical Window Select')
        grid = QGridLayout()

        lbl_start = QLabel('Start Date: ')
        lbl_end = QLabel('End Date: ')

        self.deStart = QDateEdit()
        self.deStart.setCalendarPopup(True)
        self.deStart.setMinimumDateTime(dt.datetime(2015, 1, 1))
        self.deStart.setMaximumDateTime(
            self.getWeekdaysBack(dt.datetime.now(), 22))
        self.deStart.dateChanged.connect(self.onStartDateChange)
        self.deEnd = QDateEdit()
        self.deEnd.setCalendarPopup(True)
        self.deEnd.setMaximumDateTime(dt.datetime.now())
        self.deEnd.dateChanged.connect(self.onEndDateChange)
        self.setEndDateMinimum()
        lbl_disc = QLabel(
            'Note: A minimum historical window length <br>of 22 business days has been imposed.'
        )

        grid.addWidget(lbl_start, 0, 0)
        grid.addWidget(lbl_end, 1, 0)
        grid.addWidget(self.deStart, 0, 1)
        grid.addWidget(self.deEnd, 1, 1)
        grid.addWidget(lbl_disc, 2, 1)

        self.setLayout(grid)

    def onStartDateChange(self):
        """Checks if new start date is a weekend. If so, it adds the 
        appropriate number of days to bring the start date to the following 
        Monday. After, it updates the minimum value of the end date, which must
        be 22 business days after the start date.
        """
        deStart = dt.datetime.combine(self.deStart.date().toPyDate(),
                                      dt.datetime.min.time())
        if deStart.weekday() == 5:
            deStart = deStart + timedelta(days=2)
        elif deStart.weekday() == 6:
            deStart = deStart + timedelta(days=1)

        self.deStart.setDate(deStart)
        self.setEndDateMinimum()

    def setEndDateMinimum(self):
        """Updates the minimum value of the end date, which must
        be 22 business days after the start date.
        """
        deStart = dt.datetime.combine(self.deStart.date().toPyDate(),
                                      dt.datetime.min.time())
        end_min = self.getWeekdaysOut(deStart, 22)
        self.deEnd.setMinimumDateTime(end_min)
        if self.deEnd.date() < end_min:
            self.deEnd.setDate(end_min)
            self.test_signal.emit(end_min)

    def onEndDateChange(self):
        """Checks if new end date is a weekend. If so, it adds the 
        appropriate number of days to bring the end date to the following 
        Monday. After, it emits a signal containing the new end date to be
        picked up by a slot in the PredWindowSelect widget.
        """
        deEnd = dt.datetime.combine(self.deEnd.date().toPyDate(),
                                    dt.datetime.min.time())
        if deEnd.weekday() == 5:
            deEnd = deEnd + timedelta(days=2)
        elif deEnd.weekday() == 6:
            deEnd = deEnd + timedelta(days=1)

        self.deEnd.setDate(deEnd)
        self.end_signal.emit(deEnd)

    def getWeekdaysOut(self, start_date, days_out):
        """Finds the date that is x weekdays later than the start date (where
        x = days_out).
        
        Args:
            
            start_date (datetime.datetime) - Start date from which to count 
            weekdays out.
            
            days_out (int) - Number of days out.
            
        Returns:
            
            curr_date (datetime.datetime) - the date that is days_out weekdays
            later than start_date.
        
        Raises Error:
            
            None
        
        """
        if start_date.weekday() == 5:
            start_date += timedelta(days=2)
        if start_date.weekday() == 6:
            start_date += timedelta(days=1)

        for i in range(days_out):
            if i == 0:
                curr_date = start_date
            next_date = curr_date + timedelta(days=1)
            if next_date.weekday() == 5:
                next_date = curr_date + timedelta(days=3)
            curr_date = next_date

        return (curr_date)

    def getWeekdaysBack(self, start_date, days_back):
        """Finds the date that is x weekdays earlier than the start date (where
        x = days_back).
        
        Args:
            
            start_date (datetime.datetime) - Start date from which to count 
            weekdays out.
            
            days_back (int) - Number of days back.
            
        Returns:
            
            curr_date (datetime.datetime) - the date that is days_back weekdays
            earlier than start_date.
        
        Raises Error:
            
            None
        
        """
        if start_date.weekday() == 5:
            start_date -= timedelta(days=1)
        if start_date.weekday() == 6:
            start_date -= timedelta(days=2)

        for i in range(days_back):
            if i == 0:
                curr_date = start_date
            next_date = curr_date - timedelta(days=1)
            if next_date.weekday() == 6:
                next_date = curr_date - timedelta(days=3)
            curr_date = next_date

        return (curr_date)

    def getDateWindow(self):
        """Getter function for the date window selected by the user.
        """
        start_date = dt.datetime.combine(self.deStart.date().toPyDate(),
                                         dt.datetime.min.time())
        end_date = dt.datetime.combine(self.deEnd.date().toPyDate(),
                                       dt.datetime.min.time())
        return start_date, end_date
Beispiel #2
0
class EgimDownloaderFrame(QFrame):
    """Frame to download data from EMSODEV servers"""

    # Signals
    msg2Statusbar = pyqtSignal(str)
    wf2plotSplitter = pyqtSignal(WaterFrame)

    class DownloadParameterThread(QThread):
        """
        The process to download data from the API is very slow.
        We are going to use this thread to download data without block the app.
        """
        def __init__(self, downloader):
            QThread.__init__(self)

            self.downloader = downloader

        def __del__(self):
            self.wait()

        def run(self):
            if self.downloader.instrumentList.currentItem().text() \
               == "icListen-1636":
                date = datetime.datetime.strptime(
                    self.downloader.dateList.currentItem().text(),
                    "%Y-%m-%d").strftime("%d/%m/%Y")
                self.downloader.downloadAcoustic(
                    date, self.downloader.hourMinuteList.currentItem().text())
            else:
                parameters = [item.text() for item in
                              self.downloader.parameterList.selectedItems()]
                for parameter in parameters:
                    self.downloader.downloadParameter(parameter)

    def __init__(self):
        super().__init__()

        # Instance variables
        self.downloader = EGIM()
        self.wf = WaterFrame()
        self.metadata = dict()
        self.dates = []
        self.myThread = None
        # Save the login of the EMSODEV API
        self.downloader.login = "******"
        self.downloader.password = ""

        self.initUI()

    def initUI(self):

        # Buttons
        downloadButton = QPushButton("Download", self)
        downloadButton.clicked.connect(self.downloadClick)
        downloadButton.setEnabled(False)
        closeButton = QPushButton("Close", self)
        closeButton.clicked.connect(self.hide)

        # Lists
        self.egimList = QListWidget(self)
        self.egimList.itemClicked.connect(self.loadInstruments)
        self.egimList.setMaximumWidth(200)

        self.instrumentList = QListWidget(self)
        self.instrumentList.itemClicked.connect(self.loadParameters)
        self.instrumentList.setMaximumWidth(290)

        self.metadataList = QListWidget(self)

        self.parameterList = QListWidget(self)
        self.parameterList.setSelectionMode(
            QAbstractItemView.ExtendedSelection)
        self.parameterList.itemClicked.connect(
            lambda: downloadButton.setEnabled(True))

        self.dateList = QListWidget(self)
        self.dateList.itemClicked.connect(self.loadTimes)
        self.dateList.setMaximumWidth(150)
        self.hourMinuteList = QListWidget(self)
        self.hourMinuteList.itemClicked.connect(
            lambda: downloadButton.setEnabled(True))
        self.hourMinuteList.setMaximumWidth(150)

        # Labels
        egimLabel = QLabel("EGIM", self)
        instrumentLabel = QLabel("Instrument", self)
        metadataLabel = QLabel("Metadata", self)
        parameterLabel = QLabel("Parameter", self)
        startDateLabel = QLabel("Start date", self)
        endDateLabel = QLabel("End date", self)
        limitLabel = QLabel("Get last X values", self)
        hourLabel = QLabel("Hour and minute (HHMM)", self)
        dateLabel = QLabel("Available dates", self)

        # Date edit
        self.startDateEdit = QDateEdit(self)
        self.startDateEdit.setCalendarPopup(True)
        self.startDateEdit.setDateTime(QDateTime(QDate(2017, 1, 27),
                                                 QTime(0, 0, 0)))
        self.startDateEdit.setMinimumDateTime(QDateTime(QDate(2017, 1, 27),
                                                        QTime(0, 0, 0)))
        self.endDateEdit = QDateEdit(self)
        self.endDateEdit.setCalendarPopup(True)
        self.endDateEdit.setDateTime(QDateTime(QDate(2017, 1, 27),
                                               QTime(0, 0, 0)))
        self.endDateEdit.setMinimumDateTime(QDateTime(QDate(2017, 1, 27),
                                                      QTime(0, 0, 0)))

        # Spin box
        self.limitSpinBox = QSpinBox(self)
        self.limitSpinBox.setMinimum(0)
        self.limitSpinBox.setMaximum(9999999999)
        self.limitSpinBox.setSingleStep(100)
        self.limitSpinBox.valueChanged.connect(self.enableDate)

        # Custom Widgets

        # Widget for dates of the acoustic data
        self.acousticDateWidget = QWidget(self)
        # - Layout
        vAcousticDate = QVBoxLayout()
        vAcousticDate.addWidget(dateLabel)
        vAcousticDate.addWidget(self.dateList)
        vAcousticDate.addWidget(hourLabel)
        vAcousticDate.addWidget(self.hourMinuteList)
        self.acousticDateWidget.setLayout(vAcousticDate)
        self.acousticDateWidget.setMaximumWidth(175)
        self.acousticDateWidget.setEnabled(False)

        # Widget for dates of parameters
        self.parameterDateWidget = QWidget(self)
        # - Layout
        vparameterDate = QVBoxLayout()
        vparameterDate.addWidget(startDateLabel)
        vparameterDate.addWidget(self.startDateEdit)
        vparameterDate.addWidget(endDateLabel)
        vparameterDate.addWidget(self.endDateEdit)
        vparameterDate.addWidget(limitLabel)
        vparameterDate.addWidget(self.limitSpinBox)
        vparameterDate.addStretch()
        self.parameterDateWidget.setLayout(vparameterDate)
        self.parameterDateWidget.setEnabled(False)

        # Layout
        # - Vertical layout for EGIM --
        vEgim = QVBoxLayout()
        vEgim.addWidget(egimLabel)
        vEgim.addWidget(self.egimList)
        # -- Vertical layout for instruments -
        vInstrument = QVBoxLayout()
        vInstrument.addWidget(instrumentLabel)
        vInstrument.addWidget(self.instrumentList)
        # - Vertical layout for parameters -
        vParameter = QVBoxLayout()
        vParameter.addWidget(metadataLabel)
        vParameter.addWidget(self.metadataList)
        vParameter.addWidget(parameterLabel)
        vParameter.addWidget(self.parameterList)
        # - Vertical layout for dates and buttons
        vButton = QVBoxLayout()
        vButton.addWidget(downloadButton)
        vButton.addWidget(closeButton)
        vButton.addStretch()
        # - Layout of the frame -
        hFrame = QHBoxLayout()
        hFrame.addLayout(vEgim)
        hFrame.addLayout(vInstrument)
        hFrame.addLayout(vParameter)
        hFrame.addWidget(self.parameterDateWidget)
        hFrame.addWidget(self.acousticDateWidget)
        hFrame.addLayout(vButton)

        self.setLayout(hFrame)

    def loadObservatories(self):
        """
        It asks for the available EGIM observatories and write its names into
        self.egimList
        """

        # Send a message for the statusbar
        self.msg2Statusbar.emit("Loading observatories")
        # Clear self.egimList
        self.egimList.clear()
        # Ask for the observatories
        code, observatoryList = self.downloader.observatories()
        if code:
            if code == 200:
                # It means that you are going good
                self.egimList.addItems(observatoryList)
                # Send a message for the statusbar
                self.msg2Statusbar.emit("Ready")
            elif code == 401:
                self.msg2Statusbar.emit(
                    "Unauthorized to use the EMSODEV DMP API")
                self.downloader.password = None
                self.reload()
            elif code == 404:
                self.msg2Statusbar.emit("Not Found")
            elif code == 403:
                self.msg2Statusbar.emit("Forbidden")
            elif code == 500:
                self.msg2Statusbar.emit("EMSODEV API internal error")
            else:
                self.msg2Statusbar.emit("Unknown EMSODEV DMP API error")
        else:
            self.msg2Statusbar.emit(
                "Impossible to connect to the EMSODEV DMP API")

    def loadInstruments(self, observatory):
        """
        It asks for the available instruments and write its names into
        self.instrumentList

        Parameters
        ----------
            observatory: item
                item from self.observatoryList
        """
        # Send a message for the statusbar
        self.msg2Statusbar.emit("Loading instruments")
        # Clear self.instrumentList
        self.instrumentList.clear()
        # Ask for instruments
        code, instrumentList_ = self.downloader.instruments(observatory.text())
        if code:
            if code == 200:
                # It means that you are going good
                # Obtain all sensor names of instrumentList_
                sensorType = [
                    instrument['name'] for instrument in instrumentList_]
                self.instrumentList.addItems(sensorType)
                # Add tooltip
                for i in range(self.instrumentList.count()):
                    self.instrumentList.item(i).setToolTip(
                        '<p><b>Sensor Type</b><br>' +
                        '{}</p><p>'.format(instrumentList_[i]['sensorType']) +
                        '<b>Long Name</b><br>' +
                        '{}</p>'.format(instrumentList_[i]['sensorLongName']) +
                        '<p></p><p><b>S/N</b><br>' +
                        '{}</p>'.format(instrumentList_[i]['sn']))
                # Send a message for the statusbar
                self.msg2Statusbar.emit("Ready")
            elif code == 401:
                self.msg2Statusbar.emit(
                    "Unauthorized to use the EMSODEV DMP API")
                self.downloader.password = None
                self.reload()
            elif code == 404:
                self.msg2Statusbar.emit("Not Found")
            elif code == 403:
                self.msg2Statusbar.emit("Forbidden")
            elif code == 500:
                self.msg2Statusbar.emit("EMSODEV API internal error")
            else:
                self.msg2Statusbar.emit("Unknown EMSODEV DMP API error")
        else:
            self.msg2Statusbar.emit(
                "Impossible to connect to the EMSODEV DMP API")

    def loadParameters(self, instrument):
        """
        It asks for the available parameters and metadata and write them into
        self.parameterList and self.metadataList
        """
        # Send a message for the statusbar
        self.msg2Statusbar.emit("Loading parameters")
        # Clear self.parameterList and self.metadataList
        self.parameterList.clear()
        self.metadataList.clear()
        self.parameterDateWidget.setEnabled(False)
        self.acousticDateWidget.setEnabled(False)

        # If instrument is an icListener, check times
        if instrument.text() == "icListen-1636":
            self.acousticDateWidget.setEnabled(True)
            # Ask for dates
            code, self.dates = self.downloader.acoustic_date(
                self.egimList.currentItem().text(), instrument.text())
            if code == 200:
                dateList = [
                    date['acousticObservationDate'] for date in self.dates]
                self.dateList.addItems(dateList)

            else:
                self.msg2Statusbar.emit(
                    "Impossible to connect to the EMSODEV DMP API")
                return
            return

        self.parameterDateWidget.setEnabled(True)

        # Ask for metadata
        code, self.metadata = self.downloader.metadata(
            self.egimList.currentItem().text(), instrument.text())
        if code == 200:
            items = []
            for key, value in self.metadata.items():
                items.append("{}: {}".format(key, value))
            self.metadataList.addItems(items)
        else:
            self.msg2Statusbar.emit(
                "Impossible to connect to the EMSODEV DMP API")
            return

        # Ask for parameters
        code, parameterList_ = self.downloader.parameters(
            self.egimList.currentItem().text(), instrument.text())
        if code:
            if code == 200:
                # It means that you are going good
                # Obtain all parameter names of parameterList_
                names = [parameter['name'] for parameter in parameterList_]
                self.parameterList.addItems(names)
                self.parameterList.sortItems()
                # Add tooltip
                for i in range(self.parameterList.count()):
                    self.parameterList.item(i).setToolTip(
                        '<b>Units:</b> {}'.format(parameterList_[i]['uom']))
                # Send a message for the statusbar
                self.msg2Statusbar.emit("Ready")
            elif code == 401:
                self.msg2Statusbar.emit(
                    "Unauthorized to use the EMSODEV DMP API")
                self.downloader.password = None
                self.reload()
            elif code == 404:
                self.msg2Statusbar.emit("Not Found")
            elif code == 403:
                self.msg2Statusbar.emit("Forbidden")
            elif code == 500:
                self.msg2Statusbar.emit("EMSODEV API internal error")
            else:
                self.msg2Statusbar.emit("Unknown EMSODEV DMP API error")
        else:
            self.msg2Statusbar.emit(
                "Impossible to connect to the EMSODEV DMP API")

    def loadTimes(self, date_item):
        """
        Write items into self.hourMinuteList QListWidget
        """
        for date in self.dates:
            if date['acousticObservationDate'] == date_item.text():
                timeList = []
                for time in date['observationsHourMinuteList']:
                    timeList.append(time['acousticObservationHourMinute'])
                self.hourMinuteList.addItems(timeList)

    def reload(self):
        """It clear all lists and load again the observatories."""
        # Check the password of the API
        if self.downloader.password is None:
            self.msg2Statusbar.emit(
                "Password is required to download data from EMSODEV")
            text, ok = QInputDialog.getText(self, "Attention", "Password",
                                            QLineEdit.Password)
            if ok:
                self.downloader.password = text
            else:
                return
        self.loadObservatories()

    def downloadClick(self):
        """Function when user click download"""

        self.myThread = self.DownloadParameterThread(self)
        self.myThread.start()

    def downloadParameter(self, parameter):
        """It download data with the observation function of EGIM"""

        # Send a message for the statusbar
        self.msg2Statusbar.emit("Downloading {}".format(parameter))

        code, df = self.downloader.observation(
            observatory=self.egimList.currentItem().text(),
            instrument=self.instrumentList.currentItem().text(),
            parameter=parameter,
            startDate=self.startDateEdit.text(),
            endDate=self.endDateEdit.text(),
            limit=self.limitSpinBox.text())
        if code:
            if code == 200:
                self.msg2Statusbar.emit("Waterframe creation")
                # It means that you are going good
                wf = self.downloader.to_waterframe(data=df,
                                                   metadata=self.metadata)
                # print(wf.data.head())
                # Send a signal with the new WaterFrame
                self.wf2plotSplitter.emit(wf)
                self.msg2Statusbar.emit("Ready")
            elif code == 401:
                self.msg2Statusbar.emit(
                    "Unauthorized to use the EMSODEV DMP API")
                self.downloader.password = None
                self.reload()
            elif code == 404:
                self.msg2Statusbar.emit("Not Found")
            elif code == 403:
                self.msg2Statusbar.emit("Forbidden")
            elif code == 500:
                self.msg2Statusbar.emit("EMSODEV API internal error")
            else:
                self.msg2Statusbar.emit("Unknown EMSODEV DMP API error")
        else:
            self.msg2Statusbar.emit(
                "Impossible to connect to the EMSODEV DMP API")

    def downloadAcoustic(self, date, time):
        # Send a message for the statusbar
        self.msg2Statusbar.emit(
            "Downloading acoustic file from {}, {}".format(date, time))

        code, df, metadata = self.downloader.acoustic_observation(
            observatory=self.egimList.currentItem().text(),
            instrument=self.instrumentList.currentItem().text(),
            date=date,
            hour_minute=time)
        if code:
            if code == 200:
                self.msg2Statusbar.emit("Waterframe creation")
                # It means that you are going good
                wf = self.downloader.to_waterframe(data=df, metadata=metadata)
                # Send a signal with the new WaterFrame
                self.wf2plotSplitter.emit(wf)
                self.msg2Statusbar.emit("Ready")
            elif code == 401:
                self.msg2Statusbar.emit(
                    "Unauthorized to use the EMSODEV DMP API")
                self.downloader.password = None
                self.reload()
            elif code == 404:
                self.msg2Statusbar.emit("Not Found")
            elif code == 403:
                self.msg2Statusbar.emit("Forbidden")
            elif code == 500:
                self.msg2Statusbar.emit("EMSODEV API internal error")
            else:
                self.msg2Statusbar.emit("Unknown EMSODEV DMP API error")
        else:
            self.msg2Statusbar.emit(
                "Impossible to connect to the EMSODEV DMP API")

    def enableDate(self):
        """Enable or disable date elements"""
        if int(self.limitSpinBox.text()) > 0:
            self.startDateEdit.setEnabled(False)
            self.endDateEdit.setEnabled(False)
        else:
            self.startDateEdit.setEnabled(True)
            self.endDateEdit.setEnabled(True)
Beispiel #3
0
class PredWindowSelect(QGroupBox):
    """An extension of the QGroupBox widget. It allows the user to select a
    start date and end date for the prediction window in the Monte Carlo VaR
    calculation application.
    
    The start date of the prediction window is set to the end date of the 
    historical window. The end date of the prediction window is five days later
    than the start date of the prediction window as a minimum. These 
    restrictions are implemented through the QDateEdit widget's maximum and 
    minimum date functions.
    """
    def __init__(self, parent=None):
        super(PredWindowSelect, self).__init__(parent)

        self.setTitle('Prediction Window Select')
        grid = QGridLayout()

        lbl_start = QLabel('Start Date: ')
        lbl_end = QLabel('End Date: ')

        self.deStart = QDateEdit()
        self.deStart.setEnabled(False)
        self.deStart.setCalendarPopup(True)
        self.deStart.dateChanged.connect(self.setEndDateMinimum)
        self.deEnd = QDateEdit()
        self.deEnd.setCalendarPopup(True)
        self.deEnd.dateChanged.connect(self.onEndDateChange)
        self.setEndDateMinimum()
        lbl_disc = QLabel(
            'Note: A minimum prediction window length <br>of 5 business days has been imposed.'
        )

        grid.addWidget(lbl_start, 0, 0)
        grid.addWidget(lbl_end, 1, 0)
        grid.addWidget(self.deStart, 0, 1)
        grid.addWidget(self.deEnd, 1, 1)
        grid.addWidget(lbl_disc, 2, 1)

        self.setLayout(grid)

    @QtCore.pyqtSlot(dt.datetime)
    def setStart(self, date):
        """Slotted function to set the start date based on end date of the 
        historical window.
        """
        self.deStart.setDate(date)

    def onEndDateChange(self):
        """Checks if new end date is a weekend. If so, it adds the 
        appropriate number of days to bring the end date to the following 
        Monday.
        """
        deEnd = dt.datetime.combine(self.deEnd.date().toPyDate(),
                                    dt.datetime.min.time())
        if deEnd.weekday() == 5:
            deEnd = deEnd + timedelta(days=2)
        elif deEnd.weekday() == 6:
            deEnd = deEnd + timedelta(days=1)

        self.deEnd.setDate(deEnd)

    def setEndDateMinimum(self):
        """Updates the minimum value of the end date, which must
        be 5 business days after the start date.
        """
        deStart = dt.datetime.combine(self.deStart.date().toPyDate(),
                                      dt.datetime.min.time())
        end_min = self.getWeekdaysOut(deStart, 5)
        self.deEnd.setMinimumDateTime(end_min)
        if self.deEnd.date() < end_min:
            self.deEnd.setDate(end_min)

    def getWeekdaysOut(self, start_date, days_out):
        """Finds the date that is x weekdays later than the start date (where
        x = days_out).
        
        Args:
            
            start_date (datetime.datetime) - Start date from which to count 
            weekdays out.
            
            days_out (int) - Number of days out.
            
        Returns:
            
            curr_date (datetime.datetime) - the date that is days_out weekdays
            later than start_date.
        
        Raises Error:
            
            None
        
        """
        if start_date.weekday() == 5:
            start_date += timedelta(days=2)
        if start_date.weekday() == 6:
            start_date += timedelta(days=1)

        for i in range(days_out):
            if i == 0:
                curr_date = start_date
            next_date = curr_date + timedelta(days=1)
            if next_date.weekday() == 5:
                next_date = curr_date + timedelta(days=3)
            curr_date = next_date

        return (curr_date)

    def getDateWindow(self):
        """Getter function for the date window selected by the user.
        """
        start_date = dt.datetime.combine(self.deStart.date().toPyDate(),
                                         dt.datetime.min.time())
        end_date = dt.datetime.combine(self.deEnd.date().toPyDate(),
                                       dt.datetime.min.time())
        return start_date, end_date

    def getNumDays(self):
        """Getter function for the number of weekdays between the start and
        end dates."""
        start_date = dt.datetime.combine(self.deStart.date().toPyDate(),
                                         dt.datetime.min.time())
        end_date = dt.datetime.combine(self.deEnd.date().toPyDate(),
                                       dt.datetime.min.time())

        if start_date.weekday() == 5:
            start_date += timedelta(days=2)
        if start_date.weekday() == 6:
            start_date += timedelta(days=1)

        days = 0
        curr_date = start_date
        while curr_date != end_date:
            curr_date += timedelta(days=1)
            if curr_date.weekday() == 5:
                continue
            elif curr_date.weekday() == 6:
                continue
            else:
                days += 1

        return days