Python Forum
Help adding a loop inside a loop
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help adding a loop inside a loop
#11
I think I understand your example. It prints the reminder 5sec from the time you started the program. Correct?

But I'm still confused on how to make my reminders work where they execute on their set dates & times.
When the reminder's date & time == the currentdate & currenTime I want to trigger the reminderPopup(), but how would the scheduler know when the reminder's date/time == the current date/time?


import sched
import time
import sqlite3
from BX_External_Functions import checkForReminders, reminderPopup
from BX_Constants import MainDatabase, currentdate, currentTime
  
#Create schedule
scheduler = sched.scheduler(time.time, time.sleep)

#Read in reminders from database
# Connect to the database
connection = sqlite3.connect(MainDatabase)
cursor = connection.cursor()
# Get the Reminder & it's scheduled Date & Time
cursor.execute("SELECT Reminder, Date, Time FROM Reminders")
Results = cursor.fetchall()
connection.commit()
connection.close()

#For each reminder, add an event to the schedule (enter/enterabs)
for row in Results:
    print(row)

scheduler.enterabs() #How would I add the reminder as an event that triggers on it's set date/time?

#If a new reminder is created, add it to the database & add an event to the schedule


#Inside Baxter's while True loop, call schedule.run(blocking=False)
print ('Checking for reminders...')
scheduler.run(False)
Reply
#12
Did you run my example? What it does is pretty obvious when you run the example.

The scheduler knows when to run an event because you pass the scheduled time as one of the arguments to schedule.enter() or schedule.enterabs(). enter(delta) schedules the event to run when the clock reaches current_time + delta. enterabs(abs_time) schedules the event to run when the clock reaches abs_time.

You don't need to call cursor.commit() unless you did something that changes the database. SELECT does not change the database.

You don't need to use fetchall().

Instead of this:
connection = sqlite3.connect(MainDatabase)
cursor = connection.cursor()
# Get the Reminder & it's scheduled Date & Time
cursor.execute("SELECT Reminder, Date, Time FROM Reminders")
Results = cursor.fetchall()
connection.commit()
connection.close()
 
#For each reminder, add an event to the schedule (enter/enterabs)
for row in Results:
    print(row)
Do this:
connection = sqlite3.connect(MainDatabase)
cursor = connection.cursor()
# Get the Reminder & it's scheduled Date & Time
for row in cursor.execute("SELECT Reminder, Date, Time FROM Reminders")
    print(row)
connection.close()
You really don't even need the cursor.
connection = sqlite3.connect(MainDatabase)
# Get the Reminder & it's scheduled Date & Time
for row in connection.execute("SELECT Reminder, Date, Time FROM Reminders"):
    print(row)
