Update Sun Schedule #86
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Update Sun Schedule" | |
| on: | |
| schedule: | |
| # Run once daily at 02:00 UTC (3 AM Oslo time in winter) | |
| - cron: "0 2 * * *" | |
| workflow_dispatch: # Manual trigger for testing | |
| jobs: | |
| update-schedule: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.PAT_TOKEN }} | |
| - name: Set up Python 3.11 | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install requests PyYAML | |
| - name: Fetch sun times and update schedule | |
| run: | | |
| python3 << 'EOF' | |
| import json | |
| import requests | |
| from datetime import datetime, time | |
| # Load configuration | |
| with open('config.json', 'r') as f: | |
| config = json.load(f) | |
| # Fetch sun times | |
| LAT = config['location']['latitude'] | |
| LNG = config['location']['longitude'] | |
| url = config['api']['sunrise_sunset_url'].format(lat=LAT, lng=LNG) | |
| response = requests.get(url, timeout=15) | |
| response.raise_for_status() | |
| data = response.json() | |
| if data.get("status") != "OK": | |
| raise Exception(f"API returned status: {data.get('status')}") | |
| results = data["results"] | |
| # Parse times (ISO format from API when formatted=0) | |
| def parse_time(time_str): | |
| """Extract hour and minute from ISO datetime string""" | |
| dt = datetime.fromisoformat(time_str.replace('Z', '+00:00')) | |
| return dt.hour, dt.minute | |
| # Get astronomical twilight times (darkest) | |
| twilight_end_h, twilight_end_m = parse_time(results["astronomical_twilight_end"]) | |
| twilight_begin_h, twilight_begin_m = parse_time(results["astronomical_twilight_begin"]) | |
| # Generate cron schedule for dark hours | |
| # Run every 15 minutes during astronomical darkness | |
| # Cron format: minute hour * * * | |
| # If twilight_end is 17:44:59 (5:44 PM), start at 18:00 | |
| # If twilight_begin is 4:18:25 AM, end at 4:00 AM | |
| start_hour = twilight_end_h + 1 if twilight_end_m > 30 else twilight_end_h | |
| end_hour = twilight_begin_h | |
| # Handle case where darkness spans midnight | |
| if start_hour >= end_hour: | |
| # Example: 18:00-23:59 and 00:00-04:00 | |
| cron_evening = f"*/15 {start_hour}-23 * * *" | |
| cron_morning = f"*/15 0-{end_hour} * * *" if end_hour > 0 else None | |
| cron_schedule = [cron_evening] | |
| if cron_morning: | |
| cron_schedule.append(cron_morning) | |
| else: | |
| # Continuous dark period (rare in Lommedalen) | |
| cron_schedule = [f"*/15 {start_hour}-{end_hour} * * *"] | |
| # Save schedule info | |
| schedule_info = { | |
| "updated_at": datetime.utcnow().isoformat() + "Z", | |
| "location": config['location'], | |
| "sun_times": results, | |
| "dark_hours": { | |
| "start_hour": start_hour, | |
| "end_hour": end_hour, | |
| "spans_midnight": start_hour >= end_hour | |
| }, | |
| "cron_schedules": cron_schedule | |
| } | |
| schedule_file = config['data']['sun_schedule_file'] | |
| with open(schedule_file, 'w') as f: | |
| json.dump(schedule_info, f, indent=2) | |
| print(f"✅ Updated sun schedule") | |
| print(f" Astronomical darkness: {start_hour}:00 - {end_hour}:00 UTC") | |
| print(f" Cron schedules: {', '.join(cron_schedule)}") | |
| EOF | |
| - name: Update workflow schedule | |
| run: | | |
| python3 << 'EOF' | |
| import json | |
| import re | |
| # Load config | |
| with open('config.json', 'r') as f: | |
| config = json.load(f) | |
| # Load schedule info | |
| schedule_file = config['data']['sun_schedule_file'] | |
| with open(schedule_file, 'r') as f: | |
| schedule_info = json.load(f) | |
| cron_schedules = schedule_info['cron_schedules'] | |
| # Read the workflow file | |
| with open('.github/workflows/post.yml', 'r') as f: | |
| content = f.read() | |
| # Find and replace the schedule section using regex | |
| # This preserves comments and formatting | |
| schedule_pattern = r'(on:\s*\n\s+schedule:\s*\n(?:\s+#[^\n]*\n)*)((?:\s+-\s+cron:\s+[^\n]+\n)+)' | |
| # Build new schedule lines | |
| new_schedule_lines = [] | |
| for cron in cron_schedules: | |
| new_schedule_lines.append(f' - cron: "{cron}"\n') | |
| new_schedule_section = ''.join(new_schedule_lines) | |
| # Replace the schedule section | |
| def replacer(match): | |
| return match.group(1) + new_schedule_section | |
| updated_content = re.sub(schedule_pattern, replacer, content) | |
| # Write back | |
| with open('.github/workflows/post.yml', 'w') as f: | |
| f.write(updated_content) | |
| print(f"✅ Updated post.yml with {len(cron_schedules)} cron schedule(s)") | |
| print(f" Schedules: {', '.join(cron_schedules)}") | |
| EOF | |
| - name: Commit and push changes | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Get schedule file from config | |
| SCHEDULE_FILE=$(jq -r '.data.sun_schedule_file' config.json) | |
| git add "$SCHEDULE_FILE" .github/workflows/post.yml | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit" | |
| else | |
| START_HOUR=$(jq -r '.dark_hours.start_hour' "$SCHEDULE_FILE") | |
| END_HOUR=$(jq -r '.dark_hours.end_hour' "$SCHEDULE_FILE") | |
| UPDATED_AT=$(jq -r '.updated_at' "$SCHEDULE_FILE") | |
| CRON_SCHEDULES=$(jq -r '.cron_schedules | join(", ")' "$SCHEDULE_FILE") | |
| git commit -m "chore: update sun schedule and workflow timing [automated]" \ | |
| -m "Dark hours: ${START_HOUR}:00 - ${END_HOUR}:00 UTC" \ | |
| -m "Cron: ${CRON_SCHEDULES}" \ | |
| -m "Updated: ${UPDATED_AT}" | |
| git push | |
| fi |