# This Python (PyQt6) app calculates the number of full moons between two dates. # The calculation is derived based on the first full moon of the 20th Century, # which occurred on January 15, 1900 at 19:06 GMT, using the lunar synodic period # of 29.53059 days to derive successive (or previous) full moon dates. # All dates/times are with respect to Greenwich Meridian Time (GMT) and assume # that both start and end dates conform to the Gregorian Calendar. # # Initial version of Class Ui_FMFMainWindow created by PyQt6 UI code generator 6.4.2 # Ui_FMFMainWindow attributes specification: qss/FMFstyles.qss # #################################################################################### from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import QDateTime, QDate from PyQt6.QtGui import QCursor # Generated code, with minor modifications. class Ui_FMFMainWindow(object): def setupUi(self, FMFMainWindow): FMFMainWindow.setObjectName("FMFMainWindow") FMFMainWindow.setFixedSize(680, 720) self.centralwidget = QtWidgets.QWidget(parent=FMFMainWindow) self.centralwidget.setObjectName("centralwidget") self.groupBox_startDT = QtWidgets.QGroupBox(parent=self.centralwidget) self.groupBox_startDT.setGeometry(QtCore.QRect(10, 10, 660, 261)) self.groupBox_startDT.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.groupBox_startDT.setObjectName("groupBox_startDT") self.label_startheader = QtWidgets.QLabel(parent=self.groupBox_startDT) self.label_startheader.setGeometry(QtCore.QRect(220, 10, 200, 31)) self.label_startheader.setObjectName("label_startheader") self.calendar_startDT = QtWidgets.QCalendarWidget(parent=self.groupBox_startDT) self.calendar_startDT.setGeometry(QtCore.QRect(16, 50, 351, 191)) self.calendar_startDT.setGridVisible(True) self.calendar_startDT.setObjectName("calendar_startDT") self.calendar_startDT.clicked.connect(updateStartDTCalc) self.calendar_startDT.currentPageChanged.connect(updateStartDTCalc) self.timeEdit_startDT = QtWidgets.QTimeEdit(parent=self.groupBox_startDT) self.timeEdit_startDT.DateSections_Mask = False self.timeEdit_startDT.setGeometry(QtCore.QRect(390, 50, 100, 31)) self.timeEdit_startDT.setTimeSpec(QtCore.Qt.TimeSpec.UTC) self.timeEdit_startDT.setObjectName("timeEdit_startDT") self.timeEdit_startDT.timeChanged.connect(updateStartDTCalc) self.line_startsep = QtWidgets.QFrame(parent=self.groupBox_startDT) self.line_startsep.setGeometry(QtCore.QRect(390, 120, 250, 16)) self.line_startsep.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line_startsep.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) self.line_startsep.setObjectName("line_startsep") self.label_startDTcalc = QtWidgets.QLabel(parent=self.groupBox_startDT) self.label_startDTcalc.setGeometry(QtCore.QRect(390, 170, 250, 31)) self.label_startDTcalc.setObjectName("label_startDTcalc") self.groupBox_endDT = QtWidgets.QGroupBox(parent=self.centralwidget) self.groupBox_endDT.setGeometry(QtCore.QRect(10, 290, 660, 261)) self.groupBox_endDT.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.groupBox_endDT.setObjectName("groupBox_endDT") self.label_endheader = QtWidgets.QLabel(parent=self.groupBox_endDT) self.label_endheader.setGeometry(QtCore.QRect(230, 10, 200, 31)) self.label_endheader.setObjectName("label_endheader") self.calendar_endDT = QtWidgets.QCalendarWidget(parent=self.groupBox_endDT) self.calendar_endDT.setGeometry(QtCore.QRect(16, 50, 351, 191)) self.calendar_endDT.setGridVisible(True) self.calendar_endDT.setObjectName("calendar_endDT") self.calendar_endDT.clicked.connect(updateEndDTCalc) self.calendar_endDT.currentPageChanged.connect(updateEndDTCalc) self.timeEdit_endDT = QtWidgets.QTimeEdit(parent=self.groupBox_endDT) self.timeEdit_endDT.setGeometry(QtCore.QRect(390, 50, 100, 31)) self.timeEdit_endDT.setTimeSpec(QtCore.Qt.TimeSpec.UTC) self.timeEdit_endDT.setObjectName("timeEdit_endDT") self.timeEdit_endDT.timeChanged.connect(updateEndDTCalc) self.line_endsep = QtWidgets.QFrame(parent=self.groupBox_endDT) self.line_endsep.setGeometry(QtCore.QRect(390, 120, 250, 16)) self.line_endsep.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line_endsep.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) self.line_endsep.setObjectName("line_endsep") self.label_endDTcalc = QtWidgets.QLabel(parent=self.groupBox_endDT) self.label_endDTcalc.setGeometry(QtCore.QRect(390, 170, 250, 31)) self.label_endDTcalc.setObjectName("label_endDTcalc") self.calcResults = QtWidgets.QLabel(parent=self.centralwidget) self.calcResults.setGeometry(QtCore.QRect(10, 650, 660, 31)) self.calcResults.setObjectName("calcResults") self.resultButton = QtWidgets.QPushButton(parent=self.centralwidget) self.resultButton.setGeometry(QtCore.QRect(300, 570, 72, 72)) self.resultButton.setIcon(QtGui.QIcon('img/moon.png')) self.resultButton.setIconSize(QtCore.QSize(64,64)) self.resultButton.setCursor(QtCore.Qt.CursorShape.PointingHandCursor) self.resultButton.setToolTip("Click or Press to Calculate") self.resultButton.setObjectName("resultButton") self.resultButton.clicked.connect(reportFMResults) FMFMainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(FMFMainWindow) QtCore.QMetaObject.connectSlotsByName(FMFMainWindow) # initialize the date selection fields to the current date/time. updateStartDTCalc() updateEndDTCalc() def retranslateUi(self, FMFMainWindow): _translate = QtCore.QCoreApplication.translate FMFMainWindow.setWindowTitle(_translate("FMFMainWindow", "Full Moon Fever")) self.label_startheader.setText(_translate("FMFMainWindow", "Start Date and Time")) self.label_startDTcalc.setText(_translate("FMFMainWindow", "")) self.label_endDTcalc.setText(_translate("FMFMainWindow", "")) self.label_endheader.setText(_translate("FMFMainWindow", "End Date and Time")) self.calcResults.setText(_translate("FMFMainWindow", "Calculation Results")) #self.resultButton.setText(_translate("FMFMainWindow", "Results")) #self.menuFull_Moon_Fever.setTitle(_translate("FMFMainWindow", "Full Moon Fever")) ######################################################################################### # Function to calculate the number of full moons in an interval def number_of_full_moons(dt_start, dt_end): # Synodic period of the moon (in minutes) m_moon_period = 29.53059 * 24 * 60 # datetime of first full moon after earliest start time dt_first_20c_full_moon = QDateTime(1900, 1, 15, 19, 6) # difference in minutes between dt_start and first 20c full moon m_prior_to_start = qdtdiff(dt_first_20c_full_moon, dt_start) n_prior_to_start = int(m_prior_to_start / m_moon_period) # difference in minutes between dt_end and first 20c full moon m_prior_to_end = qdtdiff(dt_first_20c_full_moon, dt_end) n_prior_to_end = int(m_prior_to_end / m_moon_period) full_moons = n_prior_to_end - n_prior_to_start return int(full_moons) # Function to format the result def format_results(dt_start, dt_end, full_moons): start_date = convert(dt_start) end_date = convert(dt_end) fm = str(full_moons) if full_moons == 0: out = "There are no full moons between " + start_date + " and " + end_date elif full_moons == 1: out = "There is one full moon between " + start_date + " and " + end_date else: out = "There are " + fm + " full moons between " + start_date + " and " + end_date return out # Function to calculate the difference between two QDateTime values def qdtdiff(dt1, dt2): msdiff = dt1.msecsTo(dt2) / 60000 return msdiff # Function to convert QDateTime to string def convert(date_time): fmt = "ddd MMM d, yyyy hh:mm" return date_time.toString(fmt) # Functions to update the Start D/T based on user date and time selections def updateStartDTCalc(): date = updateStartDate() time = ui.timeEdit_startDT.time() datetime = convert(QDateTime(date, time)) ui.label_startDTcalc.setText(datetime) def updateStartDate(): year = ui.calendar_startDT.yearShown() month = ui.calendar_startDT.monthShown() day = ui.calendar_startDT.selectedDate().day() return QDate(year, month, day) # Functions to update the End D/T based on user date and time selections def updateEndDTCalc(): date = updateEndDate() time = ui.timeEdit_endDT.time() datetime = convert(QDateTime(date, time)) ui.label_endDTcalc.setText(datetime) def updateEndDate(): year = ui.calendar_endDT.yearShown() month = ui.calendar_endDT.monthShown() day = ui.calendar_endDT.selectedDate().day() return QDate(year, month, day) # Function to display the results in a consistent format, # switching the start/end DTs if necessary. def reportFMResults(): date_start = updateStartDate() date_end = updateEndDate() time_start = ui.timeEdit_startDT.time() time_end = ui.timeEdit_endDT.time() dt_start = QDateTime(date_start, time_start) dt_end = QDateTime(date_end, time_end) if dt_start > dt_end: temp = dt_end dt_end = dt_start dt_start = temp full_moons = number_of_full_moons(dt_start, dt_end) result = format_results(dt_start, dt_end, full_moons) ui.calcResults.setText(result) ##################################################################### if __name__ == "__main__": import sys, os basedir = os.path.dirname(__file__) app = QtWidgets.QApplication(sys.argv) app.setWindowIcon(QtGui.QIcon(os.path.join(basedir, 'img/moon.ico'))) stylesheet = os.path.join(basedir, 'qss/FMFstyles.qss') with open(stylesheet, 'r') as f: style = f.read() # print("QSS Contents: " + style) app.setStyleSheet(style) FMFMainWindow = QtWidgets.QMainWindow() ui = Ui_FMFMainWindow() ui.setupUi(FMFMainWindow) FMFMainWindow.show() sys.exit(app.exec()) ###################################################################### ### QSS specification - the following code is in the file ### qss/FMFstyles.qss ###################################################################### text/x-generic FMFstyles.qss ( ASCII text ) QMainWindow#FMFMainWindow { background-image: url('img/twilight2b.jpg'); } QLabel#label_startheader { font-size: 20px; font-weight: bold; text-align: center; color: #000; } QLabel#label_endheader { font-size: 20px; font-weight: bold; text-align: center; color: #000; } QLabel#label_startDTcalc { font-size: 20px; font-weight: bold; text-align: left; color: #000; border-style: inset; border-radius: 2px; background-color: #999; } QLabel#label_endDTcalc { font-size: 20px; font-weight: bold; text-align: left; color: #000; border-style: inset; border-radius: 2px; background-color: #999; } QCalendarWidget#calendar_startDT { border-style: inset; border-radius: 2px; background-color: #999; } QCalendarWidget#calendar_endDT { border-style: inset; border-radius: 2px; background-color: #999; } QGroupBox#groupBox_startDT { border: 5px solid #999; border-style: outset; border-radius: 8px; background-color: #ddd } QGroupBox#groupBox_endDT { border: 5px solid #999; border-style: outset; border-radius: 8px; background-color: #ddd } QLabel#label_startDTcalc { margin-left: 4px; } QLabel#label_endDTcalc { margin-left: 4px; } QLabel#calcResults { margin-left: 4px; background-color: #003366; color: #ffffff; font-size: 16px; font-weight: bold; text-align: left; border-style: inset; } QPushButton { border-style: outset; border-radius: 5px; font-size: 16px; font-weight: bold; text-align: center; }