connection.close()
Why are you saving the schedule time as Date, Time instead of DateTime? I don't know what you have for Date or Time, so in the code below I use a magical function that combines date and time to give you datetime.
def send_reminder(message, scheduled_time):
    """Function called when time to send reminder"""
    print(f"{scheduled_time}: {message}

def schedule_reminder(reminder, date, time):
    """Schedule a reminder"""
    scheduled_time = date_and_time_to_datetime(date, time)
    schedule.enterabs(schedule_time.timestamp(), send_reminder, arguments=(reminder, scheduled_time))

...
#somewhere in the startup portion of the main code
# Schedule all the reminders
schedule = sched.scheduler(time.time, time.sleep)
connection = sqlite3.connect(MainDatabase)
for reminder in connection.execute("SELECT Reminder, Date, Time FROM Reminders"):
    schedule_reminder(*reminder)
connection.close()

...
# somewhere later in your main code
while True:
    schedule.run(False)
    # do other stuff
Reply
#13
Thanks, that helped clean up the SQL part of the code, but I'm still confused on the scheduler part of the code.

So this function takes in the reminder & it's scheduled date and time, then it calls the send_reminder function but I don't get what the schedule_time.timestamp() does?
Quote:
def schedule_reminder(reminder, date, time):
    """Schedule a reminder"""
    scheduled_time = date_and_time_to_datetime(date, time)
    schedule.enterabs(schedule_time.timestamp(), send_reminder, arguments=(reminder, scheduled_time))



And this is what I have for getting the current date & time by the way.
#--------------------------------------------------
#              Current Date & Time
#--------------------------------------------------
import datetime
currentdate = datetime.date.today()

from datetime import datetime
timeNow = datetime.now()
currentTime = timeNow.strftime("%I:%M %p")
#--------------------------------------------------
And this is what I have for my reminders:
Output:
('Test 1', '2022-10-10', '4:30 PM') ('Test 2', '2022-10-10', '5:00 PM')
Reply
#14
scheduler.enterabs() takes a time argument.

https://docs.python.org/3/library/sched.html

Quote:scheduler.enterabs(time, priority, action, argument=(), kwargs={})
Schedule a new event. The time argument should be a numeric type compatible with the return value of the timefunc function passed to the constructor. Events scheduled for the same time will be executed in the order of their priority. A lower number represents a higher priority.
I tried using datetime.now as the timefunc, but got an error when calling scheduler.run()
Error:
File "C:\Program Files\Python310\lib\sched.py", line 149, in run delayfunc(time - now) TypeError: 'datetime.timedelta' object cannot be interpreted as an integer
I decided to use time.time, as is shown in the docuentation and all examples If you use time.time as the timefunc for the scheduler, the time for the enter() and enterabs() must be compatible with the value returned by time.time(). According to the documentation for time.time():

https://docs.python.org/3/library/time.html
Quote:time.time() → float
Return the time in seconds since the epoch as a floating point number. The specific date of the epoch and the handling of leap seconds is platform dependent. On Windows and most Unix systems, the epoch is January 1, 1970, 00:00:00 (UTC) and leap seconds are not counted towards the time in seconds since the epoch. This is commonly referred to as Unix time. To find out what the epoch is on a given platform, look at gmtime(0).
datetime.timestamp() is an object method that converts a datetime object into an int that is compatible with time.time().

You should really save the reminder time as a datetime object. You can display the information any way you like but store it in a way that makes it easy to use. Having separate date and time columns makes it difficult to do things like sort your reminders by time or delete old reminders. Here I have to recombine the two values to get the datetime object that I need.
def schedule_reminder(reminder, date, time):
    """Schedule a reminder"""
    dt = datetime.strptime(f"{date} {time}", "%Y-%m-%d %I:%M %p")
    schedule.enterabs(
        time=schedule_time.timestamp(),
        priority=1,   # I previously forgot that you must specify a priority
        action=send_reminder,
        arguments=(reminder, scheduled_time))
Reply
#15
First off, I just want to say thanks a lot for your time & patience in helping me to understand this, I really appreciate it.

So I got it to run, but there are still a couple of things I'm not sure about.

(1) Why do all the reminders popup when they haven't reached their scheduled/set time yet?
Ex: If you look at the output, the current time was 11:23 AM but all the reminders popped up even though they were set at 11:25, 11:28, & 11:30.
Is there a way to get it so nothing would have popped up until 11:25 (Then only the "Test 1" reminder should pop up because it's scheduled for 11:25, not the rest of them)?

(2) How do I get the AM/PM to display? I know the "%p" is supposed to do it in: "%Y-%m-%d %I:%M %p" but it doesn't show.

Thanks again.

My Output from (Test.py -> The code shown below):
Output:
Reminders in Database: ('Test 1', '2022-10-15 11:25:00') Reminders in Database: ('Test 2', '2022-10-15 11:28:00') Reminders in Database: ('Test 3', '2022-10-15 11:30:00') Scheduled Time: ('2022-10-15 11:25:00',) Scheduled Time: ('2022-10-15 11:28:00',) Scheduled Time: ('2022-10-15 11:30:00',) Current Date Time: 2022-10-15 11:23:00 Reminders Popup: ('2022-10-15 11:25:00',): Test 1 Reminders Popup: ('2022-10-15 11:28:00',): Test 2 Reminders Popup: ('2022-10-15 11:30:00',): Test 3
The code I have now (Test.py -> Not link to my main code yet):
import sched
import time
import sqlite3
from BX_External_Functions import checkForReminders, reminderPopup
from BX_Constants import MainDatabase, currentdate, currentTime
  
#Create schedule
scheduler = sched.scheduler(time.time, time.sleep)

#Read in reminders from database
#For each reminder, add an event to the schedule (enter/enterabs)
connection = sqlite3.connect(MainDatabase)
for row in connection.execute("SELECT Reminder, DateTime FROM Reminders"):
    print("Reminders in Database: ", row)
connection.close()

print("\n")

connection = sqlite3.connect(MainDatabase)
for row in connection.execute("SELECT DateTime FROM Reminders"):
    scheduled_time = row
    print("Scheduled Time: ",  scheduled_time)
connection.close()

import datetime
currentdate = datetime.date.today()
from datetime import datetime
timeNow = datetime.now()
currentTime = timeNow.strftime("%I:%M %p")

Current_DateTime = datetime.strptime(f"{currentdate} {currentTime}", "%Y-%m-%d %I:%M %p")
print("\nCurrent Date Time: ", Current_DateTime ,"\n")

# #If a new reminder is created, add it to the database & add an event to the schedule
# scheduler.enterabs()

# #Inside Baxter's while True loop, call schedule.run(blocking=False)
# print ('Checking for reminders...')
# scheduler.run(False)

def schedule_reminder(reminder):
    """Schedule a reminder"""
    scheduler.enterabs(
        time=Current_DateTime.timestamp(),
        priority=1,  
        action=send_reminder,
        argument=(reminder, scheduled_time))

def send_reminder(Reminder, scheduled_time):
    """Function called when time to send reminder"""
    print(f"Reminders Popup: {scheduled_time}: {Reminder}")


#Read in reminders from database
connection = sqlite3.connect(MainDatabase)
for reminder in connection.execute("SELECT Reminder FROM Reminders"):
    schedule_reminder(*reminder)
connection.close()

# somewhere later in your main code
while True:
    scheduler.run(False)
    # do other stuff
And this is how my Reminders Table/Database is set up:
def createBXDatabase():
        #Create a database (BX_Database.db)
        connection = sqlite3.connect("BX_Database.db")
        cursor = connection.cursor()
        #--------------------------------------------
        #               Reminders Table
        #--------------------------------------------
        remindersTable = """CREATE TABLE IF NOT EXISTS Reminders
                (ID INTEGER PRIMARY KEY  AUTOINCREMENT,
                Reminder            TEXT,
                DateTime            TEXT,
                DateAdded           datetime default current_timestamp);"""

        #Execute the creation of the table
        cursor.execute(remindersTable)
        #Commit the changes
        connection.commit()
        #Close the connection
        connection.close()
Reply
#16
You scheduled all your events for the same time (Current_DateTime.timestamp()). When you print a datetime object it uses the default datetime format. If you want a specific format you need to use datetime.strftime(format_str) with an appropriate format str.
import sched
import time
from datetime import datetime, timedelta

time_format = "%I:%M:%S %p"

def send_reminder(message):
    print(f"{datetime.now().strftime(message)}> {message}")

def schedule_reminder(scheduled_time):
    """Schedule a reminder"""
    schedule.enterabs(
        time=scheduled_time.timestamp(),
        priority=1,  
        action=send_reminder,
        argument=(scheduled_time.strftime(time_format),))

schedule = sched.scheduler(time.time)
now = datetime.now()
for delay  in (0.1, 0.2, 0.5, 1):
    schedule_reminder(now + timedelta(minutes=delay))
 
while not schedule.empty():
    schedule.run()
Output:
12:51:04 PM> 12:51:04 PM 12:51:10 PM> 12:51:10 PM 12:51:28 PM> 12:51:28 PM 12:51:58 PM> 12:51:58 PM
Reply
#17
If I do time=scheduled_time.timestamp(), (like I did in the code below)
I get this error
Error:
time=scheduled_time.timestamp(), AttributeError: 'tuple' object has no attribute 'timestamp'
because the Scheduled_time is being taken from the SQLite database.

Is there a way to get around this error?
How do I convert those tuple objects to a datetime object (is that even possible)?

connection = sqlite3.connect(MainDatabase)
for row in connection.execute("SELECT DateTime FROM Reminders"):
    scheduled_time = row
    print("Scheduled Time: ",  scheduled_time)
connection.close()
def schedule_reminder(reminder):
    """Schedule a reminder"""
    scheduler.enterabs(
        time=scheduled_time.timestamp(),
        priority=1,  
        action=send_reminder,
        argument=(reminder, scheduled_time))
Reply
#18
After playing with this a bit, I don't think datetime is the best way to save a datetime object. Following my advice that save values in their most useful format, I wrote an example that saves time as a timestamp. The scheduler uses timestamps. The databases can store timestamps, but DateTime objects have to be converted to and from strings. And using DateTime forces two applications (the one that saves the events and the one that sends the events) to agree on DateTime format used.

This example writes reminders to a database, retrieves them from the database, schedules the reminders, and sends the reminders. The main change needed to make this work with your code is that your events are entered using a datetime string. You'll need to convert that to a datetime object, and then use the datetime object to get a timestamp.
import sqlite3 as sql
from datetime import datetime
import sched, time

time_format = "%I:%M:%S %p"

def send_reminder(sched_time, message):
    """Just pring the message and the scheduled time (and current time)"""
    print(message)
    print(datetime.now().strftime(time_format))
    print(datetime.fromtimestamp(sched_time).strftime(time_format))

def save_reminders(reminders):
    """Make a table in the database to hold the reminders"""
    now = int(time.time())
    db = sql.connect("test.db")
    db.execute("CREATE TABLE IF NOT EXISTS reminders(message TEXT, time INTEGER)")
    db.execute("DELETE FROM reminders")
    for reminder, seconds in reminders:
        db.execute(
            "INSERT INTO reminders (message, time) VALUES(?, ?)",
            (reminder, now + seconds))
    db.commit()
    db.close()

def schedule_reminders(schedule):
    """Get reminders from the database and add to the schedule."""
    db = sql.connect("test.db")
    for reminder in db.execute("SELECT * FROM reminders"):
        message, sched_time = reminder
        schedule.enterabs(sched_time, 1, send_reminder, (sched_time, message))
    db.close()

# Make a database of reminders that run 5, 10 and 15 seconds from now.
save_reminders((
    ("This is the first message", 5),
    ("This message is 5 seconds after the first", 10),
    ("This message is 15 seconds after the first", 20))
)

schedule = sched.scheduler(time.time)
schedule_reminders(schedule)
while not schedule.empty():
    schedule.run()
Reply
#19
I tried converting the DateTime String from the Reminders Table to a DateTime object by doing this:
connection = sqlite3.connect(MainDatabase)
for row in connection.execute("SELECT DateTime FROM Reminders"):
    scheduled_time = row

    print("Scheduled Time: ",  scheduled_time)

    #Convert scheduled_time from Tuple to List
    from itertools import chain
    DateTimeList = list(chain.from_iterable(scheduled_time))
    #Then Convert the List to a String
    DateTimeStr = "".join(str(x) for x in DateTimeList)

    print("Date Time String:", DateTimeStr)

    format = "%Y-%m-%d %I:%M %p"
    scheduled_time2 = datetime.strptime(DateTimeStr, format)

connection.close()
But I get this error:
Error:
ValueError: time data '2022-10-15 11:25:00' does not match format '%Y-%m-%d %I:%M %p'
Is that not the right format? It's year-month-date hour-minute Am/Pm
What is the proper format for this?
Reply
#20
Edit:

Did this:
    format = "%Y-%m-%d %H:%M:%S"
    scheduled_time2 = datetime.strptime(DateTimeStr, format)
No format error but I get this output:
Output:
Reminders in Database: ('Test 1', '2022-10-15 11:25:00') Reminders in Database: ('Test 2', '2022-10-15 11:28:00') Reminders in Database: ('Test 3', '2022-10-15 11:30:00') Reminders in Database: ('Test 4', '2022-10-15 20:19:00') Scheduled Time: ('2022-10-15 11:25:00',) DateTime String: 2022-10-15 11:25:00 Scheduled Time: ('2022-10-15 11:28:00',) DateTime String: 2022-10-15 11:28:00 Scheduled Time: ('2022-10-15 11:30:00',) DateTime String: 2022-10-15 11:30:00 Scheduled Time: ('2022-10-15 20:19:00',) DateTime String: 2022-10-15 20:19:00 Current Date Time: 2022-10-15 20:20:00 Reminders Popup: ('2022-10-15 20:19:00',): Test 1 Reminders Popup: ('2022-10-15 20:19:00',): Test 3 Reminders Popup: ('2022-10-15 20:19:00',): Test 2 Reminders Popup: ('2022-10-15 20:19:00',): Test 4
Which is weird because the Reminder Popup says all 4 reminders are scheduled for 8:19 but they're not (only Test 4 was)

import sched
import time
import sqlite3
from tokenize import Double
from BX_External_Functions import checkForReminders, reminderPopup
from BX_Constants import MainDatabase, currentdate, currentTime

from datetime import datetime
#Create schedule
scheduler = sched.scheduler(time.time, time.sleep)

#Read in reminders from database
#For each reminder, add an event to the schedule (enter/enterabs)
connection = sqlite3.connect(MainDatabase)
for row in connection.execute("SELECT Reminder, DateTime FROM Reminders"):
    print("Reminders in Database: ", row)
connection.close()

print("\n")

connection = sqlite3.connect(MainDatabase)
for row in connection.execute("SELECT DateTime FROM Reminders"):
    scheduled_time = row

    print("Scheduled Time: ",  scheduled_time)
#----------
    #Convert scheduled_time from Tuple to List
    from itertools import chain
    DateTimeList = list(chain.from_iterable(scheduled_time))
    #Then Convert the List to a String
    DateTimeStr = "".join(str(x) for x in DateTimeList)

    print("DateTime String:", DateTimeStr)

    format = "%Y-%m-%d %H:%M:%S"
    scheduled_time2 = datetime.strptime(DateTimeStr, format)
#----------
connection.close()

import datetime
currentdate = datetime.date.today()
from datetime import datetime
timeNow = datetime.now()
currentTime = timeNow.strftime("%I:%M %p")

Current_DateTime = datetime.strptime(f"{currentdate} {currentTime}", "%Y-%m-%d %I:%M %p")
print("\nCurrent Date Time: ", Current_DateTime ,"\n")

# #If a new reminder is created, add it to the database & add an event to the schedule
# scheduler.enterabs()

# #Inside Baxter's while True loop, call schedule.run(blocking=False)
# print ('Checking for reminders...')
# scheduler.run(False)

def schedule_reminder(reminder):
    """Schedule a reminder"""
    scheduler.enterabs(
        time=scheduled_time2.timestamp(),
        priority=1,  
        action=send_reminder,
        argument=(reminder, scheduled_time))

def send_reminder(Reminder, scheduled_time):
    """Function called when time to send reminder"""
    print(f"Reminders Popup: {scheduled_time}: {Reminder}")


#Read in reminders from database
connection = sqlite3.connect(MainDatabase)
for reminder in connection.execute("SELECT Reminder FROM Reminders"):
    schedule_reminder(*reminder)
connection.close()

# somewhere later in your main code
while True:
    scheduler.run(False)
    # do other stuff
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [SOLVED] Loop through directories and files one level down? Winfried 3 249 Apr-28-2024, 02:31 PM
Last Post: Gribouillis
  Loop through all files in a directory? Winfried 10 491 Apr-23-2024, 07:38 PM
Last Post: FortuneCoins
  for loop not executing Abendrot47 2 260 Apr-09-2024, 07:14 PM
Last Post: deanhystad
  Re Try loop for "net use..." failures tester_V 10 673 Mar-02-2024, 08:15 AM
Last Post: tester_V
  File loop curiously skipping files - FIXED mbk34 10 889 Feb-10-2024, 07:08 AM
Last Post: buran
  Optimise multiply for loop in Python KenBCN 4 504 Feb-06-2024, 06:48 PM
Last Post: Gribouillis
  Basic binary search algorithm - using a while loop Drone4four 1 409 Jan-22-2024, 06:34 PM
Last Post: deanhystad
  loop through csv format from weburl in python maddyad82 3 452 Jan-17-2024, 10:08 PM
Last Post: deanhystad
  Variable definitions inside loop / could be better? gugarciap 2 475 Jan-09-2024, 11:11 PM
Last Post: deanhystad
  UART & I2C slow down a loop trix 4 656 Dec-28-2023, 05:14 PM
Last Post: trix

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020