不断检查时间列表和当前时间并显示通知

Constantly Check Through a List of Times and Current Time and Display Notification

我目前正在开发 python to-do 应用程序。所有用户创建的事件都存储在名为 toDoEvents 的字典中,包含标题、due_time、remind_time 和描述。现在我正在使用 toast 开发一个通知系统。我需要不断检查每个事件的 remind_dates 列表,如果它与当前时间匹配,则调用 toast 通知。

我想做这样的事情,除了每次分钟变化时不断检查它:

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)

最有效的方法是什么? 编辑:想要在每次分钟更改时执行代码,而不是每 60 秒执行一次(假设用户恰好在 0 秒启动程序)

事实上,我正在编写几乎相同类型的程序,我建议使用QTimer instead of constantly checking the current time. Anyway since QTimer default timing has a 5% interval margin (which might be very high for long intervals), you might want to set its timerTypeQt.PreciseTimer

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
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.showReminder)
        self.timer.setTimerType(Qt.PreciseTimer)
        self.change_remind_time(remind_time)

    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
        self.timer.start(QDateTime.currentDateTime().msecsTo(self.remind_time))

    def change_description(self, new_description):
        self.description = new_description

    def showReminder(self):
        toaster = ToastNotifier()
        toaster.show_toast(self.title, self.description, threaded=True, icon_path=None, duration=8)

event1 = TodoEvent("test", 
                   QDateTime(2019, 10, 26, 1, 18), 
                   QDateTime(2019, 10, 26, 1, 18), 
                   "test description")

请注意,QTimer 间隔以毫秒为单位表示为 整数,因此日期间隔大于 32 位整数的最大值时可能会出现问题。标准整数的最大正值为 2147483647,以毫秒为单位时,为 24 天 20 小时 31 分 23 秒 647 毫秒。

这意味着如果您因某个日期的间隔大于该值的毫秒数而收到提醒,您将得到一个无效计时器(间隔等于 -1)。
虽然普通人不太可能让 his/her 计算机保持开启那么长的时间,但这并非不可能(想想经常 运行 数周而不重启的便携式设备),因此您可能想要添加一个函数检查过长间隔的到期时间,并最终添加另一个计时器,该计时器将 "restore" 自身直到间隔变得有效,然后启动通知的实际计时器。

我正在添加另一个答案,因为问题的更新使另一个问题有点偏离主题,但我更愿意将其留在那里,因为我相信它可能对其他人有用,即使与主题。

下面的示例对 QTimer 进行了子类化,使其成为一个 "timeouts" 仅在分钟内更改的计时器。它可能看起来有点复杂,但这是由于 QTimers 的性质,它 可以 不像人们期望的那样精确(大多数计时器都是);尽管如此,它允许更好的实现,同时避免不必要的超时信号发射,因为有时超时信号可以在分钟结束前 发射,因此当分钟实际改变时再次发射它。

真的不需要singleShot设置,因为计时器无论如何都会以正确的间隔重新启动(需要不断检查,因为在此期间可能会发生一些艰苦的工作过程,导致计时器偏移分钟更改后)。我只是添加了它,因为我 认为 它可能会在大量计时器的情况下略微提高性能,但我可能是错的。无论如何,即使没有它,它也能正常工作。

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

显然,计时器有可能在分钟更改之前发出信号(如果下一个信号在分钟更改后发出,则会导致通知失败),但这主要是由于您的实现,我相信这是由您检查一个时间范围,以确保每个待办事项都在应有的时间被捕获。
作为一种可能的替代方法,您可以使用预期的 "HH:mm" 时间发出信号(使用类似于我使用的 addSecs = 120 if now.second() > 50 else 60 的语句),然后检查提醒的实际时间。