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.
📏 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

View File

@ -99,6 +99,12 @@ class CronTool(Tool):
return "Error: no session context (channel/chat_id)"
if tz and not 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
delete_after = False

View File

@ -719,17 +719,15 @@ def cron_list(
table.add_column("Status")
table.add_column("Next Run")
import datetime
import time
from datetime import datetime as _dt
from zoneinfo import ZoneInfo
for job in jobs:
# Format schedule
if job.schedule.kind == "every":
sched = f"every {(job.schedule.every_ms or 0) // 1000}s"
elif job.schedule.kind == "cron":
sched = job.schedule.expr or ""
if job.schedule.tz:
sched = f"{sched} ({job.schedule.tz})"
sched = f"{job.schedule.expr or ''} ({job.schedule.tz})" if job.schedule.tz else (job.schedule.expr or "")
else:
sched = "one-time"
@ -737,13 +735,10 @@ def cron_list(
next_run = ""
if job.state.next_run_at_ms:
ts = job.state.next_run_at_ms / 1000
if job.schedule.kind == "cron" and job.schedule.tz:
try:
dt = datetime.fromtimestamp(ts, ZoneInfo(job.schedule.tz))
next_run = dt.strftime("%Y-%m-%d %H:%M")
except Exception:
next_run = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
else:
try:
tz = ZoneInfo(job.schedule.tz) if job.schedule.tz else None
next_run = _dt.fromtimestamp(ts, tz).strftime("%Y-%m-%d %H:%M")
except Exception:
next_run = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
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:
from croniter import croniter
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
tz = ZoneInfo(schedule.tz) if schedule.tz else datetime.now().astimezone().tzinfo
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>")
```
Timezone-aware cron:
```
cron(action="add", message="Morning standup", cron_expr="0 9 * * 1-5", tz="America/Vancouver")
```
List/remove:
```
cron(action="list")
@ -44,4 +49,9 @@ cron(action="remove", job_id="abc123")
| every hour | every_seconds: 3600 |
| every day at 8am | cron_expr: "0 8 * * *" |
| 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) |
## Timezone
Use `tz` with `cron_expr` to schedule in a specific IANA timezone. Without `tz`, the server's local timezone is used.