I'm currently working on a python to-do application. All user created events are stored in a dictionary called toDoEvents, with title, due_time, remind_time, and description. Right now I'm working on a notification system using toast. I need to constantly check the list of remind_dates for every event and if it matches the current time, calls the toast notification.
I want to do something like this, except constantly checking it every time the minute changes:
from win10toast import ToastNotifier
import time
from datetime import datetime
from PyQt5.QtCore import QDateTime
toDoEvents = {}
class TodoEvent:
def __init__(self, title, due_time, remind_time, description):
self.title = title
self.due_time = due_time
self.remind_time = remind_time
self.description = description
toDoEvents[self.title] = self
def change_title(self, new_title):
self.title = new_title
def change_due_time(self, new_due_time):
self.due_time = new_due_time
def change_remind_time(self, new_remind_time):
self.remind_time = new_remind_time
def change_description(self, new_description):
self.description = new_description
event1 = TodoEvent("test", QDateTime(2019, 10, 26, 1, 18).toPyDateTime(), QDateTime(2019, 10, 26, 1, 18).toPyDateTime(), "test description")
for event in toDoEvents.values():
if event.remind_time.strftime("%m/%d/%Y, %H:%M") == datetime.now().strftime("%m/%d/%Y, %I:%M"):
toaster = ToastNotifier()
toaster.show_toast(event.title, event.description, threaded=True, icon_path=None, duration=8)
while toaster.notification_active():
time.sleep(0.1)
What is the most efficient way to do this? Edit: want to execute code every time the minute changes, not every 60 seconds (which assumes the user starts the program at exactly 0 seconds)
I'm adding another answer as the update to the question has made the other one a bit off topic, but I prefer to leave it there as I believe that it could be useful to others, even if slightly unrelated to the topic at matter.
The example below subclasses QTimer making it a timer that "timeouts" at minute changes only. It might seem a bit complicated, but that's due to the nature of QTimers, which can be not as precise as one would expect (as most timers are); nonetheless it allows better implementation while avoiding unnecessary timeout signal emissions, as sometimes the timeout signal could be emitted before the end of the minute, thus emitting it again when the minute actually changes.
The singleShot
setting is really not required, as the timer will be restarted anyway with the correct interval (constant checks are required, as some hard working process might happen in the meantime, causing the timer to shift after the minute change). I just added it as I think it might slightly improve performance in case of high amount of timers, but I may be wrong. In any case, it will work anyway even without it.
class MinuteTimer(QTimer):
customTimeout = pyqtSignal()
def __init__(self, parent=None):
super(MinuteTimer, self).__init__(parent)
self.setSingleShot(True)
# default timer is CoarseTimer, which *could* fire up before, making it
# possible that the timeout is called twice
self.setTimerType(Qt.PreciseTimer)
# to avoid unintentional disconnection (as disconnect() without
# arguments disconnects the signal from *all* slots it's connected
# to), we "overwrite" the internal timeout signal attribute name by
# by switching it with our customTimeout signal instead, keeping
# its consistency with QTimer objects, at least by object naming
self._internalTimeout = self.timeout
self._internalTimeout.connect(self.startAdjusted)
self._internalTimeout.connect(self.customTimeout)
self.timeout = self.customTimeout
def start(self):
now = QTime.currentTime()
nextMinute = QTime(now.hour(), now.minute()).addSecs(60)
# if the next minute happens at or after the midnight of the day
# after (in this specific case, when "now" is at 23:59), msecsTo()
# will return a negative value, since QTime has no date reference:
# 14:30:00 to 14:29:00 is -60 seconds, so 23:59:00 to 00:00:00 is
# -86340 seconds; using the % modulus operator against the
# millisecond count in a day can give us the correct positive
# difference that can be used as an interval for QTimer
QTimer.start(self, now.msecsTo(nextMinute) % 86400000)
def startAdjusted(self):
now = QTime.currentTime()
# even when using a PreciseTimer, it is possible that the timeout
# is called before the minute change; if that's the case, we
# add 2 minutes, just to be "better safe than sorry"
addSecs = 120 if now.second() > 50 else 60
nextMinute = QTime(now.hour(), now.minute()).addSecs(addSecs)
QTimer.start(self, now.msecsTo(nextMinute) % 86400000)
def minuteEvent():
print('minute changed!')
for event in toDoEvents.values():
if event.remind_time.strftime("%m/%d/%Y, %H:%M") == datetime.now().strftime("%m/%d/%Y, %I:%M"):
toaster = ToastNotifier()
toaster.show_toast(event.title, event.description,
threaded=True, icon_path=None, duration=8)
if __name__ == '__main__':
app = QApplication(sys.argv)
minuteTimer = MinuteTimer()
minuteTimer.timeout.connect(minuteEvent)
minuteTimer.start()
sys.exit(app.exec_())
Obviously, it is possible that the timer emits its signal before the minute change (resulting in a failed notification if the next signal is emitted after its minute change), but that's mostly due to your implementation, and I believe that's up to you to check for a time range to ensure that each todo is catched when it should be.
As a possible alternative, you can emit a signal with the expected "HH:mm" timing (using statements similar to the addSecs = 120 if now.second() > 50 else 60
I used) and then check for the actual timing of the reminder.