From 15f2d8dcb8f30da7e4980e331c2912e4451f012f Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Wed, 17 Sep 2025 21:19:01 -0500 Subject: [PATCH 01/17] =?UTF-8?q?Age=20Calculator:=20fix=20leap-year=20fun?= =?UTF-8?q?ction=20and=20add=20time=20summary=20(days=E2=86=92hours/minute?= =?UTF-8?q?s/seconds)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Age Calculator/calculate.py | 45 ++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/Age Calculator/calculate.py b/Age Calculator/calculate.py index 83134f0..a5756bb 100644 --- a/Age Calculator/calculate.py +++ b/Age Calculator/calculate.py @@ -1,25 +1,25 @@ import time from calendar import isleap -def judge_leap_year(year): - if isleap(year): - return True - else: - return False +# Check if a year is a leap year +def judge_leap(year: int) -> bool: + return isleap(year) -def month_days(month, leap_year): +# Return number of days in a month, considering leap years +def month_days(month: int, leap_year: bool) -> int: if month in [1, 3, 5, 7, 8, 10, 12]: return 31 elif month in [4, 6, 9, 11]: return 30 elif month == 2 and leap_year: return 29 - elif month == 2 and (not leap_year): + else: return 28 - +# User input name = input("Please enter your name: ") -age = input("Please enter your age: ") +age = int(input("Please enter your age: ")) + localtime = time.localtime(time.time()) year = int(age) @@ -29,16 +29,25 @@ def month_days(month, leap_year): begin_year = int(localtime.tm_year) - year end_year = begin_year + year +# Count days in past years for y in range(begin_year, end_year): - if (judge_leap_year(y)): - day = day + 366 + if judge_leap(y): + day += 366 else: - day = day + 365 + day += 365 -leap_year = judge_leap_year(localtime.tm_year) +# Add days from current year +leap_year = judge_leap(localtime.tm_year) for m in range(1, localtime.tm_mon): - day = day + month_days(m, leap_year) - -day = day + localtime.tm_mday -print("\n\t%s's age is %d years or " % (name, year), end="") -print("%d months or %d days" % (month, day)) + day += month_days(m, leap_year) + +# Approximate breakdown (ignores time of day) +hours = day * 24 +minutes = hours * 60 +seconds = minutes * 60 + +print(f"\nHello {name}, you are approximately:") +print(f" {day:,} days") +print(f" {hours:,} hours") +print(f" {minutes:,} minutes") +print(f" {seconds:,} seconds old!") From db19bc8923ed1456bd946059e07fb515419e77f8 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Thu, 18 Sep 2025 08:33:51 -0500 Subject: [PATCH 02/17] Digital Clock: add separate date label below time --- Digital Clock/main.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Digital Clock/main.py b/Digital Clock/main.py index 831562b..d65d7c5 100644 --- a/Digital Clock/main.py +++ b/Digital Clock/main.py @@ -12,6 +12,13 @@ ) clock_label.place(x=50, y=50) +# NEW date label +date_label = Label( + window, bg="black", fg="white", font=("Arial", 14) +) +date_label.pack(pady=(0, 10), anchor="center") + + def update_label(): current_time = strftime("%H: %M: %S\n %d-%m-%Y ") @@ -20,4 +27,8 @@ def update_label(): clock_label.pack(anchor="center") update_label() -window.mainloop() \ No newline at end of file +window.mainloop()def update_label(): + clock_label.configure(text=strftime("%H:%M:%S")) + date_label.configure(text=strftime("%A, %b %d, %Y")) + clock_label.after(1000, update_label) + From 0173fc92fb118a40a50d037f1c60e729b57e78b8 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Thu, 18 Sep 2025 08:41:16 -0500 Subject: [PATCH 03/17] Digital Clock: add 12/24-hour toggle button and 'f' shortcut --- Digital Clock/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Digital Clock/main.py b/Digital Clock/main.py index d65d7c5..3266afa 100644 --- a/Digital Clock/main.py +++ b/Digital Clock/main.py @@ -1,5 +1,5 @@ from time import strftime -from tkinter import Label, Tk +from tkinter import Label, Tk , Button window = Tk() window.title("Digital Clock") @@ -27,7 +27,7 @@ def update_label(): clock_label.pack(anchor="center") update_label() -window.mainloop()def update_label(): +window.mainloop() def update_label(): clock_label.configure(text=strftime("%H:%M:%S")) date_label.configure(text=strftime("%A, %b %d, %Y")) clock_label.after(1000, update_label) From 026b56e1b9e2de9cd2234f49e7ce584e8d03c5a8 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Thu, 18 Sep 2025 08:53:14 -0500 Subject: [PATCH 04/17] Digital Clock: consolidate update loop and refresh every second --- Digital Clock/main.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Digital Clock/main.py b/Digital Clock/main.py index 3266afa..632af37 100644 --- a/Digital Clock/main.py +++ b/Digital Clock/main.py @@ -7,12 +7,14 @@ window.configure(bg="green") window.resizable(False, False) +use_24h = True + clock_label = Label( window, bg="black", fg="green", font=("Arial", 30, "bold"), relief="flat" ) clock_label.place(x=50, y=50) -# NEW date label + date_label = Label( window, bg="black", fg="white", font=("Arial", 14) ) @@ -20,15 +22,29 @@ +def toggle_format(_evt=None): + global use_24h + use_24h = not use_24h + fmt_btn.config(text="Switch to 24-hour" if not use_24h else "Switch to 12-hour") + +fmt_btn = Button(window, text="Switch to 12-hour", command=toggle_format) +fmt_btn.pack(pady=(0, 8)) +window.bind("", toggle_format) # press 'f' to toggle + + + def update_label(): - current_time = strftime("%H: %M: %S\n %d-%m-%Y ") - clock_label.configure(text=current_time) - clock_label.after(80, update_label) - clock_label.pack(anchor="center") + + if use_24h: + time_text = strftime("%H:%M:%S") + else: + # strip leading zero in 12h mode for a cleaner look + time_text = strftime("%I:%M:%S %p").lstrip("0") + clock_label.configure(text=time_text) + date_label.configure(text=strftime("%A, %b %d, %Y")) + window.after(1000, update_label) update_label() -window.mainloop() def update_label(): - clock_label.configure(text=strftime("%H:%M:%S")) - date_label.configure(text=strftime("%A, %b %d, %Y")) - clock_label.after(1000, update_label) +window.mainloop() + From 0a32153debe0040f48bdcca55641f95384a461f7 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Thu, 18 Sep 2025 21:03:18 -0500 Subject: [PATCH 05/17] Geographic Distance: add validate_coordinates() helper and use it in main() --- Geographic Distance/geographic_distance.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Geographic Distance/geographic_distance.py b/Geographic Distance/geographic_distance.py index bf6a0e2..bf5208d 100644 --- a/Geographic Distance/geographic_distance.py +++ b/Geographic Distance/geographic_distance.py @@ -17,6 +17,18 @@ def calculate_distance_and_time(coord1, coord2, avg_speed): return distance, travel_time + def validate_coordinates(coord): + """Ensure latitude and longitude are within valid ranges.""" + lat, lon = coord + if not (-90 <= lat <= 90): + raise ValueError(f"Latitude {lat} out of range (-90..90)") + if not (-180 <= lon <= 180): + raise ValueError(f"Longitude {lon} out of range (-180..180)") + return coord + + + + def main(): # Coordinates (latitude, longitude) From 392a9e6041d8fe4f260fc2c02df8e7bec98e99b5 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Thu, 18 Sep 2025 21:06:12 -0500 Subject: [PATCH 06/17] Geographic Distance: add km_to_miles() and show distance in km and miles --- Geographic Distance/geographic_distance.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Geographic Distance/geographic_distance.py b/Geographic Distance/geographic_distance.py index bf5208d..1dff49c 100644 --- a/Geographic Distance/geographic_distance.py +++ b/Geographic Distance/geographic_distance.py @@ -26,6 +26,10 @@ def validate_coordinates(coord): raise ValueError(f"Longitude {lon} out of range (-180..180)") return coord + def km_to_miles(km: float) -> float: + """Convert kilometers to miles.""" + return km * 0.621371 + From 672129d25e352975e0783874d71461d0e6a92e11 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Thu, 18 Sep 2025 21:07:48 -0500 Subject: [PATCH 07/17] Geographic Distance: format travel time as hours and minutes --- Geographic Distance/geographic_distance.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Geographic Distance/geographic_distance.py b/Geographic Distance/geographic_distance.py index 1dff49c..c91889e 100644 --- a/Geographic Distance/geographic_distance.py +++ b/Geographic Distance/geographic_distance.py @@ -30,6 +30,16 @@ def km_to_miles(km: float) -> float: """Convert kilometers to miles.""" return km * 0.621371 + def format_travel_time(hours: float) -> str: + """Format fractional hours as 'Hh Mm'.""" + h = int(hours) + m = int(round((hours - h) * 60)) + if m == 60: + h += 1 + m = 0 + return f"{h}h {m}m" + + From d4f80b85bc5c02c359e41609b1b48d782122166f Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Fri, 19 Sep 2025 17:57:01 -0500 Subject: [PATCH 08/17] Resume Builder: add interactive internships collector --- Resume Builder/resume_builder.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Resume Builder/resume_builder.py b/Resume Builder/resume_builder.py index 8d2211e..af27dfa 100644 --- a/Resume Builder/resume_builder.py +++ b/Resume Builder/resume_builder.py @@ -16,6 +16,8 @@ def clear_screen(): "projects": [], "certifications": [], "achievements": [] + "internships":[] + } # Function to navigate back to the main menu @@ -54,6 +56,27 @@ def add_experience(): break back_to_menu() + # Add internships <-- NEW +def add_internships(): + while True: + clear_screen() + print("Enter Internship Information") + internship = { + "role": prompt("Role/Title: "), + "company": prompt("Company: "), + "location": prompt("Location (optional): "), + "start_date": prompt("Start Date (e.g., Jun 2024): "), + "end_date": prompt("End Date (e.g., Aug 2024 or 'Present'): "), + "details": [s.strip() for s in prompt("Highlights (comma-separated): ").split(",") if s.strip()] + } + resume_data["internships"].append(internship) + + more = prompt("Add another internship? (yes/no): ").strip().lower() + if more == "no": + break + back_to_menu() + + # Add education details def add_education(): while True: From 56dff0e993f4d4d2be7688f6caec86b49bce4bac Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Fri, 19 Sep 2025 18:05:22 -0500 Subject: [PATCH 09/17] Resume Builder: include internships section in generated PDF --- Resume Builder/resume_builder.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Resume Builder/resume_builder.py b/Resume Builder/resume_builder.py index af27dfa..6110535 100644 --- a/Resume Builder/resume_builder.py +++ b/Resume Builder/resume_builder.py @@ -232,6 +232,20 @@ def generate_pdf(): os.system(f"start {pdf_output_path}" if os.name == "nt" else f"open {pdf_output_path}") print(f"Resume generated: {pdf_output_path}") + # Internships + if resume_data["internships"]: + pdf.set_font('Arial', 'B', 12) + pdf.cell(0, 10, "Internships", 0, 1) + pdf.set_font('Arial', '', 11) + for it in resume_data["internships"]: + hdr = f"{it['role']} at {it['company']}" + if it.get("location"): + hdr += f" — {it['location']}" + pdf.cell(0, 10, f"{hdr} ({it['start_date']} - {it['end_date']})", 0, 1) + if it.get("details"): + pdf.multi_cell(0, 10, "Highlights: " + ", ".join(it["details"])) + + # Main Menu using button_dialog from prompt_toolkit def interactive_menu(): while True: From 8dc866742bb80a9c9eda7497dd955c0778bcf0ee Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Fri, 19 Sep 2025 18:11:01 -0500 Subject: [PATCH 10/17] Resume Builder: add Internships menu option and handler --- Resume Builder/resume_builder.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Resume Builder/resume_builder.py b/Resume Builder/resume_builder.py index 6110535..72fc783 100644 --- a/Resume Builder/resume_builder.py +++ b/Resume Builder/resume_builder.py @@ -259,10 +259,11 @@ def interactive_menu(): ("Education", 3), ("Skills", 4), ("Projects", 5), - ("Certifications", 6), - ("Achievements", 7), - ("Generate PDF", 8), - ("Exit", 9) + ("Internships", 6), + ("Certifications", 7), + ("Achievements", 8), + ("Generate PDF", 9), + ("Exit", 10) ] ).run() @@ -276,13 +277,15 @@ def interactive_menu(): add_skills() elif choice == 5: add_projects() - elif choice == 6: - add_certifications() + elif choice == 6: + add_internships() elif choice == 7: - add_achievements() + add_certifications() elif choice == 8: - generate_pdf() + add_achievements() elif choice == 9: + generate_pdf() + elif choice == 10: break # Start the program From 2b0e9788a50b5248da719fa7e7119a484bc6cf0d Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Fri, 19 Sep 2025 18:17:27 -0500 Subject: [PATCH 11/17] Resume Builder: add internships list to resume_data --- Resume Builder/resume_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resume Builder/resume_builder.py b/Resume Builder/resume_builder.py index 72fc783..379fe39 100644 --- a/Resume Builder/resume_builder.py +++ b/Resume Builder/resume_builder.py @@ -15,7 +15,7 @@ def clear_screen(): "skills": [], "projects": [], "certifications": [], - "achievements": [] + "achievements": [], "internships":[] } @@ -277,7 +277,7 @@ def interactive_menu(): add_skills() elif choice == 5: add_projects() - elif choice == 6: + elif choice == 6: add_internships() elif choice == 7: add_certifications() From d43108cbf32a6a758cb2dba0805f43d2aa66e1ff Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Fri, 19 Sep 2025 18:39:44 -0500 Subject: [PATCH 12/17] Resume Builder: add interactive internships collector --- Resume Builder/resume_builder.py | 43 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Resume Builder/resume_builder.py b/Resume Builder/resume_builder.py index 379fe39..9e699c3 100644 --- a/Resume Builder/resume_builder.py +++ b/Resume Builder/resume_builder.py @@ -56,25 +56,7 @@ def add_experience(): break back_to_menu() - # Add internships <-- NEW -def add_internships(): - while True: - clear_screen() - print("Enter Internship Information") - internship = { - "role": prompt("Role/Title: "), - "company": prompt("Company: "), - "location": prompt("Location (optional): "), - "start_date": prompt("Start Date (e.g., Jun 2024): "), - "end_date": prompt("End Date (e.g., Aug 2024 or 'Present'): "), - "details": [s.strip() for s in prompt("Highlights (comma-separated): ").split(",") if s.strip()] - } - resume_data["internships"].append(internship) - - more = prompt("Add another internship? (yes/no): ").strip().lower() - if more == "no": - break - back_to_menu() + # Add education details @@ -150,6 +132,29 @@ def add_achievements(): if more == "no": break back_to_menu() + + # Add internships +def add_internships(): + while True: + clear_screen() + print("Enter Internship Information") + internship = { + "role": prompt("Role/Title: "), + "company": prompt("Company: "), + "location": prompt("Location (optional): "), + "start_date": prompt("Start Date (e.g., Jun 2024): "), + "end_date": prompt("End Date (e.g., Aug 2024 or 'Present'): "), + "details": [s.strip() for s in prompt("Highlights (comma-separated): ").split(",") if s.strip()] + } + resume_data["internships"].append(internship) + + more = prompt("Add another internship? (yes/no): ").strip().lower() + if more == "no": + break + back_to_menu() + + + # PDF Generation class class ResumePDF(FPDF): From fd67603e842254b538d145603d3fc169923071eb Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Fri, 19 Sep 2025 18:47:48 -0500 Subject: [PATCH 13/17] Resume Builder: include interships section in generated PDF --- Resume Builder/resume_builder.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Resume Builder/resume_builder.py b/Resume Builder/resume_builder.py index 9e699c3..8ee0060 100644 --- a/Resume Builder/resume_builder.py +++ b/Resume Builder/resume_builder.py @@ -214,7 +214,21 @@ def generate_pdf(): pdf.cell(0, 10, proj["name"], 0, 1) pdf.multi_cell(0, 10, proj["description"]) pdf.cell(0, 10, f"Technologies Used: {proj['technologies']}", 0, 1) + # Internships + if resume_data["internships"]: + pdf.set_font('Arial', 'B', 12) + pdf.cell(0, 10, "Internships", 0, 1) + pdf.set_font('Arial', '', 11) + for it in resume_data["internships"]: + hdr = f"{it['role']} at {it['company']}" + if it.get("location"): + hdr += f" — {it['location']}" + pdf.cell(0, 10, f"{hdr} ({it['start_date']} - {it['end_date']})", 0, 1) + if it.get("details"): + pdf.multi_cell(0, 10, "Highlights: " + ", ".join(it["details"])) + + # Certifications pdf.set_font('Arial', 'B', 12) pdf.cell(0, 10, "Certifications", 0, 1) @@ -237,19 +251,7 @@ def generate_pdf(): os.system(f"start {pdf_output_path}" if os.name == "nt" else f"open {pdf_output_path}") print(f"Resume generated: {pdf_output_path}") - # Internships - if resume_data["internships"]: - pdf.set_font('Arial', 'B', 12) - pdf.cell(0, 10, "Internships", 0, 1) - pdf.set_font('Arial', '', 11) - for it in resume_data["internships"]: - hdr = f"{it['role']} at {it['company']}" - if it.get("location"): - hdr += f" — {it['location']}" - pdf.cell(0, 10, f"{hdr} ({it['start_date']} - {it['end_date']})", 0, 1) - if it.get("details"): - pdf.multi_cell(0, 10, "Highlights: " + ", ".join(it["details"])) - + # Main Menu using button_dialog from prompt_toolkit def interactive_menu(): From 06a24bf7693ae6475bb55c4dd285779e5633a75b Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Sat, 20 Sep 2025 15:40:11 -0500 Subject: [PATCH 14/17] Countdown Timer: display time as mm:ss and initialize lavel to 00.00 --- CountDown Timer/main.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/CountDown Timer/main.py b/CountDown Timer/main.py index 83d1485..d760af8 100644 --- a/CountDown Timer/main.py +++ b/CountDown Timer/main.py @@ -1,6 +1,12 @@ from tkinter import * import tkinter as tk +def fmt_time(seconds: int) -> str: + seconds = max(0, int(seconds)) + m, s = divmod(seconds, 60) + return f"{m:02d}:{s:02d}" + + class Application(Frame): def __init__(self,master): super(Application,self).__init__(master) @@ -39,7 +45,9 @@ def createWidgets(self): self.someFrame.pack(side=TOP) self.labelvariable = StringVar() - self.labelvariable.set("") + self.labelvariable.set(fmt_time(0)) + + self.thelabel = Label(self,textvariable = self.labelvariable,font=('Helvetica',50)) self.thelabel.pack(side=TOP) @@ -80,14 +88,14 @@ def closeApp(self): def countdown(self, timeInSeconds, start=True): if timeInSeconds == 0: self._starttime=0 - self.labelvariable.set("0") + self.labelvariable.set(fmt_time(0)) return if start: self._starttime = timeInSeconds if self._paused: self._alarm_id = self.master.after(1000, self.countdown, timeInSeconds, False) else: - app.labelvariable.set(timeInSeconds) + app.labelvariable.set(fmt_time(timeInSeconds)) self._alarm_id = self.master.after(1000, self.countdown, timeInSeconds-1, False) From 592db0614617b1fd04eeebcd3cd294a619c2c162 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Sat, 20 Sep 2025 15:48:49 -0500 Subject: [PATCH 15/17] CountDown Timer: play alarm when coutdown reaches 0 --- CountDown Timer/main.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/CountDown Timer/main.py b/CountDown Timer/main.py index d760af8..46d3fe7 100644 --- a/CountDown Timer/main.py +++ b/CountDown Timer/main.py @@ -5,6 +5,24 @@ def fmt_time(seconds: int) -> str: seconds = max(0, int(seconds)) m, s = divmod(seconds, 60) return f"{m:02d}:{s:02d}" + + try: + import winsound # Windows +except Exception: + winsound = None + +def play_alarm(): + try: + if winsound: + winsound.Beep(880, 300) + winsound.Beep(988, 300) + winsound.Beep(1047, 300) + else: + # macOS/Linux fallback: terminal bell + print("\a", end="", flush=True) + except Exception: + pass + class Application(Frame): @@ -89,7 +107,8 @@ def countdown(self, timeInSeconds, start=True): if timeInSeconds == 0: self._starttime=0 self.labelvariable.set(fmt_time(0)) - return + play_alarm() + return if start: self._starttime = timeInSeconds if self._paused: From b6a6460dfa2e2e6a9c8b95709d8c122f621f4743 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Sat, 20 Sep 2025 17:25:22 -0500 Subject: [PATCH 16/17] Pomodoro: add CLI timer with countdown and pause/stop controls --- Pomodoro Timer/main.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Pomodoro Timer/main.py b/Pomodoro Timer/main.py index d882414..02c71ab 100644 --- a/Pomodoro Timer/main.py +++ b/Pomodoro Timer/main.py @@ -1,18 +1,17 @@ import time -def countdown(minutes, label): +def countdown(minutes: int, label: str) -> None: + """Print a mm:ss countdown once per second for the given label.""" total_seconds = minutes * 60 - while total_seconds: + while total_seconds >= 0: mins, secs = divmod(total_seconds, 60) - timer = f"{mins:02d}:{secs:02d}" - print(f"{label} Timer: {timer}", end="\r") + print(f"{label} Timer: {mins:02d}:{secs:02d}", end="\r") time.sleep(1) total_seconds -= 1 print(f"\n{label} finished!") - -def handle_pause_stop(): +def handle_pause_stop() -> bool: while True: user_input = input( "\nPress 'p' to pause, 's' to stop, or 'Enter' to continue: " @@ -69,4 +68,8 @@ def get_valid_input(prompt): long_break_minutes = get_valid_input("Enter long break interval in minutes: ") cycles = get_valid_input("Enter the number of cycles: ") - pomodoro_timer(work_minutes, short_break_minutes, long_break_minutes, cycles) + while True: + pomodoro_timer(work_minutes, short_break_minutes, long_break_minutes, cycles) + # If user chose to repeat at the end, loop continues; otherwise we break here. + if not repeat_or_end(): + break From aa00fc000307ff29ab11890de822965ed061d468 Mon Sep 17 00:00:00 2001 From: Eric G Butler Jr Date: Sun, 21 Sep 2025 10:12:25 -0500 Subject: [PATCH 17/17] Movie Scraper: improve resilience, add error handling, CLI, and CSV export --- Pomodoro Timer/pomodoro.py | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Pomodoro Timer/pomodoro.py diff --git a/Pomodoro Timer/pomodoro.py b/Pomodoro Timer/pomodoro.py new file mode 100644 index 0000000..02c71ab --- /dev/null +++ b/Pomodoro Timer/pomodoro.py @@ -0,0 +1,75 @@ +import time + + +def countdown(minutes: int, label: str) -> None: + """Print a mm:ss countdown once per second for the given label.""" + total_seconds = minutes * 60 + while total_seconds >= 0: + mins, secs = divmod(total_seconds, 60) + print(f"{label} Timer: {mins:02d}:{secs:02d}", end="\r") + time.sleep(1) + total_seconds -= 1 + print(f"\n{label} finished!") + +def handle_pause_stop() -> bool: + while True: + user_input = input( + "\nPress 'p' to pause, 's' to stop, or 'Enter' to continue: " + ).lower() + if user_input == "p": + print("Timer paused. Press 'Enter' to resume.") + input() + elif user_input == "s": + print("Timer stopped.") + return True # Return True to signal that the timer should stop + else: + return False # Return False to continue with the timer + + +def pomodoro_timer(work_min, short_break_min, long_break_min, cycles): + for i in range(cycles): + print(f"\nCycle {i+1} of {cycles}") + countdown(work_min, "Work") + if i < cycles - 1: + print("\nStarting short break...") + if handle_pause_stop(): + return + countdown(short_break_min, "Short Break") + else: + print("\nStarting long break...") + if handle_pause_stop(): + return + countdown(long_break_min, "Long Break") + if not repeat_or_end(): + return + + +def repeat_or_end(): + user_input = input( + "\nCycle finished. Would you like to repeat the cycle? (y/n): " + ).lower() + return user_input == "y" + + +def get_valid_input(prompt): + while True: + try: + value = int(input(prompt)) + if value <= 0: + raise ValueError + return value + except ValueError: + print("Invalid input. Please enter a positive integer.") + + +if __name__ == "__main__": + work_minutes = get_valid_input("Enter work interval in minutes: ") + short_break_minutes = get_valid_input("Enter short break interval in minutes: ") + long_break_minutes = get_valid_input("Enter long break interval in minutes: ") + cycles = get_valid_input("Enter the number of cycles: ") + + while True: + pomodoro_timer(work_minutes, short_break_minutes, long_break_minutes, cycles) + # If user chose to repeat at the end, loop continues; otherwise we break here. + if not repeat_or_end(): + break