fix(cron): fix timezone display bug, add tz validation and skill docs

This commit is contained in:
Re-bin 2026-02-17 08:30:52 +00:00
parent 2c3a568e46
commit 6bae6a617f
5 changed files with 24 additions and 13 deletions

View File

@ -16,7 +16,7 @@
⚡️ Delivers core agent functionality in just **~4,000** lines of code — **99% smaller** than Clawdbot's 430k+ lines. ⚡️ Delivers core agent functionality in just **~4,000** lines of code — **99% smaller** than Clawdbot's 430k+ lines.
📏 Real-time line count: **3,668 lines** (run `bash core_agent_lines.sh` to verify anytime) 📏 Real-time line count: **3,689 lines** (run `bash core_agent_lines.sh` to verify anytime)
## 📢 News ## 📢 News

View File

@ -99,6 +99,12 @@ class CronTool(Tool):
return "Error: no session context (channel/chat_id)" return "Error: no session context (channel/chat_id)"
if tz and not cron_expr: if tz and not cron_expr:
return "Error: tz can only be used with cron_expr" return "Error: tz can only be used with cron_expr"
if tz:
from zoneinfo import ZoneInfo
try:
ZoneInfo(tz)
except (KeyError, Exception):
return f"Error: unknown timezone '{tz}'"
# Build schedule # Build schedule
delete_after = False delete_after = False

View File

@ -719,17 +719,15 @@ def cron_list(
table.add_column("Status") table.add_column("Status")
table.add_column("Next Run") table.add_column("Next Run")
import datetime
import time import time
from datetime import datetime as _dt
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
for job in jobs: for job in jobs:
# Format schedule # Format schedule
if job.schedule.kind == "every": if job.schedule.kind == "every":
sched = f"every {(job.schedule.every_ms or 0) // 1000}s" sched = f"every {(job.schedule.every_ms or 0) // 1000}s"
elif job.schedule.kind == "cron": elif job.schedule.kind == "cron":
sched = job.schedule.expr or "" sched = f"{job.schedule.expr or ''} ({job.schedule.tz})" if job.schedule.tz else (job.schedule.expr or "")
if job.schedule.tz:
sched = f"{sched} ({job.schedule.tz})"
else: else:
sched = "one-time" sched = "one-time"
@ -737,13 +735,10 @@ def cron_list(
next_run = "" next_run = ""
if job.state.next_run_at_ms: if job.state.next_run_at_ms:
ts = job.state.next_run_at_ms / 1000 ts = job.state.next_run_at_ms / 1000
if job.schedule.kind == "cron" and job.schedule.tz: try:
try: tz = ZoneInfo(job.schedule.tz) if job.schedule.tz else None
dt = datetime.fromtimestamp(ts, ZoneInfo(job.schedule.tz)) next_run = _dt.fromtimestamp(ts, tz).strftime("%Y-%m-%d %H:%M")
next_run = dt.strftime("%Y-%m-%d %H:%M") except Exception:
except Exception:
next_run = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
else:
next_run = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts)) next_run = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
status = "[green]enabled[/green]" if job.enabled else "[dim]disabled[/dim]" status = "[green]enabled[/green]" if job.enabled else "[dim]disabled[/dim]"

View File

@ -32,7 +32,7 @@ def _compute_next_run(schedule: CronSchedule, now_ms: int) -> int | None:
try: try:
from croniter import croniter from croniter import croniter
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
# Use the caller-provided reference time for deterministic scheduling. # Use caller-provided reference time for deterministic scheduling
base_time = now_ms / 1000 base_time = now_ms / 1000
tz = ZoneInfo(schedule.tz) if schedule.tz else datetime.now().astimezone().tzinfo tz = ZoneInfo(schedule.tz) if schedule.tz else datetime.now().astimezone().tzinfo
base_dt = datetime.fromtimestamp(base_time, tz=tz) base_dt = datetime.fromtimestamp(base_time, tz=tz)

View File

@ -30,6 +30,11 @@ One-time scheduled task (compute ISO datetime from current time):
cron(action="add", message="Remind me about the meeting", at="<ISO datetime>") cron(action="add", message="Remind me about the meeting", at="<ISO datetime>")
``` ```
Timezone-aware cron:
```
cron(action="add", message="Morning standup", cron_expr="0 9 * * 1-5", tz="America/Vancouver")
```
List/remove: List/remove:
``` ```
cron(action="list") cron(action="list")
@ -44,4 +49,9 @@ cron(action="remove", job_id="abc123")
| every hour | every_seconds: 3600 | | every hour | every_seconds: 3600 |
| every day at 8am | cron_expr: "0 8 * * *" | | every day at 8am | cron_expr: "0 8 * * *" |
| weekdays at 5pm | cron_expr: "0 17 * * 1-5" | | weekdays at 5pm | cron_expr: "0 17 * * 1-5" |
| 9am Vancouver time daily | cron_expr: "0 9 * * *", tz: "America/Vancouver" |
| at a specific time | at: ISO datetime string (compute from current time) | | at a specific time | at: ISO datetime string (compute from current time) |
## Timezone
Use `tz` with `cron_expr` to schedule in a specific IANA timezone. Without `tz`, the server's local timezone is used.