Add dates, gratitude, calc, timer - dates.py: Important dates and anniversaries - gratitude.py: Gratitude log - calc.py: Calculator and unit converter - timer.py: Countdown and stopwatch - 31 tools total!
This commit is contained in:
164
tools/dates.py
Executable file
164
tools/dates.py
Executable file
@ -0,0 +1,164 @@
|
||||
#!/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 <name> <MM-DD>")
|
||||
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 <name> <MM-DD>")
|
||||
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 <name> <MM-DD> [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()
|
||||
Reference in New Issue
Block a user