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?
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.
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.
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.
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
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.
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.