#!/usr/bin/env python3 """ dates - Important dates and anniversary tracker Never forget birthdays, anniversaries, or important dates. """ import json import sys from datetime import datetime, timedelta from pathlib import Path WORKSPACE = Path("/home/wdjones/.openclaw/workspace") DATES_FILE = WORKSPACE / "data" / "dates.json" def load_dates() -> list: """Load important dates.""" DATES_FILE.parent.mkdir(parents=True, exist_ok=True) if DATES_FILE.exists(): with open(DATES_FILE) as f: return json.load(f) return [] def save_dates(dates: list): """Save dates.""" with open(DATES_FILE, 'w') as f: json.dump(dates, f, indent=2) def add_date(name: str, date: str, recurring: bool = True, category: str = 'general'): """Add an important date.""" dates = load_dates() # Parse date (supports MM-DD or YYYY-MM-DD) if len(date) == 5: # MM-DD month, day = date.split('-') date_parsed = f"2000-{month}-{day}" # Placeholder year for recurring else: date_parsed = date entry = { 'id': len(dates) + 1, 'name': name, 'date': date_parsed, 'recurring': recurring, 'category': category, 'created': datetime.now().isoformat(), } dates.append(entry) save_dates(dates) emoji = {'birthday': 'šŸŽ‚', 'anniversary': 'šŸ’', 'holiday': 'šŸŽ‰'}.get(category, 'šŸ“…') print(f"{emoji} Added: {name} ({date})") def days_until(date_str: str) -> int: """Calculate days until a date (handles recurring).""" today = datetime.now().date() # Parse date date = datetime.strptime(date_str, "%Y-%m-%d").date() # For recurring, find next occurrence this_year = date.replace(year=today.year) if this_year < today: this_year = date.replace(year=today.year + 1) return (this_year - today).days def upcoming(days: int = 30): """Show upcoming dates.""" dates = load_dates() if not dates: print("No dates saved. Add with: dates add ") return upcoming = [] for d in dates: days_left = days_until(d['date']) if days_left <= days: upcoming.append((days_left, d)) upcoming.sort(key=lambda x: x[0]) print(f"\nšŸ“… Upcoming ({days} days)") print("=" * 40) if not upcoming: print("Nothing in the next 30 days") return for days_left, d in upcoming: emoji = {'birthday': 'šŸŽ‚', 'anniversary': 'šŸ’', 'holiday': 'šŸŽ‰'}.get(d.get('category'), 'šŸ“…') if days_left == 0: print(f" {emoji} TODAY: {d['name']}") elif days_left == 1: print(f" {emoji} Tomorrow: {d['name']}") else: print(f" {emoji} {days_left} days: {d['name']}") print() def list_dates(): """List all important dates.""" dates = load_dates() if not dates: print("No dates saved. Add with: dates add ") return print(f"\nšŸ“… Important Dates ({len(dates)})") print("=" * 40) # Group by category by_cat = {} for d in dates: cat = d.get('category', 'general') if cat not in by_cat: by_cat[cat] = [] by_cat[cat].append(d) for cat, items in sorted(by_cat.items()): emoji = {'birthday': 'šŸŽ‚', 'anniversary': 'šŸ’', 'holiday': 'šŸŽ‰'}.get(cat, 'šŸ“…') print(f"\n{emoji} {cat.title()}") for d in items: date = d['date'][5:] # Just MM-DD days = days_until(d['date']) print(f" {date} {d['name']} ({days} days)") print() def main(): if len(sys.argv) < 2: upcoming(30) return cmd = sys.argv[1] if cmd == 'add' and len(sys.argv) > 3: name = sys.argv[2] date = sys.argv[3] category = sys.argv[4] if len(sys.argv) > 4 else 'general' add_date(name, date, category=category) elif cmd == 'upcoming': days = int(sys.argv[2]) if len(sys.argv) > 2 else 30 upcoming(days) elif cmd == 'list': list_dates() else: print("Usage:") print(" dates - Upcoming 30 days") print(" dates add [category]") print(" dates upcoming [days] - Show upcoming") print(" dates list - All dates") print("") print("Categories: birthday, anniversary, holiday, general") print("Example: dates add 'Mom Birthday' 03-15 birthday") if __name__ == "__main__": main()