diff --git a/.github/scripts/cron-calls-community.sh b/.github/scripts/cron-calls-community.sh index 311bd4a67..0f89a8596 100644 --- a/.github/scripts/cron-calls-community.sh +++ b/.github/scripts/cron-calls-community.sh @@ -6,6 +6,12 @@ DRY_RUN="${DRY_RUN:-true}" ANCHOR_DATE="2025-11-12" MEETING_LINK="https://zoom-lfx.platform.linuxfoundation.org/meeting/92041330205?password=2f345bee-0c14-4dd5-9883-06fbc9c60581" CALENDAR_LINK="https://zoom-lfx.platform.linuxfoundation.org/meetings/hiero?view=week" +MEETING_HOUR="${MEETING_HOUR:-14}" # 14:00 UTC — change this to adjust the meeting time + +if ! [[ "$MEETING_HOUR" =~ ^[0-9]+$ ]] || [ "$MEETING_HOUR" -lt 0 ] || [ "$MEETING_HOUR" -gt 23 ]; then + echo "ERROR: MEETING_HOUR must be an integer between 0 and 23, got '$MEETING_HOUR'." + exit 1 +fi CANCELLED_DATES=( "2025-12-24" @@ -63,15 +69,25 @@ if [ -z "$ISSUE_DATA" ] || [ "$ISSUE_DATA" = "[]" ]; then exit 0 fi +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TIME_UNTIL_MEETING=$(python3 "$SCRIPT_DIR/utils/compute-time-until-meeting.py" "$TODAY" "$MEETING_HOUR") + +if [ "$TIME_UNTIL_MEETING" = "has already started" ]; then + echo "Meeting has already started. Skipping reminder." + exit 0 +fi + +TIME_PHRASE="will begin in approximately $TIME_UNTIL_MEETING" + COMMENT_BODY=$(cat < [MOCK_TIME] + +Arguments: + MEETING_DATE The date of the meeting in YYYY-MM-DD format, or 'today' + MEETING_HOUR Meeting hour in UTC (0-23) + MOCK_TIME Optional mock time in HH:MM or HH:MM:SS format (for testing) + +Output: + A human-readable string like "3 hours and 7 minutes" printed to stdout. +""" + +from __future__ import annotations + +import sys +from datetime import datetime, timezone +from math import ceil + + +def _parse_mock_time(mock_time): + """Parse a mock time string into (hour, minute, second) components.""" + parts = mock_time.split(":") + if len(parts) not in (2, 3): + raise ValueError("MOCK_TIME must be in HH:MM or HH:MM:SS format.") + try: + hour = int(parts[0]) + minute = int(parts[1]) + second = int(parts[2]) if len(parts) == 3 else 0 + except ValueError as exc: + raise ValueError("MOCK_TIME must contain only numeric components.") from exc + + if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59): + raise ValueError("MOCK_TIME values must be within valid UTC ranges.") + return hour, minute, second + + +def _pluralize(value, singular): + """Return singular or plural form based on value.""" + return singular if value == 1 else f"{singular}s" + + +def _format_duration(hours, minutes): + """Format hours and minutes into a human-readable string.""" + if hours > 0 and minutes > 0: + return f"{hours} {_pluralize(hours, 'hour')} and {minutes} {_pluralize(minutes, 'minute')}" + if hours > 0: + return f"{hours} {_pluralize(hours, 'hour')}" + return f"{minutes} {_pluralize(minutes, 'minute')}" + + +def compute_time_until_meeting(meeting_date_str, meeting_hour, mock_time=None): + """Compute the human-readable time remaining until the meeting. + + Args: + meeting_date_str: The meeting date as "YYYY-MM-DD" or "today". + meeting_hour: The meeting hour in UTC (0-23). + mock_time: Optional mock time string in "HH:MM" or "HH:MM:SS" format. + + Returns: + A string like "3 hours and 7 minutes". + """ + now = datetime.now(timezone.utc) + + if mock_time is not None: + hour, minute, second = _parse_mock_time(mock_time) + now = now.replace(hour=hour, minute=minute, second=second, microsecond=0) + + if meeting_date_str.lower() == "today": + meeting_date = now.date() + else: + try: + meeting_date = datetime.strptime(meeting_date_str, "%Y-%m-%d").date() + except ValueError as exc: + raise ValueError("MEETING_DATE must be in YYYY-MM-DD format or 'today'.") from exc + + meeting = datetime( + year=meeting_date.year, + month=meeting_date.month, + day=meeting_date.day, + hour=meeting_hour, + minute=0, + second=0, + microsecond=0, + tzinfo=timezone.utc, + ) + + diff = meeting - now + if diff.total_seconds() <= 0: + return "has already started" + + total_minutes = max(ceil(diff.total_seconds() / 60), 0) + + return _format_duration(total_minutes // 60, total_minutes % 60) + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Usage: python3 compute-time-until-meeting.py [MOCK_TIME]", file=sys.stderr) + sys.exit(1) + + try: + meeting_date_str = sys.argv[1] + meeting_hour = int(sys.argv[2]) + if not 0 <= meeting_hour <= 23: + raise ValueError("MEETING_HOUR must be an integer between 0 and 23.") + mock_time = sys.argv[3] if len(sys.argv) > 3 else None + print(compute_time_until_meeting(meeting_date_str, meeting_hour, mock_time)) + except ValueError as exc: + print(f"ERROR: {exc}", file=sys.stderr) + sys.exit(1)