Skip to content

Commit c26f3ef

Browse files
authored
Merge pull request #488 from Hacking-the-Cloud/nick.frichette/add_gh_action_scheduler
Added GitHub actions merge scheduler
2 parents 155f745 + f69280c commit c26f3ef

3 files changed

Lines changed: 469 additions & 0 deletions

File tree

.github/scripts/parse_schedule.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import re
4+
from datetime import datetime, timedelta, timezone
5+
from zoneinfo import ZoneInfo
6+
7+
SCHEDULE_TIMEZONE = "America/Chicago"
8+
MAX_SCHEDULE_DAYS = 30
9+
10+
11+
def write_output(key: str, value: str) -> None:
12+
path = os.environ.get("GITHUB_OUTPUT")
13+
if not path:
14+
return
15+
with open(path, "a", encoding="utf-8") as handle:
16+
handle.write(f"{key}={value}\n")
17+
18+
19+
def fail(message: str) -> None:
20+
write_output("valid", "false")
21+
write_output("error", message)
22+
23+
24+
raw = os.environ.get("RAW", "").strip()
25+
if not raw:
26+
fail("Missing schedule time.")
27+
raise SystemExit(0)
28+
29+
normalized = re.sub(r"\s+", " ", raw).strip()
30+
match = re.match(
31+
r"^(?P<date>\d{4}-\d{2}-\d{2})\s+(?P<time>\d{1,2}(:\d{2})?\s*(am|pm)?)\s*(?P<tz>ct|cst|cdt)?$",
32+
normalized,
33+
re.IGNORECASE,
34+
)
35+
36+
if not match:
37+
fail(
38+
"Invalid time format. Use `/schedule-merge YYYY-MM-DD HH:MM CT` or `/schedule-merge YYYY-MM-DD 9am CT`."
39+
)
40+
raise SystemExit(0)
41+
42+
date_part = match.group("date")
43+
time_part = match.group("time").strip().lower()
44+
45+
hour = None
46+
minute = 0
47+
48+
ampm_match = re.match(r"^(?P<h>\d{1,2})(:(?P<m>\d{2}))?(?P<ampm>am|pm)$", time_part.replace(" ", ""))
49+
if ampm_match:
50+
hour = int(ampm_match.group("h"))
51+
minute = int(ampm_match.group("m") or 0)
52+
ampm = ampm_match.group("ampm")
53+
if hour < 1 or hour > 12 or minute > 59:
54+
fail("Invalid time value.")
55+
raise SystemExit(0)
56+
if ampm == "pm" and hour != 12:
57+
hour += 12
58+
if ampm == "am" and hour == 12:
59+
hour = 0
60+
else:
61+
hm_match = re.match(r"^(?P<h>\d{1,2})(:(?P<m>\d{2}))?$", time_part)
62+
if not hm_match:
63+
fail("Invalid time value.")
64+
raise SystemExit(0)
65+
hour = int(hm_match.group("h"))
66+
minute = int(hm_match.group("m") or 0)
67+
if hour > 23 or minute > 59:
68+
fail("Invalid time value.")
69+
raise SystemExit(0)
70+
71+
year, month, day = [int(part) for part in date_part.split("-")]
72+
local_tz = ZoneInfo(SCHEDULE_TIMEZONE)
73+
try:
74+
local_dt = datetime(year, month, day, hour, minute, tzinfo=local_tz)
75+
except ValueError:
76+
fail("Invalid calendar date.")
77+
raise SystemExit(0)
78+
utc_dt = local_dt.astimezone(timezone.utc)
79+
80+
now_utc = datetime.now(timezone.utc)
81+
if utc_dt <= now_utc:
82+
fail("Scheduled time must be in the future.")
83+
raise SystemExit(0)
84+
85+
max_future = now_utc + timedelta(days=MAX_SCHEDULE_DAYS)
86+
if utc_dt > max_future:
87+
fail(f"Scheduled time must be within {MAX_SCHEDULE_DAYS} days from now.")
88+
raise SystemExit(0)
89+
90+
utc_iso = utc_dt.replace(microsecond=0).isoformat().replace("+00:00", "Z")
91+
local_iso = local_dt.replace(microsecond=0).isoformat()
92+
93+
write_output("valid", "true")
94+
write_output("utc", utc_iso)
95+
write_output("local", local_iso)
96+
write_output("display", local_dt.strftime("%Y-%m-%d %I:%M %p CT"))

0 commit comments

Comments
 (0)