How to Create a Loop That Runs Every 5 Minutes

Hi. Since this is such a simple problem, I’d usually just solve it myself, but since school has started, I have way less time for personal projects.

So I have a Discord bot with an events function that I made. Basically, it checks every minute to see if it has a task to do. But I found that it causes an issue. I’m getting rate-limited by Discord. So I need to slow down the checking events task to every five minutes.

Now I need code that will cause the events task to start when the current minute is evenly divisible by five, and the seconds’ count is zero. That’s easy.

import datetime
now = datetime.datetime.now()
if now.minute % 5 == 0 and now.second == 0:
    startTask()

But the problem is when those two conditions aren’t met. Five minutes is 300 seconds. How do I do it so that when the current minute count is not evenly divisible by 5 it will wait for however long it is till the next evenly divisible by five minute, then start the task?

What tasks would it need to do? What determines if it needs to perform a task?

I would look at Discord’s webhooks feature that allows you to receive information about activities as they occur instead of waiting X number of minutes to take action.

This may help:

Uhh, I’m not asking about what I need the task to do. I’ve already got that functioning. I simply need the task to start when the current second count is equal to 0, and the current minute count is evenly divisible by five.

The current script that you wrote that checks every minute, what does it look like? How are you running that script (on a cron job)? Why not just run your task script on a cron job? You can specify every 1, 5 minutes or whatever time schedule you want.

I suggested using the webhooks, so in case the tasks you said you were performing (or checked to see if they needed to be performed) could be done more real-time instead of waiting 5 minutes to perform them. Since you did not answer what these tasks are, I was merely making a suggestion that the webhooks might be a better choice than waiting an arbitrary number of minutes.

1 Like

I can’t use threading on this one. I initially attempted to use threading to make the task execute, but threading is not compatible with Discord.py’s functions. Sending a message in a chat would throw an error. I’m using Discord.py’s built-in @task function.

Here’s my existing code for starting the task:

def __init__(self, bot):
      self.bot = bot
      now = datetime.datetime.now()
      if now.second != 0:
        sleep(60 - now.second)
        self.checkevents.start()
        log("info", "Event checking task started.")
      else:
        self.checkevents.start()
        log("info", "Event checking task started.")

(The log function is something I made.)

I apologize, I didn’t realize that.
Would something like this api work? discord.ext.tasks – asyncio.Task helpers

This is what I’m using already, but I need the task to start exactly when the current minute is evenly divisible by five, and the current second count is zero. Here’s a bit more of the code for the cog:

class Events(commands.Cog, description='Create and manage events.'):
    def __init__(self, bot):
      self.bot = bot
      now = datetime.datetime.now()
      if now.second != 0:
        sleep(60 - now.second)
        self.checkevents.start()
        log("info", "Event checking task started.")
      else:
        self.checkevents.start()
        log("info", "Event checking task started.")
      
    # Event checking background function
    @tasks.loop(seconds=60)
    async def checkevents(self):
        #code stuff here

I’ve never had to write a very precise timer in a real world application. Is it really your use case requirement that you must time things on the dot or can you just time things more or less every 5 minutes?

Edit: my personal experience is with threaded Java apps. Basically we’d just make the current thread sleep for whatever time period not really caring how exact that is. (Sleep for some cycles essentially and return)

The way the event system in my bot works is overall rather simple, but it requires the task to run every minute on the minute. Otherwise, it will miss events that should be announced.

I finally got some time to sit down and attempt to get it working, so here’s what I have so far. This code is not functioning, but hopefully, it will give you an idea of what I need my code to do.

import datetime
from time import sleep

now = datetime.datetime.now()

listOfFiveMinuteInts = ["5", "10", "20", "25", "30", "35", "40", "45", "50", "55", "60"]

def task():
    print('Task started.')

# Is not at correct conditions
if now.minute % 5 != 0:
    for x in listOfFiveMinuteInts:
        y = int(x) - now.minute
        print(y)
        if 0 <= y < 5:
            currentMinute = y
    currentSecond = 60 - now.second
    currentMinute = currentMinute * 60 - currentSecond
    sleep(currentMinute + currentSecond)
    task()
# Is at correct conditions
elif now.minute % 5 == 0 and now.second == 0:
    task()

But my concern is that you are not accounting for the fact that between the time you check the system clock and the time any of your lines of code run, time is still passing!
I think in very high level terms what you maybe are thinking of is some king of “watcher” who sits around and watches the clock all day till it hits the desired precise increment, at which point it asynchronously sends some signal out for some other process to do work while the “watcher” thread continues on watching….

So two threads.
One watching and one executing when the clock strikes the required number.

Something I found online about the design pattern you may need https://www.reddit.com/r/Python/comments/lngfnw/i_never_knew_events_were_this_powerful_a_python/?utm_source=share&utm_medium=ios_app&utm_name=iossmf

That’s a good video, but I don’t see a way for it to cover what I need it to. Why don’t you check out the Discord bot on Repl.it. The cog is under cogs/not working cogs/eventcog.py

okay so here’s my attempt to play this game:

import datetime
from time import sleep

while (1):
    now = datetime.datetime.now()
    print(now)
    # how many more seconds do I need to sleep ?
    sleepSeconds = (5 * 60) - (now.second + ((now.minute % 5) * 60))
    print(f"sleep for {sleepSeconds} seconds")
    if ( sleepSeconds == 300 ):
        print(f"just get this done on the dot {now.minute}:{now.second}")
    else:
        sleep(sleepSeconds) #snooze till the magic min

The above sample code does the following:

  • get the current time
  • figure out how many seconds there are left to the next desired mark (5 min 0 sec)
  • if we are at that mark, prints
  • if we are not, sleeps for the delta

Problems with the above of course:

  • we end up running the print inside the first half of the if many times over till the second value moves up (you can fix that with a flag and a conditional)
  • there is a ‘creep’ happening as we run because the code takes some milliseconds to execute and that means we may end up crossing over in some cases. Like if the current min/sec is 54:59 for eg, then the code will sleep for 1 sec and by that time we may no longer be at 55:00 exactly (we may have passed it to 55:01)

Not sure if any of this helps but thanks for an interesting question anyway.

Thank you so much. That should work. I will attempt to add some handling for milliseconds, but I might just not worry about that as the bot gets restarted enough for the time to be relatively close to where it should be.

1 Like

Also you have to account for this code starting at the exact time you wanted it to do something so in addition to checking for sleepSeconds is 300, you should check if it is 0.

I haven’t added handling for seconds equaling zero yet, but I will eventually. Thank you so much. It worked perfectly. All I had to do was add the break keyword after the task was started, and it worked.

1 Like